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