1 package PVE
::API2
::Qemu
;
12 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
;
24 use PVE
::RPCEnvironment
;
25 use PVE
::AccessControl
;
29 use PVE
::API2
::Firewall
::VM
;
30 use PVE
::API2
::Qemu
::Agent
;
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 use Data
::Dumper
; # fixme: remove
43 use base
qw(PVE::RESTHandler);
45 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.";
47 my $resolve_cdrom_alias = sub {
50 if (my $value = $param->{cdrom
}) {
51 $value .= ",media=cdrom" if $value !~ m/media=/;
52 $param->{ide2
} = $value;
53 delete $param->{cdrom
};
57 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
58 my $check_storage_access = sub {
59 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
61 PVE
::QemuServer
::foreach_drive
($settings, sub {
62 my ($ds, $drive) = @_;
64 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
66 my $volid = $drive->{file
};
68 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
70 } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
72 } elsif ($isCDROM && ($volid eq 'cdrom')) {
73 $rpcenv->check($authuser, "/", ['Sys.Console']);
74 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
75 my ($storeid, $size) = ($2 || $default_storage, $3);
76 die "no storage ID specified (and no default storage)\n" if !$storeid;
77 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
78 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
79 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
80 if !$scfg->{content
}->{images
};
82 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
86 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
87 if defined($settings->{vmstatestorage
});
90 my $check_storage_access_clone = sub {
91 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
95 PVE
::QemuServer
::foreach_drive
($conf, sub {
96 my ($ds, $drive) = @_;
98 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
100 my $volid = $drive->{file
};
102 return if !$volid || $volid eq 'none';
105 if ($volid eq 'cdrom') {
106 $rpcenv->check($authuser, "/", ['Sys.Console']);
108 # we simply allow access
109 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
110 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
111 $sharedvm = 0 if !$scfg->{shared
};
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
119 $sid = $storage if $storage;
120 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
124 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
125 if defined($conf->{vmstatestorage
});
130 # Note: $pool is only needed when creating a VM, because pool permissions
131 # are automatically inherited if VM already exists inside a pool.
132 my $create_disks = sub {
133 my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
140 my ($ds, $disk) = @_;
142 my $volid = $disk->{file
};
144 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
145 delete $disk->{size
};
146 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
147 } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
148 my $storeid = $1 || $default_storage;
149 die "no storage ID specified (and no default storage)\n" if !$storeid;
150 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
151 my $name = "vm-$vmid-cloudinit";
159 # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
160 my $cloudinit_iso_size = 5; # in MB
161 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid,
162 $fmt, $name, $cloudinit_iso_size*1024);
163 $disk->{file
} = $volid;
164 $disk->{media
} = 'cdrom';
165 push @$vollist, $volid;
166 delete $disk->{format
}; # no longer needed
167 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
168 } elsif ($volid =~ $NEW_DISK_RE) {
169 my ($storeid, $size) = ($2 || $default_storage, $3);
170 die "no storage ID specified (and no default storage)\n" if !$storeid;
171 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
172 my $fmt = $disk->{format
} || $defformat;
174 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
177 if ($ds eq 'efidisk0') {
178 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt);
180 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
182 push @$vollist, $volid;
183 $disk->{file
} = $volid;
184 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
185 delete $disk->{format
}; # no longer needed
186 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
189 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
191 my $volid_is_new = 1;
194 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
195 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
200 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
202 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
204 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
206 die "volume $volid does not exists\n" if !$size;
208 $disk->{size
} = $size;
211 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
215 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
217 # free allocated images on error
219 syslog
('err', "VM $vmid creating disks failed");
220 foreach my $volid (@$vollist) {
221 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
227 # modify vm config if everything went well
228 foreach my $ds (keys %$res) {
229 $conf->{$ds} = $res->{$ds};
246 my $memoryoptions = {
252 my $hwtypeoptions = {
264 my $generaloptions = {
271 'migrate_downtime' => 1,
272 'migrate_speed' => 1,
284 my $vmpoweroptions = {
291 'vmstatestorage' => 1,
294 my $cloudinitoptions = {
303 my $check_vm_modify_config_perm = sub {
304 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
306 return 1 if $authuser eq 'root@pam';
308 foreach my $opt (@$key_list) {
309 # disk checks need to be done somewhere else
310 next if PVE
::QemuServer
::is_valid_drivename
($opt);
311 next if $opt eq 'cdrom';
312 next if $opt =~ m/^unused\d+$/;
314 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
315 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
316 } elsif ($memoryoptions->{$opt}) {
317 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
318 } elsif ($hwtypeoptions->{$opt}) {
319 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
320 } elsif ($generaloptions->{$opt}) {
321 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
322 # special case for startup since it changes host behaviour
323 if ($opt eq 'startup') {
324 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
326 } elsif ($vmpoweroptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
328 } elsif ($diskoptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
330 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
331 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
333 # catches usb\d+, hostpci\d+, args, lock, etc.
334 # new options will be checked here
335 die "only root can set '$opt' config\n";
342 __PACKAGE__-
>register_method({
346 description
=> "Virtual machine index (per node).",
348 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
352 protected
=> 1, # qemu pid files are only readable by root
354 additionalProperties
=> 0,
356 node
=> get_standard_option
('pve-node'),
360 description
=> "Determine the full status of active VMs.",
370 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
375 my $rpcenv = PVE
::RPCEnvironment
::get
();
376 my $authuser = $rpcenv->get_user();
378 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
381 foreach my $vmid (keys %$vmstatus) {
382 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
384 my $data = $vmstatus->{$vmid};
385 $data->{vmid
} = int($vmid);
394 __PACKAGE__-
>register_method({
398 description
=> "Create or restore a virtual machine.",
400 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
401 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
402 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
403 user
=> 'all', # check inside
408 additionalProperties
=> 0,
409 properties
=> PVE
::QemuServer
::json_config_properties
(
411 node
=> get_standard_option
('pve-node'),
412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
414 description
=> "The backup file.",
418 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
420 storage
=> get_standard_option
('pve-storage-id', {
421 description
=> "Default storage.",
423 completion
=> \
&PVE
::QemuServer
::complete_storage
,
428 description
=> "Allow to overwrite existing VM.",
429 requires
=> 'archive',
434 description
=> "Assign a unique random ethernet address.",
435 requires
=> 'archive',
439 type
=> 'string', format
=> 'pve-poolid',
440 description
=> "Add the VM to the specified pool.",
443 description
=> "Override i/o bandwidth limit (in KiB/s).",
452 description
=> "Start VM after it was created successfully.",
462 my $rpcenv = PVE
::RPCEnvironment
::get
();
464 my $authuser = $rpcenv->get_user();
466 my $node = extract_param
($param, 'node');
468 my $vmid = extract_param
($param, 'vmid');
470 my $archive = extract_param
($param, 'archive');
471 my $is_restore = !!$archive;
473 my $storage = extract_param
($param, 'storage');
475 my $force = extract_param
($param, 'force');
477 my $unique = extract_param
($param, 'unique');
479 my $pool = extract_param
($param, 'pool');
481 my $bwlimit = extract_param
($param, 'bwlimit');
483 my $start_after_create = extract_param
($param, 'start');
485 my $filename = PVE
::QemuConfig-
>config_file($vmid);
487 my $storecfg = PVE
::Storage
::config
();
489 if (defined(my $ssh_keys = $param->{sshkeys
})) {
490 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
491 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
494 PVE
::Cluster
::check_cfs_quorum
();
496 if (defined($pool)) {
497 $rpcenv->check_pool_exist($pool);
500 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
501 if defined($storage);
503 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
505 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
507 } elsif ($archive && $force && (-f
$filename) &&
508 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
509 # OK: user has VM.Backup permissions, and want to restore an existing VM
515 &$resolve_cdrom_alias($param);
517 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
519 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
521 foreach my $opt (keys %$param) {
522 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
523 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
524 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
526 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
527 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
531 PVE
::QemuServer
::add_random_macs
($param);
533 my $keystr = join(' ', keys %$param);
534 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
536 if ($archive eq '-') {
537 die "pipe requires cli environment\n"
538 if $rpcenv->{type
} ne 'cli';
540 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
541 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
545 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
547 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
548 die "$emsg $@" if $@;
550 my $restorefn = sub {
551 my $conf = PVE
::QemuConfig-
>load_config($vmid);
553 PVE
::QemuConfig-
>check_protection($conf, $emsg);
555 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
556 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
559 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
563 bwlimit
=> $bwlimit, });
565 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
567 if ($start_after_create) {
568 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
573 # ensure no old replication state are exists
574 PVE
::ReplicationState
::delete_guest_states
($vmid);
576 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
580 # ensure no old replication state are exists
581 PVE
::ReplicationState
::delete_guest_states
($vmid);
591 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
593 if (!$conf->{bootdisk
}) {
594 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
595 $conf->{bootdisk
} = $firstdisk if $firstdisk;
598 # auto generate uuid if user did not specify smbios1 option
599 if (!$conf->{smbios1
}) {
600 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
603 PVE
::QemuConfig-
>write_config($vmid, $conf);
609 foreach my $volid (@$vollist) {
610 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
616 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
619 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
621 if ($start_after_create) {
622 print "Execute autostart\n";
623 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
628 my ($code, $worker_name);
630 $worker_name = 'qmrestore';
632 eval { $restorefn->() };
634 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
640 $worker_name = 'qmcreate';
642 eval { $createfn->() };
645 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
647 or die "failed to remove config file: $@\n";
655 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
658 __PACKAGE__-
>register_method({
663 description
=> "Directory index",
668 additionalProperties
=> 0,
670 node
=> get_standard_option
('pve-node'),
671 vmid
=> get_standard_option
('pve-vmid'),
679 subdir
=> { type
=> 'string' },
682 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
688 { subdir
=> 'config' },
689 { subdir
=> 'pending' },
690 { subdir
=> 'status' },
691 { subdir
=> 'unlink' },
692 { subdir
=> 'vncproxy' },
693 { subdir
=> 'termproxy' },
694 { subdir
=> 'migrate' },
695 { subdir
=> 'resize' },
696 { subdir
=> 'move' },
698 { subdir
=> 'rrddata' },
699 { subdir
=> 'monitor' },
700 { subdir
=> 'agent' },
701 { subdir
=> 'snapshot' },
702 { subdir
=> 'spiceproxy' },
703 { subdir
=> 'sendkey' },
704 { subdir
=> 'firewall' },
710 __PACKAGE__-
>register_method ({
711 subclass
=> "PVE::API2::Firewall::VM",
712 path
=> '{vmid}/firewall',
715 __PACKAGE__-
>register_method ({
716 subclass
=> "PVE::API2::Qemu::Agent",
717 path
=> '{vmid}/agent',
720 __PACKAGE__-
>register_method({
722 path
=> '{vmid}/rrd',
724 protected
=> 1, # fixme: can we avoid that?
726 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
728 description
=> "Read VM RRD statistics (returns PNG)",
730 additionalProperties
=> 0,
732 node
=> get_standard_option
('pve-node'),
733 vmid
=> get_standard_option
('pve-vmid'),
735 description
=> "Specify the time frame you are interested in.",
737 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
740 description
=> "The list of datasources you want to display.",
741 type
=> 'string', format
=> 'pve-configid-list',
744 description
=> "The RRD consolidation function",
746 enum
=> [ 'AVERAGE', 'MAX' ],
754 filename
=> { type
=> 'string' },
760 return PVE
::Cluster
::create_rrd_graph
(
761 "pve2-vm/$param->{vmid}", $param->{timeframe
},
762 $param->{ds
}, $param->{cf
});
766 __PACKAGE__-
>register_method({
768 path
=> '{vmid}/rrddata',
770 protected
=> 1, # fixme: can we avoid that?
772 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
774 description
=> "Read VM RRD statistics",
776 additionalProperties
=> 0,
778 node
=> get_standard_option
('pve-node'),
779 vmid
=> get_standard_option
('pve-vmid'),
781 description
=> "Specify the time frame you are interested in.",
783 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
786 description
=> "The RRD consolidation function",
788 enum
=> [ 'AVERAGE', 'MAX' ],
803 return PVE
::Cluster
::create_rrd_data
(
804 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
808 __PACKAGE__-
>register_method({
810 path
=> '{vmid}/config',
813 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
818 additionalProperties
=> 0,
820 node
=> get_standard_option
('pve-node'),
821 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
823 description
=> "Get current values (instead of pending values).",
835 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
842 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
844 delete $conf->{snapshots
};
846 if (!$param->{current
}) {
847 foreach my $opt (keys %{$conf->{pending
}}) {
848 next if $opt eq 'delete';
849 my $value = $conf->{pending
}->{$opt};
850 next if ref($value); # just to be sure
851 $conf->{$opt} = $value;
853 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
854 foreach my $opt (keys %$pending_delete_hash) {
855 delete $conf->{$opt} if $conf->{$opt};
859 delete $conf->{pending
};
861 # hide cloudinit password
862 if ($conf->{cipassword
}) {
863 $conf->{cipassword
} = '**********';
869 __PACKAGE__-
>register_method({
870 name
=> 'vm_pending',
871 path
=> '{vmid}/pending',
874 description
=> "Get virtual machine configuration, including pending changes.",
876 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
879 additionalProperties
=> 0,
881 node
=> get_standard_option
('pve-node'),
882 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
891 description
=> "Configuration option name.",
895 description
=> "Current value.",
900 description
=> "Pending value.",
905 description
=> "Indicates a pending delete request if present and not 0. " .
906 "The value 2 indicates a force-delete request.",
918 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
920 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
924 foreach my $opt (keys %$conf) {
925 next if ref($conf->{$opt});
926 my $item = { key
=> $opt };
927 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
928 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
929 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
931 # hide cloudinit password
932 if ($opt eq 'cipassword') {
933 $item->{value
} = '**********' if defined($item->{value
});
934 # the trailing space so that the pending string is different
935 $item->{pending
} = '********** ' if defined($item->{pending
});
940 foreach my $opt (keys %{$conf->{pending
}}) {
941 next if $opt eq 'delete';
942 next if ref($conf->{pending
}->{$opt}); # just to be sure
943 next if defined($conf->{$opt});
944 my $item = { key
=> $opt };
945 $item->{pending
} = $conf->{pending
}->{$opt};
947 # hide cloudinit password
948 if ($opt eq 'cipassword') {
949 $item->{pending
} = '**********' if defined($item->{pending
});
954 while (my ($opt, $force) = each %$pending_delete_hash) {
955 next if $conf->{pending
}->{$opt}; # just to be sure
956 next if $conf->{$opt};
957 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
964 # POST/PUT {vmid}/config implementation
966 # The original API used PUT (idempotent) an we assumed that all operations
967 # are fast. But it turned out that almost any configuration change can
968 # involve hot-plug actions, or disk alloc/free. Such actions can take long
969 # time to complete and have side effects (not idempotent).
971 # The new implementation uses POST and forks a worker process. We added
972 # a new option 'background_delay'. If specified we wait up to
973 # 'background_delay' second for the worker task to complete. It returns null
974 # if the task is finished within that time, else we return the UPID.
976 my $update_vm_api = sub {
977 my ($param, $sync) = @_;
979 my $rpcenv = PVE
::RPCEnvironment
::get
();
981 my $authuser = $rpcenv->get_user();
983 my $node = extract_param
($param, 'node');
985 my $vmid = extract_param
($param, 'vmid');
987 my $digest = extract_param
($param, 'digest');
989 my $background_delay = extract_param
($param, 'background_delay');
991 if (defined(my $cipassword = $param->{cipassword
})) {
992 # Same logic as in cloud-init (but with the regex fixed...)
993 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
994 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
997 my @paramarr = (); # used for log message
998 foreach my $key (sort keys %$param) {
999 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1000 push @paramarr, "-$key", $value;
1003 my $skiplock = extract_param
($param, 'skiplock');
1004 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1005 if $skiplock && $authuser ne 'root@pam';
1007 my $delete_str = extract_param
($param, 'delete');
1009 my $revert_str = extract_param
($param, 'revert');
1011 my $force = extract_param
($param, 'force');
1013 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1014 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1015 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1018 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1020 my $storecfg = PVE
::Storage
::config
();
1022 my $defaults = PVE
::QemuServer
::load_defaults
();
1024 &$resolve_cdrom_alias($param);
1026 # now try to verify all parameters
1029 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1030 if (!PVE
::QemuServer
::option_exists
($opt)) {
1031 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1034 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1035 "-revert $opt' at the same time" })
1036 if defined($param->{$opt});
1038 $revert->{$opt} = 1;
1042 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1043 $opt = 'ide2' if $opt eq 'cdrom';
1045 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1046 "-delete $opt' at the same time" })
1047 if defined($param->{$opt});
1049 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1050 "-revert $opt' at the same time" })
1053 if (!PVE
::QemuServer
::option_exists
($opt)) {
1054 raise_param_exc
({ delete => "unknown option '$opt'" });
1060 my $repl_conf = PVE
::ReplicationConfig-
>new();
1061 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1062 my $check_replication = sub {
1064 return if !$is_replicated;
1065 my $volid = $drive->{file
};
1066 return if !$volid || !($drive->{replicate
}//1);
1067 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1068 my ($storeid, $format);
1069 if ($volid =~ $NEW_DISK_RE) {
1071 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1073 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1074 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1076 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1077 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1078 return if $scfg->{shared
};
1079 die "cannot add non-replicatable volume to a replicated VM\n";
1082 foreach my $opt (keys %$param) {
1083 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1084 # cleanup drive path
1085 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1086 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1087 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1088 $check_replication->($drive);
1089 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1090 } elsif ($opt =~ m/^net(\d+)$/) {
1092 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1093 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1097 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1099 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1101 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1103 my $updatefn = sub {
1105 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1107 die "checksum missmatch (file change by other user?)\n"
1108 if $digest && $digest ne $conf->{digest
};
1110 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1112 foreach my $opt (keys %$revert) {
1113 if (defined($conf->{$opt})) {
1114 $param->{$opt} = $conf->{$opt};
1115 } elsif (defined($conf->{pending
}->{$opt})) {
1120 if ($param->{memory
} || defined($param->{balloon
})) {
1121 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1122 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1124 die "balloon value too large (must be smaller than assigned memory)\n"
1125 if $balloon && $balloon > $maxmem;
1128 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1132 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1134 # write updates to pending section
1136 my $modified = {}; # record what $option we modify
1138 foreach my $opt (@delete) {
1139 $modified->{$opt} = 1;
1140 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1141 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1142 warn "cannot delete '$opt' - not set in current configuration!\n";
1143 $modified->{$opt} = 0;
1147 if ($opt =~ m/^unused/) {
1148 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1149 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1150 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1151 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1152 delete $conf->{$opt};
1153 PVE
::QemuConfig-
>write_config($vmid, $conf);
1155 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1156 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1157 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1158 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1159 if defined($conf->{pending
}->{$opt});
1160 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1164 PVE
::QemuConfig-
>write_config($vmid, $conf);
1168 foreach my $opt (keys %$param) { # add/change
1169 $modified->{$opt} = 1;
1170 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1171 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1173 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1174 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1175 # FIXME: cloudinit: CDROM or Disk?
1176 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1177 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1179 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1181 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1182 if defined($conf->{pending
}->{$opt});
1184 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1186 $conf->{pending
}->{$opt} = $param->{$opt};
1188 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1189 PVE
::QemuConfig-
>write_config($vmid, $conf);
1192 # remove pending changes when nothing changed
1193 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1194 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1195 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1197 return if !scalar(keys %{$conf->{pending
}});
1199 my $running = PVE
::QemuServer
::check_running
($vmid);
1201 # apply pending changes
1203 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1207 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1208 raise_param_exc
($errors) if scalar(keys %$errors);
1210 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1220 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1222 if ($background_delay) {
1224 # Note: It would be better to do that in the Event based HTTPServer
1225 # to avoid blocking call to sleep.
1227 my $end_time = time() + $background_delay;
1229 my $task = PVE
::Tools
::upid_decode
($upid);
1232 while (time() < $end_time) {
1233 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1235 sleep(1); # this gets interrupted when child process ends
1239 my $status = PVE
::Tools
::upid_read_status
($upid);
1240 return undef if $status eq 'OK';
1249 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1252 my $vm_config_perm_list = [
1257 'VM.Config.Network',
1259 'VM.Config.Options',
1262 __PACKAGE__-
>register_method({
1263 name
=> 'update_vm_async',
1264 path
=> '{vmid}/config',
1268 description
=> "Set virtual machine options (asynchrounous API).",
1270 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1273 additionalProperties
=> 0,
1274 properties
=> PVE
::QemuServer
::json_config_properties
(
1276 node
=> get_standard_option
('pve-node'),
1277 vmid
=> get_standard_option
('pve-vmid'),
1278 skiplock
=> get_standard_option
('skiplock'),
1280 type
=> 'string', format
=> 'pve-configid-list',
1281 description
=> "A list of settings you want to delete.",
1285 type
=> 'string', format
=> 'pve-configid-list',
1286 description
=> "Revert a pending change.",
1291 description
=> $opt_force_description,
1293 requires
=> 'delete',
1297 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1301 background_delay
=> {
1303 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1314 code
=> $update_vm_api,
1317 __PACKAGE__-
>register_method({
1318 name
=> 'update_vm',
1319 path
=> '{vmid}/config',
1323 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1325 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1328 additionalProperties
=> 0,
1329 properties
=> PVE
::QemuServer
::json_config_properties
(
1331 node
=> get_standard_option
('pve-node'),
1332 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1333 skiplock
=> get_standard_option
('skiplock'),
1335 type
=> 'string', format
=> 'pve-configid-list',
1336 description
=> "A list of settings you want to delete.",
1340 type
=> 'string', format
=> 'pve-configid-list',
1341 description
=> "Revert a pending change.",
1346 description
=> $opt_force_description,
1348 requires
=> 'delete',
1352 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1358 returns
=> { type
=> 'null' },
1361 &$update_vm_api($param, 1);
1367 __PACKAGE__-
>register_method({
1368 name
=> 'destroy_vm',
1373 description
=> "Destroy the vm (also delete all used/owned volumes).",
1375 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1378 additionalProperties
=> 0,
1380 node
=> get_standard_option
('pve-node'),
1381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1382 skiplock
=> get_standard_option
('skiplock'),
1391 my $rpcenv = PVE
::RPCEnvironment
::get
();
1393 my $authuser = $rpcenv->get_user();
1395 my $vmid = $param->{vmid
};
1397 my $skiplock = $param->{skiplock
};
1398 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1399 if $skiplock && $authuser ne 'root@pam';
1402 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1404 my $storecfg = PVE
::Storage
::config
();
1406 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1408 die "unable to remove VM $vmid - used in HA resources\n"
1409 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1411 # do not allow destroy if there are replication jobs
1412 my $repl_conf = PVE
::ReplicationConfig-
>new();
1413 $repl_conf->check_for_existing_jobs($vmid);
1415 # early tests (repeat after locking)
1416 die "VM $vmid is running - destroy failed\n"
1417 if PVE
::QemuServer
::check_running
($vmid);
1422 syslog
('info', "destroy VM $vmid: $upid\n");
1424 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1426 PVE
::AccessControl
::remove_vm_access
($vmid);
1428 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1431 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1434 __PACKAGE__-
>register_method({
1436 path
=> '{vmid}/unlink',
1440 description
=> "Unlink/delete disk images.",
1442 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1445 additionalProperties
=> 0,
1447 node
=> get_standard_option
('pve-node'),
1448 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1450 type
=> 'string', format
=> 'pve-configid-list',
1451 description
=> "A list of disk IDs you want to delete.",
1455 description
=> $opt_force_description,
1460 returns
=> { type
=> 'null'},
1464 $param->{delete} = extract_param
($param, 'idlist');
1466 __PACKAGE__-
>update_vm($param);
1473 __PACKAGE__-
>register_method({
1475 path
=> '{vmid}/vncproxy',
1479 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1481 description
=> "Creates a TCP VNC proxy connections.",
1483 additionalProperties
=> 0,
1485 node
=> get_standard_option
('pve-node'),
1486 vmid
=> get_standard_option
('pve-vmid'),
1490 description
=> "starts websockify instead of vncproxy",
1495 additionalProperties
=> 0,
1497 user
=> { type
=> 'string' },
1498 ticket
=> { type
=> 'string' },
1499 cert
=> { type
=> 'string' },
1500 port
=> { type
=> 'integer' },
1501 upid
=> { type
=> 'string' },
1507 my $rpcenv = PVE
::RPCEnvironment
::get
();
1509 my $authuser = $rpcenv->get_user();
1511 my $vmid = $param->{vmid
};
1512 my $node = $param->{node
};
1513 my $websocket = $param->{websocket
};
1515 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1517 my $authpath = "/vms/$vmid";
1519 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1521 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1524 my ($remip, $family);
1527 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1528 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1529 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1530 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1532 $family = PVE
::Tools
::get_host_address_family
($node);
1535 my $port = PVE
::Tools
::next_vnc_port
($family);
1542 syslog
('info', "starting vnc proxy $upid\n");
1546 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1549 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1551 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1552 '-timeout', $timeout, '-authpath', $authpath,
1553 '-perm', 'Sys.Console'];
1555 if ($param->{websocket
}) {
1556 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1557 push @$cmd, '-notls', '-listen', 'localhost';
1560 push @$cmd, '-c', @$remcmd, @$termcmd;
1562 PVE
::Tools
::run_command
($cmd);
1566 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1568 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1570 my $sock = IO
::Socket
::IP-
>new(
1575 GetAddrInfoFlags
=> 0,
1576 ) or die "failed to create socket: $!\n";
1577 # Inside the worker we shouldn't have any previous alarms
1578 # running anyway...:
1580 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1582 accept(my $cli, $sock) or die "connection failed: $!\n";
1585 if (PVE
::Tools
::run_command
($cmd,
1586 output
=> '>&'.fileno($cli),
1587 input
=> '<&'.fileno($cli),
1590 die "Failed to run vncproxy.\n";
1597 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1599 PVE
::Tools
::wait_for_vnc_port
($port);
1610 __PACKAGE__-
>register_method({
1611 name
=> 'termproxy',
1612 path
=> '{vmid}/termproxy',
1616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1618 description
=> "Creates a TCP proxy connections.",
1620 additionalProperties
=> 0,
1622 node
=> get_standard_option
('pve-node'),
1623 vmid
=> get_standard_option
('pve-vmid'),
1627 enum
=> [qw(serial0 serial1 serial2 serial3)],
1628 description
=> "opens a serial terminal (defaults to display)",
1633 additionalProperties
=> 0,
1635 user
=> { type
=> 'string' },
1636 ticket
=> { type
=> 'string' },
1637 port
=> { type
=> 'integer' },
1638 upid
=> { type
=> 'string' },
1644 my $rpcenv = PVE
::RPCEnvironment
::get
();
1646 my $authuser = $rpcenv->get_user();
1648 my $vmid = $param->{vmid
};
1649 my $node = $param->{node
};
1650 my $serial = $param->{serial
};
1652 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1654 if (!defined($serial)) {
1655 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1656 $serial = $conf->{vga
};
1660 my $authpath = "/vms/$vmid";
1662 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1664 my ($remip, $family);
1666 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1667 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1669 $family = PVE
::Tools
::get_host_address_family
($node);
1672 my $port = PVE
::Tools
::next_vnc_port
($family);
1674 my $remcmd = $remip ?
1675 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1677 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1678 push @$termcmd, '-iface', $serial if $serial;
1683 syslog
('info', "starting qemu termproxy $upid\n");
1685 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1686 '--perm', 'VM.Console', '--'];
1687 push @$cmd, @$remcmd, @$termcmd;
1689 PVE
::Tools
::run_command
($cmd);
1692 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1694 PVE
::Tools
::wait_for_vnc_port
($port);
1704 __PACKAGE__-
>register_method({
1705 name
=> 'vncwebsocket',
1706 path
=> '{vmid}/vncwebsocket',
1709 description
=> "You also need to pass a valid ticket (vncticket).",
1710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1712 description
=> "Opens a weksocket for VNC traffic.",
1714 additionalProperties
=> 0,
1716 node
=> get_standard_option
('pve-node'),
1717 vmid
=> get_standard_option
('pve-vmid'),
1719 description
=> "Ticket from previous call to vncproxy.",
1724 description
=> "Port number returned by previous vncproxy call.",
1734 port
=> { type
=> 'string' },
1740 my $rpcenv = PVE
::RPCEnvironment
::get
();
1742 my $authuser = $rpcenv->get_user();
1744 my $vmid = $param->{vmid
};
1745 my $node = $param->{node
};
1747 my $authpath = "/vms/$vmid";
1749 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1751 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1753 # Note: VNC ports are acessible from outside, so we do not gain any
1754 # security if we verify that $param->{port} belongs to VM $vmid. This
1755 # check is done by verifying the VNC ticket (inside VNC protocol).
1757 my $port = $param->{port
};
1759 return { port
=> $port };
1762 __PACKAGE__-
>register_method({
1763 name
=> 'spiceproxy',
1764 path
=> '{vmid}/spiceproxy',
1769 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1771 description
=> "Returns a SPICE configuration to connect to the VM.",
1773 additionalProperties
=> 0,
1775 node
=> get_standard_option
('pve-node'),
1776 vmid
=> get_standard_option
('pve-vmid'),
1777 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1780 returns
=> get_standard_option
('remote-viewer-config'),
1784 my $rpcenv = PVE
::RPCEnvironment
::get
();
1786 my $authuser = $rpcenv->get_user();
1788 my $vmid = $param->{vmid
};
1789 my $node = $param->{node
};
1790 my $proxy = $param->{proxy
};
1792 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1793 my $title = "VM $vmid";
1794 $title .= " - ". $conf->{name
} if $conf->{name
};
1796 my $port = PVE
::QemuServer
::spice_port
($vmid);
1798 my ($ticket, undef, $remote_viewer_config) =
1799 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1801 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1802 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1804 return $remote_viewer_config;
1807 __PACKAGE__-
>register_method({
1809 path
=> '{vmid}/status',
1812 description
=> "Directory index",
1817 additionalProperties
=> 0,
1819 node
=> get_standard_option
('pve-node'),
1820 vmid
=> get_standard_option
('pve-vmid'),
1828 subdir
=> { type
=> 'string' },
1831 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1837 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1840 { subdir
=> 'current' },
1841 { subdir
=> 'start' },
1842 { subdir
=> 'stop' },
1848 __PACKAGE__-
>register_method({
1849 name
=> 'vm_status',
1850 path
=> '{vmid}/status/current',
1853 protected
=> 1, # qemu pid files are only readable by root
1854 description
=> "Get virtual machine status.",
1856 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1859 additionalProperties
=> 0,
1861 node
=> get_standard_option
('pve-node'),
1862 vmid
=> get_standard_option
('pve-vmid'),
1865 returns
=> { type
=> 'object' },
1870 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1872 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1873 my $status = $vmstatus->{$param->{vmid
}};
1875 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1877 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1879 $status->{agent
} = 1 if $conf->{agent
};
1884 __PACKAGE__-
>register_method({
1886 path
=> '{vmid}/status/start',
1890 description
=> "Start virtual machine.",
1892 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1895 additionalProperties
=> 0,
1897 node
=> get_standard_option
('pve-node'),
1898 vmid
=> get_standard_option
('pve-vmid',
1899 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1900 skiplock
=> get_standard_option
('skiplock'),
1901 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1902 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1905 enum
=> ['secure', 'insecure'],
1906 description
=> "Migration traffic is encrypted using an SSH " .
1907 "tunnel by default. On secure, completely private networks " .
1908 "this can be disabled to increase performance.",
1911 migration_network
=> {
1912 type
=> 'string', format
=> 'CIDR',
1913 description
=> "CIDR of the (sub) network that is used for migration.",
1916 machine
=> get_standard_option
('pve-qm-machine'),
1918 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1930 my $rpcenv = PVE
::RPCEnvironment
::get
();
1932 my $authuser = $rpcenv->get_user();
1934 my $node = extract_param
($param, 'node');
1936 my $vmid = extract_param
($param, 'vmid');
1938 my $machine = extract_param
($param, 'machine');
1940 my $stateuri = extract_param
($param, 'stateuri');
1941 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1942 if $stateuri && $authuser ne 'root@pam';
1944 my $skiplock = extract_param
($param, 'skiplock');
1945 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1946 if $skiplock && $authuser ne 'root@pam';
1948 my $migratedfrom = extract_param
($param, 'migratedfrom');
1949 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1950 if $migratedfrom && $authuser ne 'root@pam';
1952 my $migration_type = extract_param
($param, 'migration_type');
1953 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1954 if $migration_type && $authuser ne 'root@pam';
1956 my $migration_network = extract_param
($param, 'migration_network');
1957 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1958 if $migration_network && $authuser ne 'root@pam';
1960 my $targetstorage = extract_param
($param, 'targetstorage');
1961 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1962 if $targetstorage && $authuser ne 'root@pam';
1964 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1965 if $targetstorage && !$migratedfrom;
1967 # read spice ticket from STDIN
1969 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1970 if (defined(my $line = <STDIN
>)) {
1972 $spice_ticket = $line;
1976 PVE
::Cluster
::check_cfs_quorum
();
1978 my $storecfg = PVE
::Storage
::config
();
1980 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
1981 $rpcenv->{type
} ne 'ha') {
1986 my $service = "vm:$vmid";
1988 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
1990 print "Requesting HA start for VM $vmid\n";
1992 PVE
::Tools
::run_command
($cmd);
1997 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2004 syslog
('info', "start VM $vmid: $upid\n");
2006 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2007 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2012 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2016 __PACKAGE__-
>register_method({
2018 path
=> '{vmid}/status/stop',
2022 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2023 "is akin to pulling the power plug of a running computer and may damage the VM data",
2025 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2028 additionalProperties
=> 0,
2030 node
=> get_standard_option
('pve-node'),
2031 vmid
=> get_standard_option
('pve-vmid',
2032 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2033 skiplock
=> get_standard_option
('skiplock'),
2034 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2036 description
=> "Wait maximal timeout seconds.",
2042 description
=> "Do not deactivate storage volumes.",
2055 my $rpcenv = PVE
::RPCEnvironment
::get
();
2057 my $authuser = $rpcenv->get_user();
2059 my $node = extract_param
($param, 'node');
2061 my $vmid = extract_param
($param, 'vmid');
2063 my $skiplock = extract_param
($param, 'skiplock');
2064 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2065 if $skiplock && $authuser ne 'root@pam';
2067 my $keepActive = extract_param
($param, 'keepActive');
2068 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2069 if $keepActive && $authuser ne 'root@pam';
2071 my $migratedfrom = extract_param
($param, 'migratedfrom');
2072 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2073 if $migratedfrom && $authuser ne 'root@pam';
2076 my $storecfg = PVE
::Storage
::config
();
2078 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2083 my $service = "vm:$vmid";
2085 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2087 print "Requesting HA stop for VM $vmid\n";
2089 PVE
::Tools
::run_command
($cmd);
2094 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2100 syslog
('info', "stop VM $vmid: $upid\n");
2102 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2103 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2108 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2112 __PACKAGE__-
>register_method({
2114 path
=> '{vmid}/status/reset',
2118 description
=> "Reset virtual machine.",
2120 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2123 additionalProperties
=> 0,
2125 node
=> get_standard_option
('pve-node'),
2126 vmid
=> get_standard_option
('pve-vmid',
2127 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2128 skiplock
=> get_standard_option
('skiplock'),
2137 my $rpcenv = PVE
::RPCEnvironment
::get
();
2139 my $authuser = $rpcenv->get_user();
2141 my $node = extract_param
($param, 'node');
2143 my $vmid = extract_param
($param, 'vmid');
2145 my $skiplock = extract_param
($param, 'skiplock');
2146 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2147 if $skiplock && $authuser ne 'root@pam';
2149 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2154 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2159 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2162 __PACKAGE__-
>register_method({
2163 name
=> 'vm_shutdown',
2164 path
=> '{vmid}/status/shutdown',
2168 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2169 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2171 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2174 additionalProperties
=> 0,
2176 node
=> get_standard_option
('pve-node'),
2177 vmid
=> get_standard_option
('pve-vmid',
2178 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2179 skiplock
=> get_standard_option
('skiplock'),
2181 description
=> "Wait maximal timeout seconds.",
2187 description
=> "Make sure the VM stops.",
2193 description
=> "Do not deactivate storage volumes.",
2206 my $rpcenv = PVE
::RPCEnvironment
::get
();
2208 my $authuser = $rpcenv->get_user();
2210 my $node = extract_param
($param, 'node');
2212 my $vmid = extract_param
($param, 'vmid');
2214 my $skiplock = extract_param
($param, 'skiplock');
2215 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2216 if $skiplock && $authuser ne 'root@pam';
2218 my $keepActive = extract_param
($param, 'keepActive');
2219 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2220 if $keepActive && $authuser ne 'root@pam';
2222 my $storecfg = PVE
::Storage
::config
();
2226 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2227 # otherwise, we will infer a shutdown command, but run into the timeout,
2228 # then when the vm is resumed, it will instantly shutdown
2230 # checking the qmp status here to get feedback to the gui/cli/api
2231 # and the status query should not take too long
2234 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2238 if (!$err && $qmpstatus->{status
} eq "paused") {
2239 if ($param->{forceStop
}) {
2240 warn "VM is paused - stop instead of shutdown\n";
2243 die "VM is paused - cannot shutdown\n";
2247 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2248 ($rpcenv->{type
} ne 'ha')) {
2253 my $service = "vm:$vmid";
2255 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2257 print "Requesting HA stop for VM $vmid\n";
2259 PVE
::Tools
::run_command
($cmd);
2264 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2271 syslog
('info', "shutdown VM $vmid: $upid\n");
2273 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2274 $shutdown, $param->{forceStop
}, $keepActive);
2279 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2283 __PACKAGE__-
>register_method({
2284 name
=> 'vm_suspend',
2285 path
=> '{vmid}/status/suspend',
2289 description
=> "Suspend virtual machine.",
2291 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2294 additionalProperties
=> 0,
2296 node
=> get_standard_option
('pve-node'),
2297 vmid
=> get_standard_option
('pve-vmid',
2298 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2299 skiplock
=> get_standard_option
('skiplock'),
2308 my $rpcenv = PVE
::RPCEnvironment
::get
();
2310 my $authuser = $rpcenv->get_user();
2312 my $node = extract_param
($param, 'node');
2314 my $vmid = extract_param
($param, 'vmid');
2316 my $skiplock = extract_param
($param, 'skiplock');
2317 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2318 if $skiplock && $authuser ne 'root@pam';
2320 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2325 syslog
('info', "suspend VM $vmid: $upid\n");
2327 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2332 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2335 __PACKAGE__-
>register_method({
2336 name
=> 'vm_resume',
2337 path
=> '{vmid}/status/resume',
2341 description
=> "Resume virtual machine.",
2343 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2346 additionalProperties
=> 0,
2348 node
=> get_standard_option
('pve-node'),
2349 vmid
=> get_standard_option
('pve-vmid',
2350 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2351 skiplock
=> get_standard_option
('skiplock'),
2352 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2362 my $rpcenv = PVE
::RPCEnvironment
::get
();
2364 my $authuser = $rpcenv->get_user();
2366 my $node = extract_param
($param, 'node');
2368 my $vmid = extract_param
($param, 'vmid');
2370 my $skiplock = extract_param
($param, 'skiplock');
2371 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2372 if $skiplock && $authuser ne 'root@pam';
2374 my $nocheck = extract_param
($param, 'nocheck');
2376 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2381 syslog
('info', "resume VM $vmid: $upid\n");
2383 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2388 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2391 __PACKAGE__-
>register_method({
2392 name
=> 'vm_sendkey',
2393 path
=> '{vmid}/sendkey',
2397 description
=> "Send key event to virtual machine.",
2399 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2402 additionalProperties
=> 0,
2404 node
=> get_standard_option
('pve-node'),
2405 vmid
=> get_standard_option
('pve-vmid',
2406 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2407 skiplock
=> get_standard_option
('skiplock'),
2409 description
=> "The key (qemu monitor encoding).",
2414 returns
=> { type
=> 'null'},
2418 my $rpcenv = PVE
::RPCEnvironment
::get
();
2420 my $authuser = $rpcenv->get_user();
2422 my $node = extract_param
($param, 'node');
2424 my $vmid = extract_param
($param, 'vmid');
2426 my $skiplock = extract_param
($param, 'skiplock');
2427 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2428 if $skiplock && $authuser ne 'root@pam';
2430 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2435 __PACKAGE__-
>register_method({
2436 name
=> 'vm_feature',
2437 path
=> '{vmid}/feature',
2441 description
=> "Check if feature for virtual machine is available.",
2443 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2446 additionalProperties
=> 0,
2448 node
=> get_standard_option
('pve-node'),
2449 vmid
=> get_standard_option
('pve-vmid'),
2451 description
=> "Feature to check.",
2453 enum
=> [ 'snapshot', 'clone', 'copy' ],
2455 snapname
=> get_standard_option
('pve-snapshot-name', {
2463 hasFeature
=> { type
=> 'boolean' },
2466 items
=> { type
=> 'string' },
2473 my $node = extract_param
($param, 'node');
2475 my $vmid = extract_param
($param, 'vmid');
2477 my $snapname = extract_param
($param, 'snapname');
2479 my $feature = extract_param
($param, 'feature');
2481 my $running = PVE
::QemuServer
::check_running
($vmid);
2483 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2486 my $snap = $conf->{snapshots
}->{$snapname};
2487 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2490 my $storecfg = PVE
::Storage
::config
();
2492 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2493 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2496 hasFeature
=> $hasFeature,
2497 nodes
=> [ keys %$nodelist ],
2501 __PACKAGE__-
>register_method({
2503 path
=> '{vmid}/clone',
2507 description
=> "Create a copy of virtual machine/template.",
2509 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2510 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2511 "'Datastore.AllocateSpace' on any used storage.",
2514 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2516 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2517 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2522 additionalProperties
=> 0,
2524 node
=> get_standard_option
('pve-node'),
2525 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2526 newid
=> get_standard_option
('pve-vmid', {
2527 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2528 description
=> 'VMID for the clone.' }),
2531 type
=> 'string', format
=> 'dns-name',
2532 description
=> "Set a name for the new VM.",
2537 description
=> "Description for the new VM.",
2541 type
=> 'string', format
=> 'pve-poolid',
2542 description
=> "Add the new VM to the specified pool.",
2544 snapname
=> get_standard_option
('pve-snapshot-name', {
2547 storage
=> get_standard_option
('pve-storage-id', {
2548 description
=> "Target storage for full clone.",
2552 description
=> "Target format for file storage. Only valid for full clone.",
2555 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2560 description
=> "Create a full copy of all disks. This is always done when " .
2561 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2563 target
=> get_standard_option
('pve-node', {
2564 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2575 my $rpcenv = PVE
::RPCEnvironment
::get
();
2577 my $authuser = $rpcenv->get_user();
2579 my $node = extract_param
($param, 'node');
2581 my $vmid = extract_param
($param, 'vmid');
2583 my $newid = extract_param
($param, 'newid');
2585 my $pool = extract_param
($param, 'pool');
2587 if (defined($pool)) {
2588 $rpcenv->check_pool_exist($pool);
2591 my $snapname = extract_param
($param, 'snapname');
2593 my $storage = extract_param
($param, 'storage');
2595 my $format = extract_param
($param, 'format');
2597 my $target = extract_param
($param, 'target');
2599 my $localnode = PVE
::INotify
::nodename
();
2601 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2603 PVE
::Cluster
::check_node_exists
($target) if $target;
2605 my $storecfg = PVE
::Storage
::config
();
2608 # check if storage is enabled on local node
2609 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2611 # check if storage is available on target node
2612 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2613 # clone only works if target storage is shared
2614 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2615 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2619 PVE
::Cluster
::check_cfs_quorum
();
2621 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2623 # exclusive lock if VM is running - else shared lock is enough;
2624 my $shared_lock = $running ?
0 : 1;
2628 # do all tests after lock
2629 # we also try to do all tests before we fork the worker
2631 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2633 PVE
::QemuConfig-
>check_lock($conf);
2635 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2637 die "unexpected state change\n" if $verify_running != $running;
2639 die "snapshot '$snapname' does not exist\n"
2640 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2642 my $full = extract_param
($param, 'full');
2643 if (!defined($full)) {
2644 $full = !PVE
::QemuConfig-
>is_template($conf);
2647 die "parameter 'storage' not allowed for linked clones\n"
2648 if defined($storage) && !$full;
2650 die "parameter 'format' not allowed for linked clones\n"
2651 if defined($format) && !$full;
2653 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2655 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2657 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2659 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2661 die "unable to create VM $newid: config file already exists\n"
2664 my $newconf = { lock => 'clone' };
2669 foreach my $opt (keys %$oldconf) {
2670 my $value = $oldconf->{$opt};
2672 # do not copy snapshot related info
2673 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2674 $opt eq 'vmstate' || $opt eq 'snapstate';
2676 # no need to copy unused images, because VMID(owner) changes anyways
2677 next if $opt =~ m/^unused\d+$/;
2679 # always change MAC! address
2680 if ($opt =~ m/^net(\d+)$/) {
2681 my $net = PVE
::QemuServer
::parse_net
($value);
2682 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2683 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2684 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2685 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2686 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2687 die "unable to parse drive options for '$opt'\n" if !$drive;
2688 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2689 $newconf->{$opt} = $value; # simply copy configuration
2691 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2692 die "Full clone feature is not supported for drive '$opt'\n"
2693 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2694 $fullclone->{$opt} = 1;
2696 # not full means clone instead of copy
2697 die "Linked clone feature is not supported for drive '$opt'\n"
2698 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2700 $drives->{$opt} = $drive;
2701 push @$vollist, $drive->{file
};
2704 # copy everything else
2705 $newconf->{$opt} = $value;
2709 # auto generate a new uuid
2710 my ($uuid, $uuid_str);
2711 UUID
::generate
($uuid);
2712 UUID
::unparse
($uuid, $uuid_str);
2713 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2714 $smbios1->{uuid
} = $uuid_str;
2715 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2717 delete $newconf->{template
};
2719 if ($param->{name
}) {
2720 $newconf->{name
} = $param->{name
};
2722 if ($oldconf->{name
}) {
2723 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2725 $newconf->{name
} = "Copy-of-VM-$vmid";
2729 if ($param->{description
}) {
2730 $newconf->{description
} = $param->{description
};
2733 # create empty/temp config - this fails if VM already exists on other node
2734 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2739 my $newvollist = [];
2746 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2748 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2750 my $total_jobs = scalar(keys %{$drives});
2753 foreach my $opt (keys %$drives) {
2754 my $drive = $drives->{$opt};
2755 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2757 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2758 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2759 $jobs, $skipcomplete, $oldconf->{agent
});
2761 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2763 PVE
::QemuConfig-
>write_config($newid, $newconf);
2767 delete $newconf->{lock};
2769 # do not write pending changes
2770 if ($newconf->{pending
}) {
2771 warn "found pending changes, discarding for clone\n";
2772 delete $newconf->{pending
};
2775 PVE
::QemuConfig-
>write_config($newid, $newconf);
2778 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2779 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2780 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2782 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2783 die "Failed to move config to node '$target' - rename failed: $!\n"
2784 if !rename($conffile, $newconffile);
2787 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2792 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2794 sleep 1; # some storage like rbd need to wait before release volume - really?
2796 foreach my $volid (@$newvollist) {
2797 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2800 die "clone failed: $err";
2806 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2808 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2811 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2812 # Aquire exclusive lock lock for $newid
2813 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2818 __PACKAGE__-
>register_method({
2819 name
=> 'move_vm_disk',
2820 path
=> '{vmid}/move_disk',
2824 description
=> "Move volume to different storage.",
2826 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2828 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2829 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2833 additionalProperties
=> 0,
2835 node
=> get_standard_option
('pve-node'),
2836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2839 description
=> "The disk you want to move.",
2840 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2842 storage
=> get_standard_option
('pve-storage-id', {
2843 description
=> "Target storage.",
2844 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2848 description
=> "Target Format.",
2849 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2854 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2860 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2868 description
=> "the task ID.",
2873 my $rpcenv = PVE
::RPCEnvironment
::get
();
2875 my $authuser = $rpcenv->get_user();
2877 my $node = extract_param
($param, 'node');
2879 my $vmid = extract_param
($param, 'vmid');
2881 my $digest = extract_param
($param, 'digest');
2883 my $disk = extract_param
($param, 'disk');
2885 my $storeid = extract_param
($param, 'storage');
2887 my $format = extract_param
($param, 'format');
2889 my $storecfg = PVE
::Storage
::config
();
2891 my $updatefn = sub {
2893 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2895 PVE
::QemuConfig-
>check_lock($conf);
2897 die "checksum missmatch (file change by other user?)\n"
2898 if $digest && $digest ne $conf->{digest
};
2900 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2902 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2904 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2906 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2909 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2910 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2914 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2915 (!$format || !$oldfmt || $oldfmt eq $format);
2917 # this only checks snapshots because $disk is passed!
2918 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2919 die "you can't move a disk with snapshots and delete the source\n"
2920 if $snapshotted && $param->{delete};
2922 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2924 my $running = PVE
::QemuServer
::check_running
($vmid);
2926 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2930 my $newvollist = [];
2936 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2938 warn "moving disk with snapshots, snapshots will not be moved!\n"
2941 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2942 $vmid, $storeid, $format, 1, $newvollist);
2944 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2946 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2948 # convert moved disk to base if part of template
2949 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2950 if PVE
::QemuConfig-
>is_template($conf);
2952 PVE
::QemuConfig-
>write_config($vmid, $conf);
2955 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2956 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2963 foreach my $volid (@$newvollist) {
2964 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2967 die "storage migration failed: $err";
2970 if ($param->{delete}) {
2972 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
2973 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
2979 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2982 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
2985 __PACKAGE__-
>register_method({
2986 name
=> 'migrate_vm',
2987 path
=> '{vmid}/migrate',
2991 description
=> "Migrate virtual machine. Creates a new migration task.",
2993 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2996 additionalProperties
=> 0,
2998 node
=> get_standard_option
('pve-node'),
2999 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3000 target
=> get_standard_option
('pve-node', {
3001 description
=> "Target node.",
3002 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3006 description
=> "Use online/live migration.",
3011 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3016 enum
=> ['secure', 'insecure'],
3017 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3020 migration_network
=> {
3021 type
=> 'string', format
=> 'CIDR',
3022 description
=> "CIDR of the (sub) network that is used for migration.",
3025 "with-local-disks" => {
3027 description
=> "Enable live storage migration for local disk",
3030 targetstorage
=> get_standard_option
('pve-storage-id', {
3031 description
=> "Default target storage.",
3033 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3039 description
=> "the task ID.",
3044 my $rpcenv = PVE
::RPCEnvironment
::get
();
3046 my $authuser = $rpcenv->get_user();
3048 my $target = extract_param
($param, 'target');
3050 my $localnode = PVE
::INotify
::nodename
();
3051 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3053 PVE
::Cluster
::check_cfs_quorum
();
3055 PVE
::Cluster
::check_node_exists
($target);
3057 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3059 my $vmid = extract_param
($param, 'vmid');
3061 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3062 if !$param->{online
} && $param->{targetstorage
};
3064 raise_param_exc
({ force
=> "Only root may use this option." })
3065 if $param->{force
} && $authuser ne 'root@pam';
3067 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3068 if $param->{migration_type
} && $authuser ne 'root@pam';
3070 # allow root only until better network permissions are available
3071 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3072 if $param->{migration_network
} && $authuser ne 'root@pam';
3075 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3077 # try to detect errors early
3079 PVE
::QemuConfig-
>check_lock($conf);
3081 if (PVE
::QemuServer
::check_running
($vmid)) {
3082 die "cant migrate running VM without --online\n"
3083 if !$param->{online
};
3086 my $storecfg = PVE
::Storage
::config
();
3088 if( $param->{targetstorage
}) {
3089 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3091 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3094 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3099 my $service = "vm:$vmid";
3101 my $cmd = ['ha-manager', 'migrate', $service, $target];
3103 print "Requesting HA migration for VM $vmid to node $target\n";
3105 PVE
::Tools
::run_command
($cmd);
3110 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3115 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3119 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3122 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3127 __PACKAGE__-
>register_method({
3129 path
=> '{vmid}/monitor',
3133 description
=> "Execute Qemu monitor commands.",
3135 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3136 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3139 additionalProperties
=> 0,
3141 node
=> get_standard_option
('pve-node'),
3142 vmid
=> get_standard_option
('pve-vmid'),
3145 description
=> "The monitor command.",
3149 returns
=> { type
=> 'string'},
3153 my $rpcenv = PVE
::RPCEnvironment
::get
();
3154 my $authuser = $rpcenv->get_user();
3157 my $command = shift;
3158 return $command =~ m/^\s*info(\s+|$)/
3159 || $command =~ m/^\s*help\s*$/;
3162 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3163 if !&$is_ro($param->{command
});
3165 my $vmid = $param->{vmid
};
3167 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3171 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3173 $res = "ERROR: $@" if $@;
3178 __PACKAGE__-
>register_method({
3179 name
=> 'resize_vm',
3180 path
=> '{vmid}/resize',
3184 description
=> "Extend volume size.",
3186 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3189 additionalProperties
=> 0,
3191 node
=> get_standard_option
('pve-node'),
3192 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3193 skiplock
=> get_standard_option
('skiplock'),
3196 description
=> "The disk you want to resize.",
3197 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3201 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3202 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.",
3206 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3212 returns
=> { type
=> 'null'},
3216 my $rpcenv = PVE
::RPCEnvironment
::get
();
3218 my $authuser = $rpcenv->get_user();
3220 my $node = extract_param
($param, 'node');
3222 my $vmid = extract_param
($param, 'vmid');
3224 my $digest = extract_param
($param, 'digest');
3226 my $disk = extract_param
($param, 'disk');
3228 my $sizestr = extract_param
($param, 'size');
3230 my $skiplock = extract_param
($param, 'skiplock');
3231 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3232 if $skiplock && $authuser ne 'root@pam';
3234 my $storecfg = PVE
::Storage
::config
();
3236 my $updatefn = sub {
3238 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3240 die "checksum missmatch (file change by other user?)\n"
3241 if $digest && $digest ne $conf->{digest
};
3242 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3244 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3246 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3248 my (undef, undef, undef, undef, undef, undef, $format) =
3249 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3251 die "can't resize volume: $disk if snapshot exists\n"
3252 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3254 my $volid = $drive->{file
};
3256 die "disk '$disk' has no associated volume\n" if !$volid;
3258 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3260 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3262 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3264 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3265 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3267 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3268 my ($ext, $newsize, $unit) = ($1, $2, $4);
3271 $newsize = $newsize * 1024;
3272 } elsif ($unit eq 'M') {
3273 $newsize = $newsize * 1024 * 1024;
3274 } elsif ($unit eq 'G') {
3275 $newsize = $newsize * 1024 * 1024 * 1024;
3276 } elsif ($unit eq 'T') {
3277 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3280 $newsize += $size if $ext;
3281 $newsize = int($newsize);
3283 die "shrinking disks is not supported\n" if $newsize < $size;
3285 return if $size == $newsize;
3287 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3289 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3291 $drive->{size
} = $newsize;
3292 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3294 PVE
::QemuConfig-
>write_config($vmid, $conf);
3297 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3301 __PACKAGE__-
>register_method({
3302 name
=> 'snapshot_list',
3303 path
=> '{vmid}/snapshot',
3305 description
=> "List all snapshots.",
3307 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3310 protected
=> 1, # qemu pid files are only readable by root
3312 additionalProperties
=> 0,
3314 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3315 node
=> get_standard_option
('pve-node'),
3324 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3329 my $vmid = $param->{vmid
};
3331 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3332 my $snaphash = $conf->{snapshots
} || {};
3336 foreach my $name (keys %$snaphash) {
3337 my $d = $snaphash->{$name};
3340 snaptime
=> $d->{snaptime
} || 0,
3341 vmstate
=> $d->{vmstate
} ?
1 : 0,
3342 description
=> $d->{description
} || '',
3344 $item->{parent
} = $d->{parent
} if $d->{parent
};
3345 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3349 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3350 my $current = { name
=> 'current', digest
=> $conf->{digest
}, running
=> $running };
3351 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3353 push @$res, $current;
3358 __PACKAGE__-
>register_method({
3360 path
=> '{vmid}/snapshot',
3364 description
=> "Snapshot a VM.",
3366 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3369 additionalProperties
=> 0,
3371 node
=> get_standard_option
('pve-node'),
3372 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3373 snapname
=> get_standard_option
('pve-snapshot-name'),
3377 description
=> "Save the vmstate",
3382 description
=> "A textual description or comment.",
3388 description
=> "the task ID.",
3393 my $rpcenv = PVE
::RPCEnvironment
::get
();
3395 my $authuser = $rpcenv->get_user();
3397 my $node = extract_param
($param, 'node');
3399 my $vmid = extract_param
($param, 'vmid');
3401 my $snapname = extract_param
($param, 'snapname');
3403 die "unable to use snapshot name 'current' (reserved name)\n"
3404 if $snapname eq 'current';
3407 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3408 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3409 $param->{description
});
3412 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3415 __PACKAGE__-
>register_method({
3416 name
=> 'snapshot_cmd_idx',
3417 path
=> '{vmid}/snapshot/{snapname}',
3424 additionalProperties
=> 0,
3426 vmid
=> get_standard_option
('pve-vmid'),
3427 node
=> get_standard_option
('pve-node'),
3428 snapname
=> get_standard_option
('pve-snapshot-name'),
3437 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3444 push @$res, { cmd
=> 'rollback' };
3445 push @$res, { cmd
=> 'config' };
3450 __PACKAGE__-
>register_method({
3451 name
=> 'update_snapshot_config',
3452 path
=> '{vmid}/snapshot/{snapname}/config',
3456 description
=> "Update snapshot metadata.",
3458 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3461 additionalProperties
=> 0,
3463 node
=> get_standard_option
('pve-node'),
3464 vmid
=> get_standard_option
('pve-vmid'),
3465 snapname
=> get_standard_option
('pve-snapshot-name'),
3469 description
=> "A textual description or comment.",
3473 returns
=> { type
=> 'null' },
3477 my $rpcenv = PVE
::RPCEnvironment
::get
();
3479 my $authuser = $rpcenv->get_user();
3481 my $vmid = extract_param
($param, 'vmid');
3483 my $snapname = extract_param
($param, 'snapname');
3485 return undef if !defined($param->{description
});
3487 my $updatefn = sub {
3489 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3491 PVE
::QemuConfig-
>check_lock($conf);
3493 my $snap = $conf->{snapshots
}->{$snapname};
3495 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3497 $snap->{description
} = $param->{description
} if defined($param->{description
});
3499 PVE
::QemuConfig-
>write_config($vmid, $conf);
3502 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3507 __PACKAGE__-
>register_method({
3508 name
=> 'get_snapshot_config',
3509 path
=> '{vmid}/snapshot/{snapname}/config',
3512 description
=> "Get snapshot configuration",
3514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3517 additionalProperties
=> 0,
3519 node
=> get_standard_option
('pve-node'),
3520 vmid
=> get_standard_option
('pve-vmid'),
3521 snapname
=> get_standard_option
('pve-snapshot-name'),
3524 returns
=> { type
=> "object" },
3528 my $rpcenv = PVE
::RPCEnvironment
::get
();
3530 my $authuser = $rpcenv->get_user();
3532 my $vmid = extract_param
($param, 'vmid');
3534 my $snapname = extract_param
($param, 'snapname');
3536 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3538 my $snap = $conf->{snapshots
}->{$snapname};
3540 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3545 __PACKAGE__-
>register_method({
3547 path
=> '{vmid}/snapshot/{snapname}/rollback',
3551 description
=> "Rollback VM state to specified snapshot.",
3553 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3556 additionalProperties
=> 0,
3558 node
=> get_standard_option
('pve-node'),
3559 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3560 snapname
=> get_standard_option
('pve-snapshot-name'),
3565 description
=> "the task ID.",
3570 my $rpcenv = PVE
::RPCEnvironment
::get
();
3572 my $authuser = $rpcenv->get_user();
3574 my $node = extract_param
($param, 'node');
3576 my $vmid = extract_param
($param, 'vmid');
3578 my $snapname = extract_param
($param, 'snapname');
3581 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3582 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3586 # hold migration lock, this makes sure that nobody create replication snapshots
3587 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3590 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3593 __PACKAGE__-
>register_method({
3594 name
=> 'delsnapshot',
3595 path
=> '{vmid}/snapshot/{snapname}',
3599 description
=> "Delete a VM snapshot.",
3601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3604 additionalProperties
=> 0,
3606 node
=> get_standard_option
('pve-node'),
3607 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3608 snapname
=> get_standard_option
('pve-snapshot-name'),
3612 description
=> "For removal from config file, even if removing disk snapshots fails.",
3618 description
=> "the task ID.",
3623 my $rpcenv = PVE
::RPCEnvironment
::get
();
3625 my $authuser = $rpcenv->get_user();
3627 my $node = extract_param
($param, 'node');
3629 my $vmid = extract_param
($param, 'vmid');
3631 my $snapname = extract_param
($param, 'snapname');
3634 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3635 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3638 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3641 __PACKAGE__-
>register_method({
3643 path
=> '{vmid}/template',
3647 description
=> "Create a Template.",
3649 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3650 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3653 additionalProperties
=> 0,
3655 node
=> get_standard_option
('pve-node'),
3656 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3660 description
=> "If you want to convert only 1 disk to base image.",
3661 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3666 returns
=> { type
=> 'null'},
3670 my $rpcenv = PVE
::RPCEnvironment
::get
();
3672 my $authuser = $rpcenv->get_user();
3674 my $node = extract_param
($param, 'node');
3676 my $vmid = extract_param
($param, 'vmid');
3678 my $disk = extract_param
($param, 'disk');
3680 my $updatefn = sub {
3682 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3684 PVE
::QemuConfig-
>check_lock($conf);
3686 die "unable to create template, because VM contains snapshots\n"
3687 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3689 die "you can't convert a template to a template\n"
3690 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3692 die "you can't convert a VM to template if VM is running\n"
3693 if PVE
::QemuServer
::check_running
($vmid);
3696 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3699 $conf->{template
} = 1;
3700 PVE
::QemuConfig-
>write_config($vmid, $conf);
3702 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3705 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);