1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
25 use PVE
::RPCEnvironment
;
26 use PVE
::AccessControl
;
30 use PVE
::API2
::Firewall
::VM
;
31 use PVE
::API2
::Qemu
::Agent
;
32 use PVE
::VZDump
::Plugin
;
33 use PVE
::DataCenterConfig
;
37 if (!$ENV{PVE_GENERATING_DOCS
}) {
38 require PVE
::HA
::Env
::PVE2
;
39 import PVE
::HA
::Env
::PVE2
;
40 require PVE
::HA
::Config
;
41 import PVE
::HA
::Config
;
45 use Data
::Dumper
; # fixme: remove
47 use base
qw(PVE::RESTHandler);
49 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
51 my $resolve_cdrom_alias = sub {
54 if (my $value = $param->{cdrom
}) {
55 $value .= ",media=cdrom" if $value !~ m/media=/;
56 $param->{ide2
} = $value;
57 delete $param->{cdrom
};
61 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
62 my $check_storage_access = sub {
63 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
65 PVE
::QemuServer
::foreach_drive
($settings, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
71 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
73 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
75 } elsif ($isCDROM && ($volid eq 'cdrom')) {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
77 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
81 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
82 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
83 if !$scfg->{content
}->{images
};
85 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
89 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
90 if defined($settings->{vmstatestorage
});
93 my $check_storage_access_clone = sub {
94 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
98 PVE
::QemuServer
::foreach_drive
($conf, sub {
99 my ($ds, $drive) = @_;
101 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
103 my $volid = $drive->{file
};
105 return if !$volid || $volid eq 'none';
108 if ($volid eq 'cdrom') {
109 $rpcenv->check($authuser, "/", ['Sys.Console']);
111 # we simply allow access
112 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
114 $sharedvm = 0 if !$scfg->{shared
};
118 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
120 $sharedvm = 0 if !$scfg->{shared
};
122 $sid = $storage if $storage;
123 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
127 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
128 if defined($conf->{vmstatestorage
});
133 # Note: $pool is only needed when creating a VM, because pool permissions
134 # are automatically inherited if VM already exists inside a pool.
135 my $create_disks = sub {
136 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
143 my ($ds, $disk) = @_;
145 my $volid = $disk->{file
};
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
149 delete $disk->{size
};
150 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
151 } elsif (defined($volname) && $volname eq 'cloudinit') {
152 $storeid = $storeid // $default_storage;
153 die "no storage ID specified (and no default storage)\n" if !$storeid;
154 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
155 my $name = "vm-$vmid-cloudinit";
159 $fmt = $disk->{format
} // "qcow2";
162 $fmt = $disk->{format
} // "raw";
165 # Initial disk created with 4 MB and aligned to 4MB on regeneration
166 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
167 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
168 $disk->{file
} = $volid;
169 $disk->{media
} = 'cdrom';
170 push @$vollist, $volid;
171 delete $disk->{format
}; # no longer needed
172 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
173 } elsif ($volid =~ $NEW_DISK_RE) {
174 my ($storeid, $size) = ($2 || $default_storage, $3);
175 die "no storage ID specified (and no default storage)\n" if !$storeid;
176 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
177 my $fmt = $disk->{format
} || $defformat;
179 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
182 if ($ds eq 'efidisk0') {
183 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
185 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
187 push @$vollist, $volid;
188 $disk->{file
} = $volid;
189 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
190 delete $disk->{format
}; # no longer needed
191 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
194 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
196 my $volid_is_new = 1;
199 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
200 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
205 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
207 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
209 die "volume $volid does not exist\n" if !$size;
211 $disk->{size
} = $size;
214 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
218 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
220 # free allocated images on error
222 syslog
('err', "VM $vmid creating disks failed");
223 foreach my $volid (@$vollist) {
224 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
230 # modify vm config if everything went well
231 foreach my $ds (keys %$res) {
232 $conf->{$ds} = $res->{$ds};
249 my $memoryoptions = {
255 my $hwtypeoptions = {
268 my $generaloptions = {
275 'migrate_downtime' => 1,
276 'migrate_speed' => 1,
289 my $vmpoweroptions = {
296 'vmstatestorage' => 1,
299 my $cloudinitoptions = {
309 my $check_vm_modify_config_perm = sub {
310 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
312 return 1 if $authuser eq 'root@pam';
314 foreach my $opt (@$key_list) {
315 # some checks (e.g., disk, serial port, usb) need to be done somewhere
316 # else, as there the permission can be value dependend
317 next if PVE
::QemuServer
::is_valid_drivename
($opt);
318 next if $opt eq 'cdrom';
319 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
322 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
324 } elsif ($memoryoptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
326 } elsif ($hwtypeoptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
328 } elsif ($generaloptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
330 # special case for startup since it changes host behaviour
331 if ($opt eq 'startup') {
332 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
334 } elsif ($vmpoweroptions->{$opt}) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
336 } elsif ($diskoptions->{$opt}) {
337 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
338 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
339 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
340 } elsif ($opt eq 'vmstate') {
341 # the user needs Disk and PowerMgmt privileges to change the vmstate
342 # also needs privileges on the storage, that will be checked later
343 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
345 # catches hostpci\d+, args, lock, etc.
346 # new options will be checked here
347 die "only root can set '$opt' config\n";
354 __PACKAGE__-
>register_method({
358 description
=> "Virtual machine index (per node).",
360 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
364 protected
=> 1, # qemu pid files are only readable by root
366 additionalProperties
=> 0,
368 node
=> get_standard_option
('pve-node'),
372 description
=> "Determine the full status of active VMs.",
380 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
382 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
387 my $rpcenv = PVE
::RPCEnvironment
::get
();
388 my $authuser = $rpcenv->get_user();
390 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
393 foreach my $vmid (keys %$vmstatus) {
394 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
396 my $data = $vmstatus->{$vmid};
405 __PACKAGE__-
>register_method({
409 description
=> "Create or restore a virtual machine.",
411 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
412 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
413 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
414 user
=> 'all', # check inside
419 additionalProperties
=> 0,
420 properties
=> PVE
::QemuServer
::json_config_properties
(
422 node
=> get_standard_option
('pve-node'),
423 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
425 description
=> "The backup file.",
429 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
431 storage
=> get_standard_option
('pve-storage-id', {
432 description
=> "Default storage.",
434 completion
=> \
&PVE
::QemuServer
::complete_storage
,
439 description
=> "Allow to overwrite existing VM.",
440 requires
=> 'archive',
445 description
=> "Assign a unique random ethernet address.",
446 requires
=> 'archive',
450 type
=> 'string', format
=> 'pve-poolid',
451 description
=> "Add the VM to the specified pool.",
454 description
=> "Override I/O bandwidth limit (in KiB/s).",
458 default => 'restore limit from datacenter or storage config',
464 description
=> "Start VM after it was created successfully.",
474 my $rpcenv = PVE
::RPCEnvironment
::get
();
475 my $authuser = $rpcenv->get_user();
477 my $node = extract_param
($param, 'node');
478 my $vmid = extract_param
($param, 'vmid');
480 my $archive = extract_param
($param, 'archive');
481 my $is_restore = !!$archive;
483 my $bwlimit = extract_param
($param, 'bwlimit');
484 my $force = extract_param
($param, 'force');
485 my $pool = extract_param
($param, 'pool');
486 my $start_after_create = extract_param
($param, 'start');
487 my $storage = extract_param
($param, 'storage');
488 my $unique = extract_param
($param, 'unique');
490 if (defined(my $ssh_keys = $param->{sshkeys
})) {
491 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
492 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
495 PVE
::Cluster
::check_cfs_quorum
();
497 my $filename = PVE
::QemuConfig-
>config_file($vmid);
498 my $storecfg = PVE
::Storage
::config
();
500 if (defined($pool)) {
501 $rpcenv->check_pool_exist($pool);
504 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
505 if defined($storage);
507 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
509 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
511 } elsif ($archive && $force && (-f
$filename) &&
512 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
513 # OK: user has VM.Backup permissions, and want to restore an existing VM
519 &$resolve_cdrom_alias($param);
521 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
523 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
525 foreach my $opt (keys %$param) {
526 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
527 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
528 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
530 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
531 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
535 PVE
::QemuServer
::add_random_macs
($param);
537 my $keystr = join(' ', keys %$param);
538 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
540 if ($archive eq '-') {
541 die "pipe requires cli environment\n"
542 if $rpcenv->{type
} ne 'cli';
544 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
545 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
549 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
551 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
552 die "$emsg $@" if $@;
554 my $restorefn = sub {
555 my $conf = PVE
::QemuConfig-
>load_config($vmid);
557 PVE
::QemuConfig-
>check_protection($conf, $emsg);
559 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
562 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
568 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
569 # Convert restored VM to template if backup was VM template
570 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
571 warn "Convert to template.\n";
572 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
576 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
582 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
584 if ($start_after_create) {
585 print "Execute autostart\n";
586 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
592 # ensure no old replication state are exists
593 PVE
::ReplicationState
::delete_guest_states
($vmid);
597 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
601 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
603 if (!$conf->{bootdisk
}) {
604 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
605 $conf->{bootdisk
} = $firstdisk if $firstdisk;
608 # auto generate uuid if user did not specify smbios1 option
609 if (!$conf->{smbios1
}) {
610 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
613 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
614 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
617 PVE
::QemuConfig-
>write_config($vmid, $conf);
623 foreach my $volid (@$vollist) {
624 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
630 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
633 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
635 if ($start_after_create) {
636 print "Execute autostart\n";
637 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
642 my ($code, $worker_name);
644 $worker_name = 'qmrestore';
646 eval { $restorefn->() };
648 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
654 $worker_name = 'qmcreate';
656 eval { $createfn->() };
659 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
660 unlink($conffile) or die "failed to remove config file: $!\n";
668 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
671 __PACKAGE__-
>register_method({
676 description
=> "Directory index",
681 additionalProperties
=> 0,
683 node
=> get_standard_option
('pve-node'),
684 vmid
=> get_standard_option
('pve-vmid'),
692 subdir
=> { type
=> 'string' },
695 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
701 { subdir
=> 'config' },
702 { subdir
=> 'pending' },
703 { subdir
=> 'status' },
704 { subdir
=> 'unlink' },
705 { subdir
=> 'vncproxy' },
706 { subdir
=> 'termproxy' },
707 { subdir
=> 'migrate' },
708 { subdir
=> 'resize' },
709 { subdir
=> 'move' },
711 { subdir
=> 'rrddata' },
712 { subdir
=> 'monitor' },
713 { subdir
=> 'agent' },
714 { subdir
=> 'snapshot' },
715 { subdir
=> 'spiceproxy' },
716 { subdir
=> 'sendkey' },
717 { subdir
=> 'firewall' },
723 __PACKAGE__-
>register_method ({
724 subclass
=> "PVE::API2::Firewall::VM",
725 path
=> '{vmid}/firewall',
728 __PACKAGE__-
>register_method ({
729 subclass
=> "PVE::API2::Qemu::Agent",
730 path
=> '{vmid}/agent',
733 __PACKAGE__-
>register_method({
735 path
=> '{vmid}/rrd',
737 protected
=> 1, # fixme: can we avoid that?
739 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
741 description
=> "Read VM RRD statistics (returns PNG)",
743 additionalProperties
=> 0,
745 node
=> get_standard_option
('pve-node'),
746 vmid
=> get_standard_option
('pve-vmid'),
748 description
=> "Specify the time frame you are interested in.",
750 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
753 description
=> "The list of datasources you want to display.",
754 type
=> 'string', format
=> 'pve-configid-list',
757 description
=> "The RRD consolidation function",
759 enum
=> [ 'AVERAGE', 'MAX' ],
767 filename
=> { type
=> 'string' },
773 return PVE
::RRD
::create_rrd_graph
(
774 "pve2-vm/$param->{vmid}", $param->{timeframe
},
775 $param->{ds
}, $param->{cf
});
779 __PACKAGE__-
>register_method({
781 path
=> '{vmid}/rrddata',
783 protected
=> 1, # fixme: can we avoid that?
785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
787 description
=> "Read VM RRD statistics",
789 additionalProperties
=> 0,
791 node
=> get_standard_option
('pve-node'),
792 vmid
=> get_standard_option
('pve-vmid'),
794 description
=> "Specify the time frame you are interested in.",
796 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
799 description
=> "The RRD consolidation function",
801 enum
=> [ 'AVERAGE', 'MAX' ],
816 return PVE
::RRD
::create_rrd_data
(
817 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
821 __PACKAGE__-
>register_method({
823 path
=> '{vmid}/config',
826 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
828 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
831 additionalProperties
=> 0,
833 node
=> get_standard_option
('pve-node'),
834 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
836 description
=> "Get current values (instead of pending values).",
841 snapshot
=> get_standard_option
('pve-snapshot-name', {
842 description
=> "Fetch config values from given snapshot.",
845 my ($cmd, $pname, $cur, $args) = @_;
846 PVE
::QemuConfig-
>snapshot_list($args->[0]);
852 description
=> "The current VM configuration.",
854 properties
=> PVE
::QemuServer
::json_config_properties
({
857 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
864 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
865 current
=> "cannot use 'snapshot' parameter with 'current'"})
866 if ($param->{snapshot
} && $param->{current
});
869 if ($param->{snapshot
}) {
870 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
872 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
874 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
879 __PACKAGE__-
>register_method({
880 name
=> 'vm_pending',
881 path
=> '{vmid}/pending',
884 description
=> "Get virtual machine configuration, including pending changes.",
886 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
889 additionalProperties
=> 0,
891 node
=> get_standard_option
('pve-node'),
892 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
901 description
=> "Configuration option name.",
905 description
=> "Current value.",
910 description
=> "Pending value.",
915 description
=> "Indicates a pending delete request if present and not 0. " .
916 "The value 2 indicates a force-delete request.",
928 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
930 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
932 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
933 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
935 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
938 # POST/PUT {vmid}/config implementation
940 # The original API used PUT (idempotent) an we assumed that all operations
941 # are fast. But it turned out that almost any configuration change can
942 # involve hot-plug actions, or disk alloc/free. Such actions can take long
943 # time to complete and have side effects (not idempotent).
945 # The new implementation uses POST and forks a worker process. We added
946 # a new option 'background_delay'. If specified we wait up to
947 # 'background_delay' second for the worker task to complete. It returns null
948 # if the task is finished within that time, else we return the UPID.
950 my $update_vm_api = sub {
951 my ($param, $sync) = @_;
953 my $rpcenv = PVE
::RPCEnvironment
::get
();
955 my $authuser = $rpcenv->get_user();
957 my $node = extract_param
($param, 'node');
959 my $vmid = extract_param
($param, 'vmid');
961 my $digest = extract_param
($param, 'digest');
963 my $background_delay = extract_param
($param, 'background_delay');
965 if (defined(my $cipassword = $param->{cipassword
})) {
966 # Same logic as in cloud-init (but with the regex fixed...)
967 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
968 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
971 my @paramarr = (); # used for log message
972 foreach my $key (sort keys %$param) {
973 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
974 push @paramarr, "-$key", $value;
977 my $skiplock = extract_param
($param, 'skiplock');
978 raise_param_exc
({ skiplock
=> "Only root may use this option." })
979 if $skiplock && $authuser ne 'root@pam';
981 my $delete_str = extract_param
($param, 'delete');
983 my $revert_str = extract_param
($param, 'revert');
985 my $force = extract_param
($param, 'force');
987 if (defined(my $ssh_keys = $param->{sshkeys
})) {
988 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
989 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
992 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
994 my $storecfg = PVE
::Storage
::config
();
996 my $defaults = PVE
::QemuServer
::load_defaults
();
998 &$resolve_cdrom_alias($param);
1000 # now try to verify all parameters
1003 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1004 if (!PVE
::QemuServer
::option_exists
($opt)) {
1005 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1008 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1009 "-revert $opt' at the same time" })
1010 if defined($param->{$opt});
1012 $revert->{$opt} = 1;
1016 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1017 $opt = 'ide2' if $opt eq 'cdrom';
1019 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1020 "-delete $opt' at the same time" })
1021 if defined($param->{$opt});
1023 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1024 "-revert $opt' at the same time" })
1027 if (!PVE
::QemuServer
::option_exists
($opt)) {
1028 raise_param_exc
({ delete => "unknown option '$opt'" });
1034 my $repl_conf = PVE
::ReplicationConfig-
>new();
1035 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1036 my $check_replication = sub {
1038 return if !$is_replicated;
1039 my $volid = $drive->{file
};
1040 return if !$volid || !($drive->{replicate
}//1);
1041 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1043 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1044 return if $volname eq 'cloudinit';
1047 if ($volid =~ $NEW_DISK_RE) {
1049 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1051 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1053 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1054 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1055 return if $scfg->{shared
};
1056 die "cannot add non-replicatable volume to a replicated VM\n";
1059 foreach my $opt (keys %$param) {
1060 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1061 # cleanup drive path
1062 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1063 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1064 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1065 $check_replication->($drive);
1066 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1067 } elsif ($opt =~ m/^net(\d+)$/) {
1069 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1070 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1071 } elsif ($opt eq 'vmgenid') {
1072 if ($param->{$opt} eq '1') {
1073 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1075 } elsif ($opt eq 'hookscript') {
1076 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1077 raise_param_exc
({ $opt => $@ }) if $@;
1081 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1083 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1085 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1087 my $updatefn = sub {
1089 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1091 die "checksum missmatch (file change by other user?)\n"
1092 if $digest && $digest ne $conf->{digest
};
1094 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1095 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1096 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1097 delete $conf->{lock}; # for check lock check, not written out
1098 push @delete, 'lock'; # this is the real deal to write it out
1100 push @delete, 'runningmachine' if $conf->{runningmachine
};
1103 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1105 foreach my $opt (keys %$revert) {
1106 if (defined($conf->{$opt})) {
1107 $param->{$opt} = $conf->{$opt};
1108 } elsif (defined($conf->{pending
}->{$opt})) {
1113 if ($param->{memory
} || defined($param->{balloon
})) {
1114 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1115 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1117 die "balloon value too large (must be smaller than assigned memory)\n"
1118 if $balloon && $balloon > $maxmem;
1121 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1125 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1127 # write updates to pending section
1129 my $modified = {}; # record what $option we modify
1131 foreach my $opt (@delete) {
1132 $modified->{$opt} = 1;
1133 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1135 # value of what we want to delete, independent if pending or not
1136 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1137 if (!defined($val)) {
1138 warn "cannot delete '$opt' - not set in current configuration!\n";
1139 $modified->{$opt} = 0;
1142 my $is_pending_val = defined($conf->{pending
}->{$opt});
1143 delete $conf->{pending
}->{$opt};
1145 if ($opt =~ m/^unused/) {
1146 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1147 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1148 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1149 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1150 delete $conf->{$opt};
1151 PVE
::QemuConfig-
>write_config($vmid, $conf);
1153 } elsif ($opt eq 'vmstate') {
1154 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1155 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1156 delete $conf->{$opt};
1157 PVE
::QemuConfig-
>write_config($vmid, $conf);
1159 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1160 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1161 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1162 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1164 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1165 PVE
::QemuConfig-
>write_config($vmid, $conf);
1166 } elsif ($opt =~ m/^serial\d+$/) {
1167 if ($val eq 'socket') {
1168 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1169 } elsif ($authuser ne 'root@pam') {
1170 die "only root can delete '$opt' config for real devices\n";
1172 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1173 PVE
::QemuConfig-
>write_config($vmid, $conf);
1174 } elsif ($opt =~ m/^usb\d+$/) {
1175 if ($val =~ m/spice/) {
1176 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1177 } elsif ($authuser ne 'root@pam') {
1178 die "only root can delete '$opt' config for real devices\n";
1180 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1181 PVE
::QemuConfig-
>write_config($vmid, $conf);
1183 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1184 PVE
::QemuConfig-
>write_config($vmid, $conf);
1188 foreach my $opt (keys %$param) { # add/change
1189 $modified->{$opt} = 1;
1190 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1191 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1193 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1195 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1196 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1197 # FIXME: cloudinit: CDROM or Disk?
1198 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1199 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1201 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1203 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1204 if defined($conf->{pending
}->{$opt});
1206 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1207 } elsif ($opt =~ m/^serial\d+/) {
1208 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1209 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1210 } elsif ($authuser ne 'root@pam') {
1211 die "only root can modify '$opt' config for real devices\n";
1213 $conf->{pending
}->{$opt} = $param->{$opt};
1214 } elsif ($opt =~ m/^usb\d+/) {
1215 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1216 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1217 } elsif ($authuser ne 'root@pam') {
1218 die "only root can modify '$opt' config for real devices\n";
1220 $conf->{pending
}->{$opt} = $param->{$opt};
1222 $conf->{pending
}->{$opt} = $param->{$opt};
1224 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1225 PVE
::QemuConfig-
>write_config($vmid, $conf);
1228 # remove pending changes when nothing changed
1229 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1230 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1231 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1233 return if !scalar(keys %{$conf->{pending
}});
1235 my $running = PVE
::QemuServer
::check_running
($vmid);
1237 # apply pending changes
1239 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1243 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1245 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1247 raise_param_exc
($errors) if scalar(keys %$errors);
1256 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1258 if ($background_delay) {
1260 # Note: It would be better to do that in the Event based HTTPServer
1261 # to avoid blocking call to sleep.
1263 my $end_time = time() + $background_delay;
1265 my $task = PVE
::Tools
::upid_decode
($upid);
1268 while (time() < $end_time) {
1269 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1271 sleep(1); # this gets interrupted when child process ends
1275 my $status = PVE
::Tools
::upid_read_status
($upid);
1276 return undef if $status eq 'OK';
1285 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1288 my $vm_config_perm_list = [
1293 'VM.Config.Network',
1295 'VM.Config.Options',
1298 __PACKAGE__-
>register_method({
1299 name
=> 'update_vm_async',
1300 path
=> '{vmid}/config',
1304 description
=> "Set virtual machine options (asynchrounous API).",
1306 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1309 additionalProperties
=> 0,
1310 properties
=> PVE
::QemuServer
::json_config_properties
(
1312 node
=> get_standard_option
('pve-node'),
1313 vmid
=> get_standard_option
('pve-vmid'),
1314 skiplock
=> get_standard_option
('skiplock'),
1316 type
=> 'string', format
=> 'pve-configid-list',
1317 description
=> "A list of settings you want to delete.",
1321 type
=> 'string', format
=> 'pve-configid-list',
1322 description
=> "Revert a pending change.",
1327 description
=> $opt_force_description,
1329 requires
=> 'delete',
1333 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1337 background_delay
=> {
1339 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1350 code
=> $update_vm_api,
1353 __PACKAGE__-
>register_method({
1354 name
=> 'update_vm',
1355 path
=> '{vmid}/config',
1359 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1361 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1364 additionalProperties
=> 0,
1365 properties
=> PVE
::QemuServer
::json_config_properties
(
1367 node
=> get_standard_option
('pve-node'),
1368 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1369 skiplock
=> get_standard_option
('skiplock'),
1371 type
=> 'string', format
=> 'pve-configid-list',
1372 description
=> "A list of settings you want to delete.",
1376 type
=> 'string', format
=> 'pve-configid-list',
1377 description
=> "Revert a pending change.",
1382 description
=> $opt_force_description,
1384 requires
=> 'delete',
1388 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1394 returns
=> { type
=> 'null' },
1397 &$update_vm_api($param, 1);
1402 __PACKAGE__-
>register_method({
1403 name
=> 'destroy_vm',
1408 description
=> "Destroy the vm (also delete all used/owned volumes).",
1410 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1413 additionalProperties
=> 0,
1415 node
=> get_standard_option
('pve-node'),
1416 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1417 skiplock
=> get_standard_option
('skiplock'),
1420 description
=> "Remove vmid from backup cron jobs.",
1431 my $rpcenv = PVE
::RPCEnvironment
::get
();
1432 my $authuser = $rpcenv->get_user();
1433 my $vmid = $param->{vmid
};
1435 my $skiplock = $param->{skiplock
};
1436 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1437 if $skiplock && $authuser ne 'root@pam';
1440 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1441 my $storecfg = PVE
::Storage
::config
();
1442 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1443 die "unable to remove VM $vmid - used in HA resources\n"
1444 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1446 if (!$param->{purge
}) {
1447 # don't allow destroy if with replication jobs but no purge param
1448 my $repl_conf = PVE
::ReplicationConfig-
>new();
1449 $repl_conf->check_for_existing_jobs($vmid);
1452 # early tests (repeat after locking)
1453 die "VM $vmid is running - destroy failed\n"
1454 if PVE
::QemuServer
::check_running
($vmid);
1459 syslog
('info', "destroy VM $vmid: $upid\n");
1460 PVE
::QemuConfig-
>lock_config($vmid, sub {
1461 die "VM $vmid is running - destroy failed\n"
1462 if (PVE
::QemuServer
::check_running
($vmid));
1464 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1466 PVE
::AccessControl
::remove_vm_access
($vmid);
1467 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1468 if ($param->{purge
}) {
1469 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1470 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1473 # only now remove the zombie config, else we can have reuse race
1474 PVE
::QemuConfig-
>destroy_config($vmid);
1478 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1481 __PACKAGE__-
>register_method({
1483 path
=> '{vmid}/unlink',
1487 description
=> "Unlink/delete disk images.",
1489 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1492 additionalProperties
=> 0,
1494 node
=> get_standard_option
('pve-node'),
1495 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1497 type
=> 'string', format
=> 'pve-configid-list',
1498 description
=> "A list of disk IDs you want to delete.",
1502 description
=> $opt_force_description,
1507 returns
=> { type
=> 'null'},
1511 $param->{delete} = extract_param
($param, 'idlist');
1513 __PACKAGE__-
>update_vm($param);
1520 __PACKAGE__-
>register_method({
1522 path
=> '{vmid}/vncproxy',
1526 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1528 description
=> "Creates a TCP VNC proxy connections.",
1530 additionalProperties
=> 0,
1532 node
=> get_standard_option
('pve-node'),
1533 vmid
=> get_standard_option
('pve-vmid'),
1537 description
=> "starts websockify instead of vncproxy",
1542 additionalProperties
=> 0,
1544 user
=> { type
=> 'string' },
1545 ticket
=> { type
=> 'string' },
1546 cert
=> { type
=> 'string' },
1547 port
=> { type
=> 'integer' },
1548 upid
=> { type
=> 'string' },
1554 my $rpcenv = PVE
::RPCEnvironment
::get
();
1556 my $authuser = $rpcenv->get_user();
1558 my $vmid = $param->{vmid
};
1559 my $node = $param->{node
};
1560 my $websocket = $param->{websocket
};
1562 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1563 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1565 my $authpath = "/vms/$vmid";
1567 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1569 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1575 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1576 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1577 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1578 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1579 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1581 $family = PVE
::Tools
::get_host_address_family
($node);
1584 my $port = PVE
::Tools
::next_vnc_port
($family);
1591 syslog
('info', "starting vnc proxy $upid\n");
1597 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1599 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1600 '-timeout', $timeout, '-authpath', $authpath,
1601 '-perm', 'Sys.Console'];
1603 if ($param->{websocket
}) {
1604 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1605 push @$cmd, '-notls', '-listen', 'localhost';
1608 push @$cmd, '-c', @$remcmd, @$termcmd;
1610 PVE
::Tools
::run_command
($cmd);
1614 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1616 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1618 my $sock = IO
::Socket
::IP-
>new(
1623 GetAddrInfoFlags
=> 0,
1624 ) or die "failed to create socket: $!\n";
1625 # Inside the worker we shouldn't have any previous alarms
1626 # running anyway...:
1628 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1630 accept(my $cli, $sock) or die "connection failed: $!\n";
1633 if (PVE
::Tools
::run_command
($cmd,
1634 output
=> '>&'.fileno($cli),
1635 input
=> '<&'.fileno($cli),
1638 die "Failed to run vncproxy.\n";
1645 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1647 PVE
::Tools
::wait_for_vnc_port
($port);
1658 __PACKAGE__-
>register_method({
1659 name
=> 'termproxy',
1660 path
=> '{vmid}/termproxy',
1664 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1666 description
=> "Creates a TCP proxy connections.",
1668 additionalProperties
=> 0,
1670 node
=> get_standard_option
('pve-node'),
1671 vmid
=> get_standard_option
('pve-vmid'),
1675 enum
=> [qw(serial0 serial1 serial2 serial3)],
1676 description
=> "opens a serial terminal (defaults to display)",
1681 additionalProperties
=> 0,
1683 user
=> { type
=> 'string' },
1684 ticket
=> { type
=> 'string' },
1685 port
=> { type
=> 'integer' },
1686 upid
=> { type
=> 'string' },
1692 my $rpcenv = PVE
::RPCEnvironment
::get
();
1694 my $authuser = $rpcenv->get_user();
1696 my $vmid = $param->{vmid
};
1697 my $node = $param->{node
};
1698 my $serial = $param->{serial
};
1700 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1702 if (!defined($serial)) {
1703 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1704 $serial = $conf->{vga
};
1708 my $authpath = "/vms/$vmid";
1710 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1715 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1716 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1717 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1718 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1719 push @$remcmd, '--';
1721 $family = PVE
::Tools
::get_host_address_family
($node);
1724 my $port = PVE
::Tools
::next_vnc_port
($family);
1726 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1727 push @$termcmd, '-iface', $serial if $serial;
1732 syslog
('info', "starting qemu termproxy $upid\n");
1734 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1735 '--perm', 'VM.Console', '--'];
1736 push @$cmd, @$remcmd, @$termcmd;
1738 PVE
::Tools
::run_command
($cmd);
1741 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1743 PVE
::Tools
::wait_for_vnc_port
($port);
1753 __PACKAGE__-
>register_method({
1754 name
=> 'vncwebsocket',
1755 path
=> '{vmid}/vncwebsocket',
1758 description
=> "You also need to pass a valid ticket (vncticket).",
1759 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1761 description
=> "Opens a weksocket for VNC traffic.",
1763 additionalProperties
=> 0,
1765 node
=> get_standard_option
('pve-node'),
1766 vmid
=> get_standard_option
('pve-vmid'),
1768 description
=> "Ticket from previous call to vncproxy.",
1773 description
=> "Port number returned by previous vncproxy call.",
1783 port
=> { type
=> 'string' },
1789 my $rpcenv = PVE
::RPCEnvironment
::get
();
1791 my $authuser = $rpcenv->get_user();
1793 my $vmid = $param->{vmid
};
1794 my $node = $param->{node
};
1796 my $authpath = "/vms/$vmid";
1798 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1800 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1802 # Note: VNC ports are acessible from outside, so we do not gain any
1803 # security if we verify that $param->{port} belongs to VM $vmid. This
1804 # check is done by verifying the VNC ticket (inside VNC protocol).
1806 my $port = $param->{port
};
1808 return { port
=> $port };
1811 __PACKAGE__-
>register_method({
1812 name
=> 'spiceproxy',
1813 path
=> '{vmid}/spiceproxy',
1818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1820 description
=> "Returns a SPICE configuration to connect to the VM.",
1822 additionalProperties
=> 0,
1824 node
=> get_standard_option
('pve-node'),
1825 vmid
=> get_standard_option
('pve-vmid'),
1826 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1829 returns
=> get_standard_option
('remote-viewer-config'),
1833 my $rpcenv = PVE
::RPCEnvironment
::get
();
1835 my $authuser = $rpcenv->get_user();
1837 my $vmid = $param->{vmid
};
1838 my $node = $param->{node
};
1839 my $proxy = $param->{proxy
};
1841 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1842 my $title = "VM $vmid";
1843 $title .= " - ". $conf->{name
} if $conf->{name
};
1845 my $port = PVE
::QemuServer
::spice_port
($vmid);
1847 my ($ticket, undef, $remote_viewer_config) =
1848 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1850 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1851 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1853 return $remote_viewer_config;
1856 __PACKAGE__-
>register_method({
1858 path
=> '{vmid}/status',
1861 description
=> "Directory index",
1866 additionalProperties
=> 0,
1868 node
=> get_standard_option
('pve-node'),
1869 vmid
=> get_standard_option
('pve-vmid'),
1877 subdir
=> { type
=> 'string' },
1880 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1886 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1889 { subdir
=> 'current' },
1890 { subdir
=> 'start' },
1891 { subdir
=> 'stop' },
1892 { subdir
=> 'reset' },
1893 { subdir
=> 'shutdown' },
1894 { subdir
=> 'suspend' },
1895 { subdir
=> 'reboot' },
1901 __PACKAGE__-
>register_method({
1902 name
=> 'vm_status',
1903 path
=> '{vmid}/status/current',
1906 protected
=> 1, # qemu pid files are only readable by root
1907 description
=> "Get virtual machine status.",
1909 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1912 additionalProperties
=> 0,
1914 node
=> get_standard_option
('pve-node'),
1915 vmid
=> get_standard_option
('pve-vmid'),
1921 %$PVE::QemuServer
::vmstatus_return_properties
,
1923 description
=> "HA manager service status.",
1927 description
=> "Qemu VGA configuration supports spice.",
1932 description
=> "Qemu GuestAgent enabled in config.",
1942 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1944 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1945 my $status = $vmstatus->{$param->{vmid
}};
1947 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1949 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1950 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1955 __PACKAGE__-
>register_method({
1957 path
=> '{vmid}/status/start',
1961 description
=> "Start virtual machine.",
1963 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1966 additionalProperties
=> 0,
1968 node
=> get_standard_option
('pve-node'),
1969 vmid
=> get_standard_option
('pve-vmid',
1970 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1971 skiplock
=> get_standard_option
('skiplock'),
1972 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1973 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1976 enum
=> ['secure', 'insecure'],
1977 description
=> "Migration traffic is encrypted using an SSH " .
1978 "tunnel by default. On secure, completely private networks " .
1979 "this can be disabled to increase performance.",
1982 migration_network
=> {
1983 type
=> 'string', format
=> 'CIDR',
1984 description
=> "CIDR of the (sub) network that is used for migration.",
1987 machine
=> get_standard_option
('pve-qemu-machine'),
1989 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1994 description
=> "Wait maximal timeout seconds.",
1997 default => 'max(30, vm memory in GiB)',
2008 my $rpcenv = PVE
::RPCEnvironment
::get
();
2009 my $authuser = $rpcenv->get_user();
2011 my $node = extract_param
($param, 'node');
2012 my $vmid = extract_param
($param, 'vmid');
2013 my $timeout = extract_param
($param, 'timeout');
2015 my $machine = extract_param
($param, 'machine');
2017 my $get_root_param = sub {
2018 my $value = extract_param
($param, $_[0]);
2019 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2020 if $value && $authuser ne 'root@pam';
2024 my $stateuri = $get_root_param->('stateuri');
2025 my $skiplock = $get_root_param->('skiplock');
2026 my $migratedfrom = $get_root_param->('migratedfrom');
2027 my $migration_type = $get_root_param->('migration_type');
2028 my $migration_network = $get_root_param->('migration_network');
2029 my $targetstorage = $get_root_param->('targetstorage');
2031 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2032 if $targetstorage && !$migratedfrom;
2034 # read spice ticket from STDIN
2036 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2037 if (defined(my $line = <STDIN
>)) {
2039 $spice_ticket = $line;
2043 PVE
::Cluster
::check_cfs_quorum
();
2045 my $storecfg = PVE
::Storage
::config
();
2047 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2051 print "Requesting HA start for VM $vmid\n";
2053 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2054 PVE
::Tools
::run_command
($cmd);
2058 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2065 syslog
('info', "start VM $vmid: $upid\n");
2067 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine,
2068 $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout);
2072 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2076 __PACKAGE__-
>register_method({
2078 path
=> '{vmid}/status/stop',
2082 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2083 "is akin to pulling the power plug of a running computer and may damage the VM data",
2085 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2088 additionalProperties
=> 0,
2090 node
=> get_standard_option
('pve-node'),
2091 vmid
=> get_standard_option
('pve-vmid',
2092 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2093 skiplock
=> get_standard_option
('skiplock'),
2094 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2096 description
=> "Wait maximal timeout seconds.",
2102 description
=> "Do not deactivate storage volumes.",
2115 my $rpcenv = PVE
::RPCEnvironment
::get
();
2116 my $authuser = $rpcenv->get_user();
2118 my $node = extract_param
($param, 'node');
2119 my $vmid = extract_param
($param, 'vmid');
2121 my $skiplock = extract_param
($param, 'skiplock');
2122 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2123 if $skiplock && $authuser ne 'root@pam';
2125 my $keepActive = extract_param
($param, 'keepActive');
2126 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2127 if $keepActive && $authuser ne 'root@pam';
2129 my $migratedfrom = extract_param
($param, 'migratedfrom');
2130 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2131 if $migratedfrom && $authuser ne 'root@pam';
2134 my $storecfg = PVE
::Storage
::config
();
2136 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2141 print "Requesting HA stop for VM $vmid\n";
2143 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2144 PVE
::Tools
::run_command
($cmd);
2148 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2154 syslog
('info', "stop VM $vmid: $upid\n");
2156 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2157 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2161 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2165 __PACKAGE__-
>register_method({
2167 path
=> '{vmid}/status/reset',
2171 description
=> "Reset virtual machine.",
2173 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2176 additionalProperties
=> 0,
2178 node
=> get_standard_option
('pve-node'),
2179 vmid
=> get_standard_option
('pve-vmid',
2180 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2181 skiplock
=> get_standard_option
('skiplock'),
2190 my $rpcenv = PVE
::RPCEnvironment
::get
();
2192 my $authuser = $rpcenv->get_user();
2194 my $node = extract_param
($param, 'node');
2196 my $vmid = extract_param
($param, 'vmid');
2198 my $skiplock = extract_param
($param, 'skiplock');
2199 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2200 if $skiplock && $authuser ne 'root@pam';
2202 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2207 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2212 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2215 __PACKAGE__-
>register_method({
2216 name
=> 'vm_shutdown',
2217 path
=> '{vmid}/status/shutdown',
2221 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2222 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2224 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2227 additionalProperties
=> 0,
2229 node
=> get_standard_option
('pve-node'),
2230 vmid
=> get_standard_option
('pve-vmid',
2231 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2232 skiplock
=> get_standard_option
('skiplock'),
2234 description
=> "Wait maximal timeout seconds.",
2240 description
=> "Make sure the VM stops.",
2246 description
=> "Do not deactivate storage volumes.",
2259 my $rpcenv = PVE
::RPCEnvironment
::get
();
2260 my $authuser = $rpcenv->get_user();
2262 my $node = extract_param
($param, 'node');
2263 my $vmid = extract_param
($param, 'vmid');
2265 my $skiplock = extract_param
($param, 'skiplock');
2266 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2267 if $skiplock && $authuser ne 'root@pam';
2269 my $keepActive = extract_param
($param, 'keepActive');
2270 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2271 if $keepActive && $authuser ne 'root@pam';
2273 my $storecfg = PVE
::Storage
::config
();
2277 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2278 # otherwise, we will infer a shutdown command, but run into the timeout,
2279 # then when the vm is resumed, it will instantly shutdown
2281 # checking the qmp status here to get feedback to the gui/cli/api
2282 # and the status query should not take too long
2283 my $qmpstatus = eval {
2284 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2285 mon_cmd
($vmid, "query-status");
2289 if (!$err && $qmpstatus->{status
} eq "paused") {
2290 if ($param->{forceStop
}) {
2291 warn "VM is paused - stop instead of shutdown\n";
2294 die "VM is paused - cannot shutdown\n";
2298 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2300 my $timeout = $param->{timeout
} // 60;
2304 print "Requesting HA stop for VM $vmid\n";
2306 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2307 PVE
::Tools
::run_command
($cmd);
2311 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2318 syslog
('info', "shutdown VM $vmid: $upid\n");
2320 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2321 $shutdown, $param->{forceStop
}, $keepActive);
2325 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2329 __PACKAGE__-
>register_method({
2330 name
=> 'vm_reboot',
2331 path
=> '{vmid}/status/reboot',
2335 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2337 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2340 additionalProperties
=> 0,
2342 node
=> get_standard_option
('pve-node'),
2343 vmid
=> get_standard_option
('pve-vmid',
2344 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2346 description
=> "Wait maximal timeout seconds for the shutdown.",
2359 my $rpcenv = PVE
::RPCEnvironment
::get
();
2360 my $authuser = $rpcenv->get_user();
2362 my $node = extract_param
($param, 'node');
2363 my $vmid = extract_param
($param, 'vmid');
2365 my $qmpstatus = eval {
2366 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2367 mon_cmd
($vmid, "query-status");
2371 if (!$err && $qmpstatus->{status
} eq "paused") {
2372 die "VM is paused - cannot shutdown\n";
2375 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2380 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2381 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2385 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2388 __PACKAGE__-
>register_method({
2389 name
=> 'vm_suspend',
2390 path
=> '{vmid}/status/suspend',
2394 description
=> "Suspend virtual machine.",
2396 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2397 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2398 " on the storage for the vmstate.",
2399 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2402 additionalProperties
=> 0,
2404 node
=> get_standard_option
('pve-node'),
2405 vmid
=> get_standard_option
('pve-vmid',
2406 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2407 skiplock
=> get_standard_option
('skiplock'),
2412 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2414 statestorage
=> get_standard_option
('pve-storage-id', {
2415 description
=> "The storage for the VM state",
2416 requires
=> 'todisk',
2418 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2428 my $rpcenv = PVE
::RPCEnvironment
::get
();
2429 my $authuser = $rpcenv->get_user();
2431 my $node = extract_param
($param, 'node');
2432 my $vmid = extract_param
($param, 'vmid');
2434 my $todisk = extract_param
($param, 'todisk') // 0;
2436 my $statestorage = extract_param
($param, 'statestorage');
2438 my $skiplock = extract_param
($param, 'skiplock');
2439 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2440 if $skiplock && $authuser ne 'root@pam';
2442 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2444 die "Cannot suspend HA managed VM to disk\n"
2445 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2447 # early check for storage permission, for better user feedback
2449 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2451 if (!$statestorage) {
2452 # get statestorage from config if none is given
2453 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2454 my $storecfg = PVE
::Storage
::config
();
2455 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2458 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2464 syslog
('info', "suspend VM $vmid: $upid\n");
2466 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2471 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2472 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2475 __PACKAGE__-
>register_method({
2476 name
=> 'vm_resume',
2477 path
=> '{vmid}/status/resume',
2481 description
=> "Resume virtual machine.",
2483 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2486 additionalProperties
=> 0,
2488 node
=> get_standard_option
('pve-node'),
2489 vmid
=> get_standard_option
('pve-vmid',
2490 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2491 skiplock
=> get_standard_option
('skiplock'),
2492 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2502 my $rpcenv = PVE
::RPCEnvironment
::get
();
2504 my $authuser = $rpcenv->get_user();
2506 my $node = extract_param
($param, 'node');
2508 my $vmid = extract_param
($param, 'vmid');
2510 my $skiplock = extract_param
($param, 'skiplock');
2511 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2512 if $skiplock && $authuser ne 'root@pam';
2514 my $nocheck = extract_param
($param, 'nocheck');
2516 my $to_disk_suspended;
2518 PVE
::QemuConfig-
>lock_config($vmid, sub {
2519 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2520 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2524 die "VM $vmid not running\n"
2525 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2530 syslog
('info', "resume VM $vmid: $upid\n");
2532 if (!$to_disk_suspended) {
2533 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2535 my $storecfg = PVE
::Storage
::config
();
2536 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2542 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2545 __PACKAGE__-
>register_method({
2546 name
=> 'vm_sendkey',
2547 path
=> '{vmid}/sendkey',
2551 description
=> "Send key event to virtual machine.",
2553 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2556 additionalProperties
=> 0,
2558 node
=> get_standard_option
('pve-node'),
2559 vmid
=> get_standard_option
('pve-vmid',
2560 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2561 skiplock
=> get_standard_option
('skiplock'),
2563 description
=> "The key (qemu monitor encoding).",
2568 returns
=> { type
=> 'null'},
2572 my $rpcenv = PVE
::RPCEnvironment
::get
();
2574 my $authuser = $rpcenv->get_user();
2576 my $node = extract_param
($param, 'node');
2578 my $vmid = extract_param
($param, 'vmid');
2580 my $skiplock = extract_param
($param, 'skiplock');
2581 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2582 if $skiplock && $authuser ne 'root@pam';
2584 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2589 __PACKAGE__-
>register_method({
2590 name
=> 'vm_feature',
2591 path
=> '{vmid}/feature',
2595 description
=> "Check if feature for virtual machine is available.",
2597 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2600 additionalProperties
=> 0,
2602 node
=> get_standard_option
('pve-node'),
2603 vmid
=> get_standard_option
('pve-vmid'),
2605 description
=> "Feature to check.",
2607 enum
=> [ 'snapshot', 'clone', 'copy' ],
2609 snapname
=> get_standard_option
('pve-snapshot-name', {
2617 hasFeature
=> { type
=> 'boolean' },
2620 items
=> { type
=> 'string' },
2627 my $node = extract_param
($param, 'node');
2629 my $vmid = extract_param
($param, 'vmid');
2631 my $snapname = extract_param
($param, 'snapname');
2633 my $feature = extract_param
($param, 'feature');
2635 my $running = PVE
::QemuServer
::check_running
($vmid);
2637 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2640 my $snap = $conf->{snapshots
}->{$snapname};
2641 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2644 my $storecfg = PVE
::Storage
::config
();
2646 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2647 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2650 hasFeature
=> $hasFeature,
2651 nodes
=> [ keys %$nodelist ],
2655 __PACKAGE__-
>register_method({
2657 path
=> '{vmid}/clone',
2661 description
=> "Create a copy of virtual machine/template.",
2663 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2664 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2665 "'Datastore.AllocateSpace' on any used storage.",
2668 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2670 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2671 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2676 additionalProperties
=> 0,
2678 node
=> get_standard_option
('pve-node'),
2679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2680 newid
=> get_standard_option
('pve-vmid', {
2681 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2682 description
=> 'VMID for the clone.' }),
2685 type
=> 'string', format
=> 'dns-name',
2686 description
=> "Set a name for the new VM.",
2691 description
=> "Description for the new VM.",
2695 type
=> 'string', format
=> 'pve-poolid',
2696 description
=> "Add the new VM to the specified pool.",
2698 snapname
=> get_standard_option
('pve-snapshot-name', {
2701 storage
=> get_standard_option
('pve-storage-id', {
2702 description
=> "Target storage for full clone.",
2706 description
=> "Target format for file storage. Only valid for full clone.",
2709 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2714 description
=> "Create a full copy of all disks. This is always done when " .
2715 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2717 target
=> get_standard_option
('pve-node', {
2718 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2722 description
=> "Override I/O bandwidth limit (in KiB/s).",
2726 default => 'clone limit from datacenter or storage config',
2736 my $rpcenv = PVE
::RPCEnvironment
::get
();
2737 my $authuser = $rpcenv->get_user();
2739 my $node = extract_param
($param, 'node');
2740 my $vmid = extract_param
($param, 'vmid');
2741 my $newid = extract_param
($param, 'newid');
2742 my $pool = extract_param
($param, 'pool');
2743 $rpcenv->check_pool_exist($pool) if defined($pool);
2745 my $snapname = extract_param
($param, 'snapname');
2746 my $storage = extract_param
($param, 'storage');
2747 my $format = extract_param
($param, 'format');
2748 my $target = extract_param
($param, 'target');
2750 my $localnode = PVE
::INotify
::nodename
();
2752 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2755 PVE
::Cluster
::check_node_exists
($target);
2758 my $storecfg = PVE
::Storage
::config
();
2761 # check if storage is enabled on local node
2762 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2764 # check if storage is available on target node
2765 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2766 # clone only works if target storage is shared
2767 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2768 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2772 PVE
::Cluster
::check_cfs_quorum
();
2774 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2776 # exclusive lock if VM is running - else shared lock is enough;
2777 my $shared_lock = $running ?
0 : 1;
2780 # do all tests after lock but before forking worker - if possible
2782 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2783 PVE
::QemuConfig-
>check_lock($conf);
2785 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2786 die "unexpected state change\n" if $verify_running != $running;
2788 die "snapshot '$snapname' does not exist\n"
2789 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2791 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2793 die "parameter 'storage' not allowed for linked clones\n"
2794 if defined($storage) && !$full;
2796 die "parameter 'format' not allowed for linked clones\n"
2797 if defined($format) && !$full;
2799 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2801 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2803 die "can't clone VM to node '$target' (VM uses local storage)\n"
2804 if $target && !$sharedvm;
2806 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2807 die "unable to create VM $newid: config file already exists\n"
2810 my $newconf = { lock => 'clone' };
2815 foreach my $opt (keys %$oldconf) {
2816 my $value = $oldconf->{$opt};
2818 # do not copy snapshot related info
2819 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2820 $opt eq 'vmstate' || $opt eq 'snapstate';
2822 # no need to copy unused images, because VMID(owner) changes anyways
2823 next if $opt =~ m/^unused\d+$/;
2825 # always change MAC! address
2826 if ($opt =~ m/^net(\d+)$/) {
2827 my $net = PVE
::QemuServer
::parse_net
($value);
2828 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2829 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2830 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2831 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2832 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2833 die "unable to parse drive options for '$opt'\n" if !$drive;
2834 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2835 $newconf->{$opt} = $value; # simply copy configuration
2837 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2838 die "Full clone feature is not supported for drive '$opt'\n"
2839 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2840 $fullclone->{$opt} = 1;
2842 # not full means clone instead of copy
2843 die "Linked clone feature is not supported for drive '$opt'\n"
2844 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2846 $drives->{$opt} = $drive;
2847 push @$vollist, $drive->{file
};
2850 # copy everything else
2851 $newconf->{$opt} = $value;
2855 # auto generate a new uuid
2856 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2857 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2858 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2859 # auto generate a new vmgenid only if the option was set for template
2860 if ($newconf->{vmgenid
}) {
2861 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2864 delete $newconf->{template
};
2866 if ($param->{name
}) {
2867 $newconf->{name
} = $param->{name
};
2869 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
2872 if ($param->{description
}) {
2873 $newconf->{description
} = $param->{description
};
2876 # create empty/temp config - this fails if VM already exists on other node
2877 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
2878 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2883 my $newvollist = [];
2890 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2892 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2894 my $bwlimit = extract_param
($param, 'bwlimit');
2896 my $total_jobs = scalar(keys %{$drives});
2899 foreach my $opt (keys %$drives) {
2900 my $drive = $drives->{$opt};
2901 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2903 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2904 my $storage_list = [ $src_sid ];
2905 push @$storage_list, $storage if defined($storage);
2906 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2908 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2909 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2910 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2912 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
2914 PVE
::QemuConfig-
>write_config($newid, $newconf);
2918 delete $newconf->{lock};
2920 # do not write pending changes
2921 if (my @changes = keys %{$newconf->{pending
}}) {
2922 my $pending = join(',', @changes);
2923 warn "found pending changes for '$pending', discarding for clone\n";
2924 delete $newconf->{pending
};
2927 PVE
::QemuConfig-
>write_config($newid, $newconf);
2930 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2931 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2932 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2934 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2935 die "Failed to move config to node '$target' - rename failed: $!\n"
2936 if !rename($conffile, $newconffile);
2939 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2942 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2943 sleep 1; # some storage like rbd need to wait before release volume - really?
2945 foreach my $volid (@$newvollist) {
2946 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2950 PVE
::Firewall
::remove_vmfw_conf
($newid);
2952 unlink $conffile; # avoid races -> last thing before die
2954 die "clone failed: $err";
2960 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2962 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2965 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2966 # Aquire exclusive lock lock for $newid
2967 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2972 __PACKAGE__-
>register_method({
2973 name
=> 'move_vm_disk',
2974 path
=> '{vmid}/move_disk',
2978 description
=> "Move volume to different storage.",
2980 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2982 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2983 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2987 additionalProperties
=> 0,
2989 node
=> get_standard_option
('pve-node'),
2990 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2993 description
=> "The disk you want to move.",
2994 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2996 storage
=> get_standard_option
('pve-storage-id', {
2997 description
=> "Target storage.",
2998 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3002 description
=> "Target Format.",
3003 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3008 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3014 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3019 description
=> "Override I/O bandwidth limit (in KiB/s).",
3023 default => 'move limit from datacenter or storage config',
3029 description
=> "the task ID.",
3034 my $rpcenv = PVE
::RPCEnvironment
::get
();
3035 my $authuser = $rpcenv->get_user();
3037 my $node = extract_param
($param, 'node');
3038 my $vmid = extract_param
($param, 'vmid');
3039 my $digest = extract_param
($param, 'digest');
3040 my $disk = extract_param
($param, 'disk');
3041 my $storeid = extract_param
($param, 'storage');
3042 my $format = extract_param
($param, 'format');
3044 my $storecfg = PVE
::Storage
::config
();
3046 my $updatefn = sub {
3047 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3048 PVE
::QemuConfig-
>check_lock($conf);
3050 die "VM config checksum missmatch (file change by other user?)\n"
3051 if $digest && $digest ne $conf->{digest
};
3053 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3055 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3057 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3058 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3060 my $old_volid = $drive->{file
};
3062 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3063 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3067 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3068 (!$format || !$oldfmt || $oldfmt eq $format);
3070 # this only checks snapshots because $disk is passed!
3071 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3072 die "you can't move a disk with snapshots and delete the source\n"
3073 if $snapshotted && $param->{delete};
3075 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3077 my $running = PVE
::QemuServer
::check_running
($vmid);
3079 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3082 my $newvollist = [];
3088 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3090 warn "moving disk with snapshots, snapshots will not be moved!\n"
3093 my $bwlimit = extract_param
($param, 'bwlimit');
3094 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3096 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3097 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3099 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3101 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3103 # convert moved disk to base if part of template
3104 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3105 if PVE
::QemuConfig-
>is_template($conf);
3107 PVE
::QemuConfig-
>write_config($vmid, $conf);
3109 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3110 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3111 eval { mon_cmd
($vmid, "guest-fstrim") };
3115 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3116 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3122 foreach my $volid (@$newvollist) {
3123 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3126 die "storage migration failed: $err";
3129 if ($param->{delete}) {
3131 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3132 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3138 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3141 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3144 my $check_vm_disks_local = sub {
3145 my ($storecfg, $vmconf, $vmid) = @_;
3147 my $local_disks = {};
3149 # add some more information to the disks e.g. cdrom
3150 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3151 my ($volid, $attr) = @_;
3153 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3155 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3156 return if $scfg->{shared
};
3158 # The shared attr here is just a special case where the vdisk
3159 # is marked as shared manually
3160 return if $attr->{shared
};
3161 return if $attr->{cdrom
} and $volid eq "none";
3163 if (exists $local_disks->{$volid}) {
3164 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3166 $local_disks->{$volid} = $attr;
3167 # ensure volid is present in case it's needed
3168 $local_disks->{$volid}->{volid
} = $volid;
3172 return $local_disks;
3175 __PACKAGE__-
>register_method({
3176 name
=> 'migrate_vm_precondition',
3177 path
=> '{vmid}/migrate',
3181 description
=> "Get preconditions for migration.",
3183 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3186 additionalProperties
=> 0,
3188 node
=> get_standard_option
('pve-node'),
3189 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3190 target
=> get_standard_option
('pve-node', {
3191 description
=> "Target node.",
3192 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3200 running
=> { type
=> 'boolean' },
3204 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3206 not_allowed_nodes
=> {
3209 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3213 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3215 local_resources
=> {
3217 description
=> "List local resources e.g. pci, usb"
3224 my $rpcenv = PVE
::RPCEnvironment
::get
();
3226 my $authuser = $rpcenv->get_user();
3228 PVE
::Cluster
::check_cfs_quorum
();
3232 my $vmid = extract_param
($param, 'vmid');
3233 my $target = extract_param
($param, 'target');
3234 my $localnode = PVE
::INotify
::nodename
();
3238 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3239 my $storecfg = PVE
::Storage
::config
();
3242 # try to detect errors early
3243 PVE
::QemuConfig-
>check_lock($vmconf);
3245 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3247 # if vm is not running, return target nodes where local storage is available
3248 # for offline migration
3249 if (!$res->{running
}) {
3250 $res->{allowed_nodes
} = [];
3251 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3252 delete $checked_nodes->{$localnode};
3254 foreach my $node (keys %$checked_nodes) {
3255 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3256 push @{$res->{allowed_nodes
}}, $node;
3260 $res->{not_allowed_nodes
} = $checked_nodes;
3264 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3265 $res->{local_disks
} = [ values %$local_disks ];;
3267 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3269 $res->{local_resources
} = $local_resources;
3276 __PACKAGE__-
>register_method({
3277 name
=> 'migrate_vm',
3278 path
=> '{vmid}/migrate',
3282 description
=> "Migrate virtual machine. Creates a new migration task.",
3284 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3287 additionalProperties
=> 0,
3289 node
=> get_standard_option
('pve-node'),
3290 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3291 target
=> get_standard_option
('pve-node', {
3292 description
=> "Target node.",
3293 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3297 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3302 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3307 enum
=> ['secure', 'insecure'],
3308 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3311 migration_network
=> {
3312 type
=> 'string', format
=> 'CIDR',
3313 description
=> "CIDR of the (sub) network that is used for migration.",
3316 "with-local-disks" => {
3318 description
=> "Enable live storage migration for local disk",
3321 targetstorage
=> get_standard_option
('pve-storage-id', {
3322 description
=> "Default target storage.",
3324 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3327 description
=> "Override I/O bandwidth limit (in KiB/s).",
3331 default => 'migrate limit from datacenter or storage config',
3337 description
=> "the task ID.",
3342 my $rpcenv = PVE
::RPCEnvironment
::get
();
3343 my $authuser = $rpcenv->get_user();
3345 my $target = extract_param
($param, 'target');
3347 my $localnode = PVE
::INotify
::nodename
();
3348 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3350 PVE
::Cluster
::check_cfs_quorum
();
3352 PVE
::Cluster
::check_node_exists
($target);
3354 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3356 my $vmid = extract_param
($param, 'vmid');
3358 raise_param_exc
({ force
=> "Only root may use this option." })
3359 if $param->{force
} && $authuser ne 'root@pam';
3361 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3362 if $param->{migration_type
} && $authuser ne 'root@pam';
3364 # allow root only until better network permissions are available
3365 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3366 if $param->{migration_network
} && $authuser ne 'root@pam';
3369 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3371 # try to detect errors early
3373 PVE
::QemuConfig-
>check_lock($conf);
3375 if (PVE
::QemuServer
::check_running
($vmid)) {
3376 die "can't migrate running VM without --online\n" if !$param->{online
};
3378 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3379 $param->{online
} = 0;
3382 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3383 if !$param->{online
} && $param->{targetstorage
};
3385 my $storecfg = PVE
::Storage
::config
();
3387 if( $param->{targetstorage
}) {
3388 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3390 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3393 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3398 print "Requesting HA migration for VM $vmid to node $target\n";
3400 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3401 PVE
::Tools
::run_command
($cmd);
3405 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3410 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3414 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3417 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3422 __PACKAGE__-
>register_method({
3424 path
=> '{vmid}/monitor',
3428 description
=> "Execute Qemu monitor commands.",
3430 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3431 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3434 additionalProperties
=> 0,
3436 node
=> get_standard_option
('pve-node'),
3437 vmid
=> get_standard_option
('pve-vmid'),
3440 description
=> "The monitor command.",
3444 returns
=> { type
=> 'string'},
3448 my $rpcenv = PVE
::RPCEnvironment
::get
();
3449 my $authuser = $rpcenv->get_user();
3452 my $command = shift;
3453 return $command =~ m/^\s*info(\s+|$)/
3454 || $command =~ m/^\s*help\s*$/;
3457 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3458 if !&$is_ro($param->{command
});
3460 my $vmid = $param->{vmid
};
3462 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3466 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3468 $res = "ERROR: $@" if $@;
3473 __PACKAGE__-
>register_method({
3474 name
=> 'resize_vm',
3475 path
=> '{vmid}/resize',
3479 description
=> "Extend volume size.",
3481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3484 additionalProperties
=> 0,
3486 node
=> get_standard_option
('pve-node'),
3487 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3488 skiplock
=> get_standard_option
('skiplock'),
3491 description
=> "The disk you want to resize.",
3492 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3496 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3497 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.",
3501 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3507 returns
=> { type
=> 'null'},
3511 my $rpcenv = PVE
::RPCEnvironment
::get
();
3513 my $authuser = $rpcenv->get_user();
3515 my $node = extract_param
($param, 'node');
3517 my $vmid = extract_param
($param, 'vmid');
3519 my $digest = extract_param
($param, 'digest');
3521 my $disk = extract_param
($param, 'disk');
3523 my $sizestr = extract_param
($param, 'size');
3525 my $skiplock = extract_param
($param, 'skiplock');
3526 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3527 if $skiplock && $authuser ne 'root@pam';
3529 my $storecfg = PVE
::Storage
::config
();
3531 my $updatefn = sub {
3533 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3535 die "checksum missmatch (file change by other user?)\n"
3536 if $digest && $digest ne $conf->{digest
};
3537 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3539 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3541 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3543 my (undef, undef, undef, undef, undef, undef, $format) =
3544 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3546 die "can't resize volume: $disk if snapshot exists\n"
3547 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3549 my $volid = $drive->{file
};
3551 die "disk '$disk' has no associated volume\n" if !$volid;
3553 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3555 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3557 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3559 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3560 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3562 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3564 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3565 my ($ext, $newsize, $unit) = ($1, $2, $4);
3568 $newsize = $newsize * 1024;
3569 } elsif ($unit eq 'M') {
3570 $newsize = $newsize * 1024 * 1024;
3571 } elsif ($unit eq 'G') {
3572 $newsize = $newsize * 1024 * 1024 * 1024;
3573 } elsif ($unit eq 'T') {
3574 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3577 $newsize += $size if $ext;
3578 $newsize = int($newsize);
3580 die "shrinking disks is not supported\n" if $newsize < $size;
3582 return if $size == $newsize;
3584 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3586 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3588 $drive->{size
} = $newsize;
3589 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3591 PVE
::QemuConfig-
>write_config($vmid, $conf);
3594 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3598 __PACKAGE__-
>register_method({
3599 name
=> 'snapshot_list',
3600 path
=> '{vmid}/snapshot',
3602 description
=> "List all snapshots.",
3604 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3607 protected
=> 1, # qemu pid files are only readable by root
3609 additionalProperties
=> 0,
3611 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3612 node
=> get_standard_option
('pve-node'),
3621 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3625 description
=> "Snapshot includes RAM.",
3630 description
=> "Snapshot description.",
3634 description
=> "Snapshot creation time",
3636 renderer
=> 'timestamp',
3640 description
=> "Parent snapshot identifier.",
3646 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3651 my $vmid = $param->{vmid
};
3653 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3654 my $snaphash = $conf->{snapshots
} || {};
3658 foreach my $name (keys %$snaphash) {
3659 my $d = $snaphash->{$name};
3662 snaptime
=> $d->{snaptime
} || 0,
3663 vmstate
=> $d->{vmstate
} ?
1 : 0,
3664 description
=> $d->{description
} || '',
3666 $item->{parent
} = $d->{parent
} if $d->{parent
};
3667 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3671 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3674 digest
=> $conf->{digest
},
3675 running
=> $running,
3676 description
=> "You are here!",
3678 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3680 push @$res, $current;
3685 __PACKAGE__-
>register_method({
3687 path
=> '{vmid}/snapshot',
3691 description
=> "Snapshot a VM.",
3693 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3696 additionalProperties
=> 0,
3698 node
=> get_standard_option
('pve-node'),
3699 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3700 snapname
=> get_standard_option
('pve-snapshot-name'),
3704 description
=> "Save the vmstate",
3709 description
=> "A textual description or comment.",
3715 description
=> "the task ID.",
3720 my $rpcenv = PVE
::RPCEnvironment
::get
();
3722 my $authuser = $rpcenv->get_user();
3724 my $node = extract_param
($param, 'node');
3726 my $vmid = extract_param
($param, 'vmid');
3728 my $snapname = extract_param
($param, 'snapname');
3730 die "unable to use snapshot name 'current' (reserved name)\n"
3731 if $snapname eq 'current';
3733 die "unable to use snapshot name 'pending' (reserved name)\n"
3734 if lc($snapname) eq 'pending';
3737 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3738 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3739 $param->{description
});
3742 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3745 __PACKAGE__-
>register_method({
3746 name
=> 'snapshot_cmd_idx',
3747 path
=> '{vmid}/snapshot/{snapname}',
3754 additionalProperties
=> 0,
3756 vmid
=> get_standard_option
('pve-vmid'),
3757 node
=> get_standard_option
('pve-node'),
3758 snapname
=> get_standard_option
('pve-snapshot-name'),
3767 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3774 push @$res, { cmd
=> 'rollback' };
3775 push @$res, { cmd
=> 'config' };
3780 __PACKAGE__-
>register_method({
3781 name
=> 'update_snapshot_config',
3782 path
=> '{vmid}/snapshot/{snapname}/config',
3786 description
=> "Update snapshot metadata.",
3788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3791 additionalProperties
=> 0,
3793 node
=> get_standard_option
('pve-node'),
3794 vmid
=> get_standard_option
('pve-vmid'),
3795 snapname
=> get_standard_option
('pve-snapshot-name'),
3799 description
=> "A textual description or comment.",
3803 returns
=> { type
=> 'null' },
3807 my $rpcenv = PVE
::RPCEnvironment
::get
();
3809 my $authuser = $rpcenv->get_user();
3811 my $vmid = extract_param
($param, 'vmid');
3813 my $snapname = extract_param
($param, 'snapname');
3815 return undef if !defined($param->{description
});
3817 my $updatefn = sub {
3819 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3821 PVE
::QemuConfig-
>check_lock($conf);
3823 my $snap = $conf->{snapshots
}->{$snapname};
3825 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3827 $snap->{description
} = $param->{description
} if defined($param->{description
});
3829 PVE
::QemuConfig-
>write_config($vmid, $conf);
3832 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3837 __PACKAGE__-
>register_method({
3838 name
=> 'get_snapshot_config',
3839 path
=> '{vmid}/snapshot/{snapname}/config',
3842 description
=> "Get snapshot configuration",
3844 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3847 additionalProperties
=> 0,
3849 node
=> get_standard_option
('pve-node'),
3850 vmid
=> get_standard_option
('pve-vmid'),
3851 snapname
=> get_standard_option
('pve-snapshot-name'),
3854 returns
=> { type
=> "object" },
3858 my $rpcenv = PVE
::RPCEnvironment
::get
();
3860 my $authuser = $rpcenv->get_user();
3862 my $vmid = extract_param
($param, 'vmid');
3864 my $snapname = extract_param
($param, 'snapname');
3866 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3868 my $snap = $conf->{snapshots
}->{$snapname};
3870 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3875 __PACKAGE__-
>register_method({
3877 path
=> '{vmid}/snapshot/{snapname}/rollback',
3881 description
=> "Rollback VM state to specified snapshot.",
3883 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3886 additionalProperties
=> 0,
3888 node
=> get_standard_option
('pve-node'),
3889 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3890 snapname
=> get_standard_option
('pve-snapshot-name'),
3895 description
=> "the task ID.",
3900 my $rpcenv = PVE
::RPCEnvironment
::get
();
3902 my $authuser = $rpcenv->get_user();
3904 my $node = extract_param
($param, 'node');
3906 my $vmid = extract_param
($param, 'vmid');
3908 my $snapname = extract_param
($param, 'snapname');
3911 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3912 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3916 # hold migration lock, this makes sure that nobody create replication snapshots
3917 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3920 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3923 __PACKAGE__-
>register_method({
3924 name
=> 'delsnapshot',
3925 path
=> '{vmid}/snapshot/{snapname}',
3929 description
=> "Delete a VM snapshot.",
3931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3934 additionalProperties
=> 0,
3936 node
=> get_standard_option
('pve-node'),
3937 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3938 snapname
=> get_standard_option
('pve-snapshot-name'),
3942 description
=> "For removal from config file, even if removing disk snapshots fails.",
3948 description
=> "the task ID.",
3953 my $rpcenv = PVE
::RPCEnvironment
::get
();
3955 my $authuser = $rpcenv->get_user();
3957 my $node = extract_param
($param, 'node');
3959 my $vmid = extract_param
($param, 'vmid');
3961 my $snapname = extract_param
($param, 'snapname');
3964 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3965 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3968 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3971 __PACKAGE__-
>register_method({
3973 path
=> '{vmid}/template',
3977 description
=> "Create a Template.",
3979 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3980 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3983 additionalProperties
=> 0,
3985 node
=> get_standard_option
('pve-node'),
3986 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3990 description
=> "If you want to convert only 1 disk to base image.",
3991 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3996 returns
=> { type
=> 'null'},
4000 my $rpcenv = PVE
::RPCEnvironment
::get
();
4002 my $authuser = $rpcenv->get_user();
4004 my $node = extract_param
($param, 'node');
4006 my $vmid = extract_param
($param, 'vmid');
4008 my $disk = extract_param
($param, 'disk');
4010 my $updatefn = sub {
4012 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4014 PVE
::QemuConfig-
>check_lock($conf);
4016 die "unable to create template, because VM contains snapshots\n"
4017 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4019 die "you can't convert a template to a template\n"
4020 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4022 die "you can't convert a VM to template if VM is running\n"
4023 if PVE
::QemuServer
::check_running
($vmid);
4026 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4029 $conf->{template
} = 1;
4030 PVE
::QemuConfig-
>write_config($vmid, $conf);
4032 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4035 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4039 __PACKAGE__-
>register_method({
4040 name
=> 'cloudinit_generated_config_dump',
4041 path
=> '{vmid}/cloudinit/dump',
4044 description
=> "Get automatically generated cloudinit config.",
4046 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4049 additionalProperties
=> 0,
4051 node
=> get_standard_option
('pve-node'),
4052 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4054 description
=> 'Config type.',
4056 enum
=> ['user', 'network', 'meta'],
4066 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4068 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});