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.",
368 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
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};
393 __PACKAGE__-
>register_method({
397 description
=> "Create or restore a virtual machine.",
399 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
400 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
401 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
402 user
=> 'all', # check inside
407 additionalProperties
=> 0,
408 properties
=> PVE
::QemuServer
::json_config_properties
(
410 node
=> get_standard_option
('pve-node'),
411 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
413 description
=> "The backup file.",
417 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
419 storage
=> get_standard_option
('pve-storage-id', {
420 description
=> "Default storage.",
422 completion
=> \
&PVE
::QemuServer
::complete_storage
,
427 description
=> "Allow to overwrite existing VM.",
428 requires
=> 'archive',
433 description
=> "Assign a unique random ethernet address.",
434 requires
=> 'archive',
438 type
=> 'string', format
=> 'pve-poolid',
439 description
=> "Add the VM to the specified pool.",
442 description
=> "Override i/o bandwidth limit (in KiB/s).",
451 description
=> "Start VM after it was created successfully.",
461 my $rpcenv = PVE
::RPCEnvironment
::get
();
463 my $authuser = $rpcenv->get_user();
465 my $node = extract_param
($param, 'node');
467 my $vmid = extract_param
($param, 'vmid');
469 my $archive = extract_param
($param, 'archive');
470 my $is_restore = !!$archive;
472 my $storage = extract_param
($param, 'storage');
474 my $force = extract_param
($param, 'force');
476 my $unique = extract_param
($param, 'unique');
478 my $pool = extract_param
($param, 'pool');
480 my $bwlimit = extract_param
($param, 'bwlimit');
482 my $start_after_create = extract_param
($param, 'start');
484 my $filename = PVE
::QemuConfig-
>config_file($vmid);
486 my $storecfg = PVE
::Storage
::config
();
488 if (defined(my $ssh_keys = $param->{sshkeys
})) {
489 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
490 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
493 PVE
::Cluster
::check_cfs_quorum
();
495 if (defined($pool)) {
496 $rpcenv->check_pool_exist($pool);
499 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
500 if defined($storage);
502 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
504 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
506 } elsif ($archive && $force && (-f
$filename) &&
507 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
508 # OK: user has VM.Backup permissions, and want to restore an existing VM
514 &$resolve_cdrom_alias($param);
516 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
518 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
520 foreach my $opt (keys %$param) {
521 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
522 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
523 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
525 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
526 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
530 PVE
::QemuServer
::add_random_macs
($param);
532 my $keystr = join(' ', keys %$param);
533 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
535 if ($archive eq '-') {
536 die "pipe requires cli environment\n"
537 if $rpcenv->{type
} ne 'cli';
539 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
540 $archive = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
544 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
546 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
547 die "$emsg $@" if $@;
549 my $restorefn = sub {
550 my $conf = PVE
::QemuConfig-
>load_config($vmid);
552 PVE
::QemuConfig-
>check_protection($conf, $emsg);
554 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
555 die "$emsg vm is a template\n" if PVE
::QemuConfig-
>is_template($conf);
558 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
562 bwlimit
=> $bwlimit, });
564 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
566 if ($start_after_create) {
567 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
572 # ensure no old replication state are exists
573 PVE
::ReplicationState
::delete_guest_states
($vmid);
575 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
579 # ensure no old replication state are exists
580 PVE
::ReplicationState
::delete_guest_states
($vmid);
590 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
592 if (!$conf->{bootdisk
}) {
593 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
594 $conf->{bootdisk
} = $firstdisk if $firstdisk;
597 # auto generate uuid if user did not specify smbios1 option
598 if (!$conf->{smbios1
}) {
599 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
602 if (!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') {
603 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
606 PVE
::QemuConfig-
>write_config($vmid, $conf);
612 foreach my $volid (@$vollist) {
613 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
619 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
622 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
624 if ($start_after_create) {
625 print "Execute autostart\n";
626 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
631 my ($code, $worker_name);
633 $worker_name = 'qmrestore';
635 eval { $restorefn->() };
637 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
643 $worker_name = 'qmcreate';
645 eval { $createfn->() };
648 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
650 or die "failed to remove config file: $@\n";
658 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
661 __PACKAGE__-
>register_method({
666 description
=> "Directory index",
671 additionalProperties
=> 0,
673 node
=> get_standard_option
('pve-node'),
674 vmid
=> get_standard_option
('pve-vmid'),
682 subdir
=> { type
=> 'string' },
685 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
691 { subdir
=> 'config' },
692 { subdir
=> 'pending' },
693 { subdir
=> 'status' },
694 { subdir
=> 'unlink' },
695 { subdir
=> 'vncproxy' },
696 { subdir
=> 'termproxy' },
697 { subdir
=> 'migrate' },
698 { subdir
=> 'resize' },
699 { subdir
=> 'move' },
701 { subdir
=> 'rrddata' },
702 { subdir
=> 'monitor' },
703 { subdir
=> 'agent' },
704 { subdir
=> 'snapshot' },
705 { subdir
=> 'spiceproxy' },
706 { subdir
=> 'sendkey' },
707 { subdir
=> 'firewall' },
713 __PACKAGE__-
>register_method ({
714 subclass
=> "PVE::API2::Firewall::VM",
715 path
=> '{vmid}/firewall',
718 __PACKAGE__-
>register_method ({
719 subclass
=> "PVE::API2::Qemu::Agent",
720 path
=> '{vmid}/agent',
723 __PACKAGE__-
>register_method({
725 path
=> '{vmid}/rrd',
727 protected
=> 1, # fixme: can we avoid that?
729 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
731 description
=> "Read VM RRD statistics (returns PNG)",
733 additionalProperties
=> 0,
735 node
=> get_standard_option
('pve-node'),
736 vmid
=> get_standard_option
('pve-vmid'),
738 description
=> "Specify the time frame you are interested in.",
740 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
743 description
=> "The list of datasources you want to display.",
744 type
=> 'string', format
=> 'pve-configid-list',
747 description
=> "The RRD consolidation function",
749 enum
=> [ 'AVERAGE', 'MAX' ],
757 filename
=> { type
=> 'string' },
763 return PVE
::Cluster
::create_rrd_graph
(
764 "pve2-vm/$param->{vmid}", $param->{timeframe
},
765 $param->{ds
}, $param->{cf
});
769 __PACKAGE__-
>register_method({
771 path
=> '{vmid}/rrddata',
773 protected
=> 1, # fixme: can we avoid that?
775 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
777 description
=> "Read VM RRD statistics",
779 additionalProperties
=> 0,
781 node
=> get_standard_option
('pve-node'),
782 vmid
=> get_standard_option
('pve-vmid'),
784 description
=> "Specify the time frame you are interested in.",
786 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
789 description
=> "The RRD consolidation function",
791 enum
=> [ 'AVERAGE', 'MAX' ],
806 return PVE
::Cluster
::create_rrd_data
(
807 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
811 __PACKAGE__-
>register_method({
813 path
=> '{vmid}/config',
816 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
821 additionalProperties
=> 0,
823 node
=> get_standard_option
('pve-node'),
824 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
826 description
=> "Get current values (instead of pending values).",
834 description
=> "The current VM configuration.",
836 properties
=> PVE
::QemuServer
::json_config_properties
({
839 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
846 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
848 delete $conf->{snapshots
};
850 if (!$param->{current
}) {
851 foreach my $opt (keys %{$conf->{pending
}}) {
852 next if $opt eq 'delete';
853 my $value = $conf->{pending
}->{$opt};
854 next if ref($value); # just to be sure
855 $conf->{$opt} = $value;
857 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
858 foreach my $opt (keys %$pending_delete_hash) {
859 delete $conf->{$opt} if $conf->{$opt};
863 delete $conf->{pending
};
865 # hide cloudinit password
866 if ($conf->{cipassword
}) {
867 $conf->{cipassword
} = '**********';
873 __PACKAGE__-
>register_method({
874 name
=> 'vm_pending',
875 path
=> '{vmid}/pending',
878 description
=> "Get virtual machine configuration, including pending changes.",
880 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
883 additionalProperties
=> 0,
885 node
=> get_standard_option
('pve-node'),
886 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
895 description
=> "Configuration option name.",
899 description
=> "Current value.",
904 description
=> "Pending value.",
909 description
=> "Indicates a pending delete request if present and not 0. " .
910 "The value 2 indicates a force-delete request.",
922 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
924 my $pending_delete_hash = PVE
::QemuServer
::split_flagged_list
($conf->{pending
}->{delete});
928 foreach my $opt (keys %$conf) {
929 next if ref($conf->{$opt});
930 my $item = { key
=> $opt };
931 $item->{value
} = $conf->{$opt} if defined($conf->{$opt});
932 $item->{pending
} = $conf->{pending
}->{$opt} if defined($conf->{pending
}->{$opt});
933 $item->{delete} = ($pending_delete_hash->{$opt} ?
2 : 1) if exists $pending_delete_hash->{$opt};
935 # hide cloudinit password
936 if ($opt eq 'cipassword') {
937 $item->{value
} = '**********' if defined($item->{value
});
938 # the trailing space so that the pending string is different
939 $item->{pending
} = '********** ' if defined($item->{pending
});
944 foreach my $opt (keys %{$conf->{pending
}}) {
945 next if $opt eq 'delete';
946 next if ref($conf->{pending
}->{$opt}); # just to be sure
947 next if defined($conf->{$opt});
948 my $item = { key
=> $opt };
949 $item->{pending
} = $conf->{pending
}->{$opt};
951 # hide cloudinit password
952 if ($opt eq 'cipassword') {
953 $item->{pending
} = '**********' if defined($item->{pending
});
958 while (my ($opt, $force) = each %$pending_delete_hash) {
959 next if $conf->{pending
}->{$opt}; # just to be sure
960 next if $conf->{$opt};
961 my $item = { key
=> $opt, delete => ($force ?
2 : 1)};
968 # POST/PUT {vmid}/config implementation
970 # The original API used PUT (idempotent) an we assumed that all operations
971 # are fast. But it turned out that almost any configuration change can
972 # involve hot-plug actions, or disk alloc/free. Such actions can take long
973 # time to complete and have side effects (not idempotent).
975 # The new implementation uses POST and forks a worker process. We added
976 # a new option 'background_delay'. If specified we wait up to
977 # 'background_delay' second for the worker task to complete. It returns null
978 # if the task is finished within that time, else we return the UPID.
980 my $update_vm_api = sub {
981 my ($param, $sync) = @_;
983 my $rpcenv = PVE
::RPCEnvironment
::get
();
985 my $authuser = $rpcenv->get_user();
987 my $node = extract_param
($param, 'node');
989 my $vmid = extract_param
($param, 'vmid');
991 my $digest = extract_param
($param, 'digest');
993 my $background_delay = extract_param
($param, 'background_delay');
995 if (defined(my $cipassword = $param->{cipassword
})) {
996 # Same logic as in cloud-init (but with the regex fixed...)
997 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
998 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1001 my @paramarr = (); # used for log message
1002 foreach my $key (sort keys %$param) {
1003 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1004 push @paramarr, "-$key", $value;
1007 my $skiplock = extract_param
($param, 'skiplock');
1008 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1009 if $skiplock && $authuser ne 'root@pam';
1011 my $delete_str = extract_param
($param, 'delete');
1013 my $revert_str = extract_param
($param, 'revert');
1015 my $force = extract_param
($param, 'force');
1017 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1018 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1019 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1022 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1024 my $storecfg = PVE
::Storage
::config
();
1026 my $defaults = PVE
::QemuServer
::load_defaults
();
1028 &$resolve_cdrom_alias($param);
1030 # now try to verify all parameters
1033 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1034 if (!PVE
::QemuServer
::option_exists
($opt)) {
1035 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1038 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1039 "-revert $opt' at the same time" })
1040 if defined($param->{$opt});
1042 $revert->{$opt} = 1;
1046 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1047 $opt = 'ide2' if $opt eq 'cdrom';
1049 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1050 "-delete $opt' at the same time" })
1051 if defined($param->{$opt});
1053 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1054 "-revert $opt' at the same time" })
1057 if (!PVE
::QemuServer
::option_exists
($opt)) {
1058 raise_param_exc
({ delete => "unknown option '$opt'" });
1064 my $repl_conf = PVE
::ReplicationConfig-
>new();
1065 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1066 my $check_replication = sub {
1068 return if !$is_replicated;
1069 my $volid = $drive->{file
};
1070 return if !$volid || !($drive->{replicate
}//1);
1071 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1072 my ($storeid, $format);
1073 if ($volid =~ $NEW_DISK_RE) {
1075 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1077 ($storeid, undef) = PVE
::Storage
::parse_volume_id
($volid, 1);
1078 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1080 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1081 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1082 return if $scfg->{shared
};
1083 die "cannot add non-replicatable volume to a replicated VM\n";
1086 foreach my $opt (keys %$param) {
1087 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1088 # cleanup drive path
1089 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1090 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1091 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1092 $check_replication->($drive);
1093 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1094 } elsif ($opt =~ m/^net(\d+)$/) {
1096 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1097 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1098 } elsif ($opt eq 'vmgenid') {
1099 if ($param->{$opt} eq '1') {
1100 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1105 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1107 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1109 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1111 my $updatefn = sub {
1113 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1115 die "checksum missmatch (file change by other user?)\n"
1116 if $digest && $digest ne $conf->{digest
};
1118 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1120 foreach my $opt (keys %$revert) {
1121 if (defined($conf->{$opt})) {
1122 $param->{$opt} = $conf->{$opt};
1123 } elsif (defined($conf->{pending
}->{$opt})) {
1128 if ($param->{memory
} || defined($param->{balloon
})) {
1129 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1130 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1132 die "balloon value too large (must be smaller than assigned memory)\n"
1133 if $balloon && $balloon > $maxmem;
1136 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1140 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1142 # write updates to pending section
1144 my $modified = {}; # record what $option we modify
1146 foreach my $opt (@delete) {
1147 $modified->{$opt} = 1;
1148 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1149 if (!defined($conf->{$opt}) && !defined($conf->{pending
}->{$opt})) {
1150 warn "cannot delete '$opt' - not set in current configuration!\n";
1151 $modified->{$opt} = 0;
1155 if ($opt =~ m/^unused/) {
1156 my $drive = PVE
::QemuServer
::parse_drive
($opt, $conf->{$opt});
1157 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1158 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1159 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1160 delete $conf->{$opt};
1161 PVE
::QemuConfig-
>write_config($vmid, $conf);
1163 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1164 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1165 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1166 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1167 if defined($conf->{pending
}->{$opt});
1168 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1169 PVE
::QemuConfig-
>write_config($vmid, $conf);
1171 PVE
::QemuServer
::vmconfig_delete_pending_option
($conf, $opt, $force);
1172 PVE
::QemuConfig-
>write_config($vmid, $conf);
1176 foreach my $opt (keys %$param) { # add/change
1177 $modified->{$opt} = 1;
1178 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1179 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1181 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1182 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1183 # FIXME: cloudinit: CDROM or Disk?
1184 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1185 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1187 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1189 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1190 if defined($conf->{pending
}->{$opt});
1192 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1194 $conf->{pending
}->{$opt} = $param->{$opt};
1196 PVE
::QemuServer
::vmconfig_undelete_pending_option
($conf, $opt);
1197 PVE
::QemuConfig-
>write_config($vmid, $conf);
1200 # remove pending changes when nothing changed
1201 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1202 my $changes = PVE
::QemuServer
::vmconfig_cleanup_pending
($conf);
1203 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1205 return if !scalar(keys %{$conf->{pending
}});
1207 my $running = PVE
::QemuServer
::check_running
($vmid);
1209 # apply pending changes
1211 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1215 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1216 raise_param_exc
($errors) if scalar(keys %$errors);
1218 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1228 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1230 if ($background_delay) {
1232 # Note: It would be better to do that in the Event based HTTPServer
1233 # to avoid blocking call to sleep.
1235 my $end_time = time() + $background_delay;
1237 my $task = PVE
::Tools
::upid_decode
($upid);
1240 while (time() < $end_time) {
1241 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1243 sleep(1); # this gets interrupted when child process ends
1247 my $status = PVE
::Tools
::upid_read_status
($upid);
1248 return undef if $status eq 'OK';
1257 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1260 my $vm_config_perm_list = [
1265 'VM.Config.Network',
1267 'VM.Config.Options',
1270 __PACKAGE__-
>register_method({
1271 name
=> 'update_vm_async',
1272 path
=> '{vmid}/config',
1276 description
=> "Set virtual machine options (asynchrounous API).",
1278 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1281 additionalProperties
=> 0,
1282 properties
=> PVE
::QemuServer
::json_config_properties
(
1284 node
=> get_standard_option
('pve-node'),
1285 vmid
=> get_standard_option
('pve-vmid'),
1286 skiplock
=> get_standard_option
('skiplock'),
1288 type
=> 'string', format
=> 'pve-configid-list',
1289 description
=> "A list of settings you want to delete.",
1293 type
=> 'string', format
=> 'pve-configid-list',
1294 description
=> "Revert a pending change.",
1299 description
=> $opt_force_description,
1301 requires
=> 'delete',
1305 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1309 background_delay
=> {
1311 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1322 code
=> $update_vm_api,
1325 __PACKAGE__-
>register_method({
1326 name
=> 'update_vm',
1327 path
=> '{vmid}/config',
1331 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1333 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1336 additionalProperties
=> 0,
1337 properties
=> PVE
::QemuServer
::json_config_properties
(
1339 node
=> get_standard_option
('pve-node'),
1340 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1341 skiplock
=> get_standard_option
('skiplock'),
1343 type
=> 'string', format
=> 'pve-configid-list',
1344 description
=> "A list of settings you want to delete.",
1348 type
=> 'string', format
=> 'pve-configid-list',
1349 description
=> "Revert a pending change.",
1354 description
=> $opt_force_description,
1356 requires
=> 'delete',
1360 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1366 returns
=> { type
=> 'null' },
1369 &$update_vm_api($param, 1);
1375 __PACKAGE__-
>register_method({
1376 name
=> 'destroy_vm',
1381 description
=> "Destroy the vm (also delete all used/owned volumes).",
1383 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1386 additionalProperties
=> 0,
1388 node
=> get_standard_option
('pve-node'),
1389 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1390 skiplock
=> get_standard_option
('skiplock'),
1399 my $rpcenv = PVE
::RPCEnvironment
::get
();
1401 my $authuser = $rpcenv->get_user();
1403 my $vmid = $param->{vmid
};
1405 my $skiplock = $param->{skiplock
};
1406 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1407 if $skiplock && $authuser ne 'root@pam';
1410 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1412 my $storecfg = PVE
::Storage
::config
();
1414 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1416 die "unable to remove VM $vmid - used in HA resources\n"
1417 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1419 # do not allow destroy if there are replication jobs
1420 my $repl_conf = PVE
::ReplicationConfig-
>new();
1421 $repl_conf->check_for_existing_jobs($vmid);
1423 # early tests (repeat after locking)
1424 die "VM $vmid is running - destroy failed\n"
1425 if PVE
::QemuServer
::check_running
($vmid);
1430 syslog
('info', "destroy VM $vmid: $upid\n");
1432 PVE
::QemuServer
::vm_destroy
($storecfg, $vmid, $skiplock);
1434 PVE
::AccessControl
::remove_vm_access
($vmid);
1436 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1439 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1442 __PACKAGE__-
>register_method({
1444 path
=> '{vmid}/unlink',
1448 description
=> "Unlink/delete disk images.",
1450 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1453 additionalProperties
=> 0,
1455 node
=> get_standard_option
('pve-node'),
1456 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1458 type
=> 'string', format
=> 'pve-configid-list',
1459 description
=> "A list of disk IDs you want to delete.",
1463 description
=> $opt_force_description,
1468 returns
=> { type
=> 'null'},
1472 $param->{delete} = extract_param
($param, 'idlist');
1474 __PACKAGE__-
>update_vm($param);
1481 __PACKAGE__-
>register_method({
1483 path
=> '{vmid}/vncproxy',
1487 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1489 description
=> "Creates a TCP VNC proxy connections.",
1491 additionalProperties
=> 0,
1493 node
=> get_standard_option
('pve-node'),
1494 vmid
=> get_standard_option
('pve-vmid'),
1498 description
=> "starts websockify instead of vncproxy",
1503 additionalProperties
=> 0,
1505 user
=> { type
=> 'string' },
1506 ticket
=> { type
=> 'string' },
1507 cert
=> { type
=> 'string' },
1508 port
=> { type
=> 'integer' },
1509 upid
=> { type
=> 'string' },
1515 my $rpcenv = PVE
::RPCEnvironment
::get
();
1517 my $authuser = $rpcenv->get_user();
1519 my $vmid = $param->{vmid
};
1520 my $node = $param->{node
};
1521 my $websocket = $param->{websocket
};
1523 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1525 my $authpath = "/vms/$vmid";
1527 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1529 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1532 my ($remip, $family);
1535 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1536 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1537 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1538 $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
1540 $family = PVE
::Tools
::get_host_address_family
($node);
1543 my $port = PVE
::Tools
::next_vnc_port
($family);
1550 syslog
('info', "starting vnc proxy $upid\n");
1554 if ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/)) {
1557 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1559 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1560 '-timeout', $timeout, '-authpath', $authpath,
1561 '-perm', 'Sys.Console'];
1563 if ($param->{websocket
}) {
1564 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1565 push @$cmd, '-notls', '-listen', 'localhost';
1568 push @$cmd, '-c', @$remcmd, @$termcmd;
1570 PVE
::Tools
::run_command
($cmd);
1574 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1576 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1578 my $sock = IO
::Socket
::IP-
>new(
1583 GetAddrInfoFlags
=> 0,
1584 ) or die "failed to create socket: $!\n";
1585 # Inside the worker we shouldn't have any previous alarms
1586 # running anyway...:
1588 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1590 accept(my $cli, $sock) or die "connection failed: $!\n";
1593 if (PVE
::Tools
::run_command
($cmd,
1594 output
=> '>&'.fileno($cli),
1595 input
=> '<&'.fileno($cli),
1598 die "Failed to run vncproxy.\n";
1605 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1607 PVE
::Tools
::wait_for_vnc_port
($port);
1618 __PACKAGE__-
>register_method({
1619 name
=> 'termproxy',
1620 path
=> '{vmid}/termproxy',
1624 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1626 description
=> "Creates a TCP proxy connections.",
1628 additionalProperties
=> 0,
1630 node
=> get_standard_option
('pve-node'),
1631 vmid
=> get_standard_option
('pve-vmid'),
1635 enum
=> [qw(serial0 serial1 serial2 serial3)],
1636 description
=> "opens a serial terminal (defaults to display)",
1641 additionalProperties
=> 0,
1643 user
=> { type
=> 'string' },
1644 ticket
=> { type
=> 'string' },
1645 port
=> { type
=> 'integer' },
1646 upid
=> { type
=> 'string' },
1652 my $rpcenv = PVE
::RPCEnvironment
::get
();
1654 my $authuser = $rpcenv->get_user();
1656 my $vmid = $param->{vmid
};
1657 my $node = $param->{node
};
1658 my $serial = $param->{serial
};
1660 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1662 if (!defined($serial)) {
1663 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1664 $serial = $conf->{vga
};
1668 my $authpath = "/vms/$vmid";
1670 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1672 my ($remip, $family);
1674 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1675 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1677 $family = PVE
::Tools
::get_host_address_family
($node);
1680 my $port = PVE
::Tools
::next_vnc_port
($family);
1682 my $remcmd = $remip ?
1683 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
1685 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1686 push @$termcmd, '-iface', $serial if $serial;
1691 syslog
('info', "starting qemu termproxy $upid\n");
1693 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1694 '--perm', 'VM.Console', '--'];
1695 push @$cmd, @$remcmd, @$termcmd;
1697 PVE
::Tools
::run_command
($cmd);
1700 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1702 PVE
::Tools
::wait_for_vnc_port
($port);
1712 __PACKAGE__-
>register_method({
1713 name
=> 'vncwebsocket',
1714 path
=> '{vmid}/vncwebsocket',
1717 description
=> "You also need to pass a valid ticket (vncticket).",
1718 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1720 description
=> "Opens a weksocket for VNC traffic.",
1722 additionalProperties
=> 0,
1724 node
=> get_standard_option
('pve-node'),
1725 vmid
=> get_standard_option
('pve-vmid'),
1727 description
=> "Ticket from previous call to vncproxy.",
1732 description
=> "Port number returned by previous vncproxy call.",
1742 port
=> { type
=> 'string' },
1748 my $rpcenv = PVE
::RPCEnvironment
::get
();
1750 my $authuser = $rpcenv->get_user();
1752 my $vmid = $param->{vmid
};
1753 my $node = $param->{node
};
1755 my $authpath = "/vms/$vmid";
1757 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1759 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1761 # Note: VNC ports are acessible from outside, so we do not gain any
1762 # security if we verify that $param->{port} belongs to VM $vmid. This
1763 # check is done by verifying the VNC ticket (inside VNC protocol).
1765 my $port = $param->{port
};
1767 return { port
=> $port };
1770 __PACKAGE__-
>register_method({
1771 name
=> 'spiceproxy',
1772 path
=> '{vmid}/spiceproxy',
1777 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1779 description
=> "Returns a SPICE configuration to connect to the VM.",
1781 additionalProperties
=> 0,
1783 node
=> get_standard_option
('pve-node'),
1784 vmid
=> get_standard_option
('pve-vmid'),
1785 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1788 returns
=> get_standard_option
('remote-viewer-config'),
1792 my $rpcenv = PVE
::RPCEnvironment
::get
();
1794 my $authuser = $rpcenv->get_user();
1796 my $vmid = $param->{vmid
};
1797 my $node = $param->{node
};
1798 my $proxy = $param->{proxy
};
1800 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1801 my $title = "VM $vmid";
1802 $title .= " - ". $conf->{name
} if $conf->{name
};
1804 my $port = PVE
::QemuServer
::spice_port
($vmid);
1806 my ($ticket, undef, $remote_viewer_config) =
1807 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1809 PVE
::QemuServer
::vm_mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1810 PVE
::QemuServer
::vm_mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1812 return $remote_viewer_config;
1815 __PACKAGE__-
>register_method({
1817 path
=> '{vmid}/status',
1820 description
=> "Directory index",
1825 additionalProperties
=> 0,
1827 node
=> get_standard_option
('pve-node'),
1828 vmid
=> get_standard_option
('pve-vmid'),
1836 subdir
=> { type
=> 'string' },
1839 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1845 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1848 { subdir
=> 'current' },
1849 { subdir
=> 'start' },
1850 { subdir
=> 'stop' },
1856 __PACKAGE__-
>register_method({
1857 name
=> 'vm_status',
1858 path
=> '{vmid}/status/current',
1861 protected
=> 1, # qemu pid files are only readable by root
1862 description
=> "Get virtual machine status.",
1864 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1867 additionalProperties
=> 0,
1869 node
=> get_standard_option
('pve-node'),
1870 vmid
=> get_standard_option
('pve-vmid'),
1876 %$PVE::QemuServer
::vmstatus_return_properties
,
1878 description
=> "HA manager service status.",
1882 description
=> "Qemu VGA configuration supports spice.",
1887 description
=> "Qemu GuestAgent enabled in config.",
1897 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1899 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1900 my $status = $vmstatus->{$param->{vmid
}};
1902 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1904 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1905 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1910 __PACKAGE__-
>register_method({
1912 path
=> '{vmid}/status/start',
1916 description
=> "Start virtual machine.",
1918 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1921 additionalProperties
=> 0,
1923 node
=> get_standard_option
('pve-node'),
1924 vmid
=> get_standard_option
('pve-vmid',
1925 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1926 skiplock
=> get_standard_option
('skiplock'),
1927 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1928 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1931 enum
=> ['secure', 'insecure'],
1932 description
=> "Migration traffic is encrypted using an SSH " .
1933 "tunnel by default. On secure, completely private networks " .
1934 "this can be disabled to increase performance.",
1937 migration_network
=> {
1938 type
=> 'string', format
=> 'CIDR',
1939 description
=> "CIDR of the (sub) network that is used for migration.",
1942 machine
=> get_standard_option
('pve-qm-machine'),
1944 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1956 my $rpcenv = PVE
::RPCEnvironment
::get
();
1958 my $authuser = $rpcenv->get_user();
1960 my $node = extract_param
($param, 'node');
1962 my $vmid = extract_param
($param, 'vmid');
1964 my $machine = extract_param
($param, 'machine');
1966 my $stateuri = extract_param
($param, 'stateuri');
1967 raise_param_exc
({ stateuri
=> "Only root may use this option." })
1968 if $stateuri && $authuser ne 'root@pam';
1970 my $skiplock = extract_param
($param, 'skiplock');
1971 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1972 if $skiplock && $authuser ne 'root@pam';
1974 my $migratedfrom = extract_param
($param, 'migratedfrom');
1975 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
1976 if $migratedfrom && $authuser ne 'root@pam';
1978 my $migration_type = extract_param
($param, 'migration_type');
1979 raise_param_exc
({ migration_type
=> "Only root may use this option." })
1980 if $migration_type && $authuser ne 'root@pam';
1982 my $migration_network = extract_param
($param, 'migration_network');
1983 raise_param_exc
({ migration_network
=> "Only root may use this option." })
1984 if $migration_network && $authuser ne 'root@pam';
1986 my $targetstorage = extract_param
($param, 'targetstorage');
1987 raise_param_exc
({ targetstorage
=> "Only root may use this option." })
1988 if $targetstorage && $authuser ne 'root@pam';
1990 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
1991 if $targetstorage && !$migratedfrom;
1993 # read spice ticket from STDIN
1995 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
1996 if (defined(my $line = <STDIN
>)) {
1998 $spice_ticket = $line;
2002 PVE
::Cluster
::check_cfs_quorum
();
2004 my $storecfg = PVE
::Storage
::config
();
2006 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri &&
2007 $rpcenv->{type
} ne 'ha') {
2012 my $service = "vm:$vmid";
2014 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
2016 print "Requesting HA start for VM $vmid\n";
2018 PVE
::Tools
::run_command
($cmd);
2023 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2030 syslog
('info', "start VM $vmid: $upid\n");
2032 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2033 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2038 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2042 __PACKAGE__-
>register_method({
2044 path
=> '{vmid}/status/stop',
2048 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2049 "is akin to pulling the power plug of a running computer and may damage the VM data",
2051 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2054 additionalProperties
=> 0,
2056 node
=> get_standard_option
('pve-node'),
2057 vmid
=> get_standard_option
('pve-vmid',
2058 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2059 skiplock
=> get_standard_option
('skiplock'),
2060 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2062 description
=> "Wait maximal timeout seconds.",
2068 description
=> "Do not deactivate storage volumes.",
2081 my $rpcenv = PVE
::RPCEnvironment
::get
();
2083 my $authuser = $rpcenv->get_user();
2085 my $node = extract_param
($param, 'node');
2087 my $vmid = extract_param
($param, 'vmid');
2089 my $skiplock = extract_param
($param, 'skiplock');
2090 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2091 if $skiplock && $authuser ne 'root@pam';
2093 my $keepActive = extract_param
($param, 'keepActive');
2094 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2095 if $keepActive && $authuser ne 'root@pam';
2097 my $migratedfrom = extract_param
($param, 'migratedfrom');
2098 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2099 if $migratedfrom && $authuser ne 'root@pam';
2102 my $storecfg = PVE
::Storage
::config
();
2104 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2109 my $service = "vm:$vmid";
2111 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2113 print "Requesting HA stop for VM $vmid\n";
2115 PVE
::Tools
::run_command
($cmd);
2120 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2126 syslog
('info', "stop VM $vmid: $upid\n");
2128 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2129 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2134 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2138 __PACKAGE__-
>register_method({
2140 path
=> '{vmid}/status/reset',
2144 description
=> "Reset virtual machine.",
2146 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2149 additionalProperties
=> 0,
2151 node
=> get_standard_option
('pve-node'),
2152 vmid
=> get_standard_option
('pve-vmid',
2153 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2154 skiplock
=> get_standard_option
('skiplock'),
2163 my $rpcenv = PVE
::RPCEnvironment
::get
();
2165 my $authuser = $rpcenv->get_user();
2167 my $node = extract_param
($param, 'node');
2169 my $vmid = extract_param
($param, 'vmid');
2171 my $skiplock = extract_param
($param, 'skiplock');
2172 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2173 if $skiplock && $authuser ne 'root@pam';
2175 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2180 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2185 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2188 __PACKAGE__-
>register_method({
2189 name
=> 'vm_shutdown',
2190 path
=> '{vmid}/status/shutdown',
2194 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2195 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2197 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2200 additionalProperties
=> 0,
2202 node
=> get_standard_option
('pve-node'),
2203 vmid
=> get_standard_option
('pve-vmid',
2204 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2205 skiplock
=> get_standard_option
('skiplock'),
2207 description
=> "Wait maximal timeout seconds.",
2213 description
=> "Make sure the VM stops.",
2219 description
=> "Do not deactivate storage volumes.",
2232 my $rpcenv = PVE
::RPCEnvironment
::get
();
2234 my $authuser = $rpcenv->get_user();
2236 my $node = extract_param
($param, 'node');
2238 my $vmid = extract_param
($param, 'vmid');
2240 my $skiplock = extract_param
($param, 'skiplock');
2241 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2242 if $skiplock && $authuser ne 'root@pam';
2244 my $keepActive = extract_param
($param, 'keepActive');
2245 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2246 if $keepActive && $authuser ne 'root@pam';
2248 my $storecfg = PVE
::Storage
::config
();
2252 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2253 # otherwise, we will infer a shutdown command, but run into the timeout,
2254 # then when the vm is resumed, it will instantly shutdown
2256 # checking the qmp status here to get feedback to the gui/cli/api
2257 # and the status query should not take too long
2260 $qmpstatus = PVE
::QemuServer
::vm_qmp_command
($vmid, { execute
=> "query-status" }, 0);
2264 if (!$err && $qmpstatus->{status
} eq "paused") {
2265 if ($param->{forceStop
}) {
2266 warn "VM is paused - stop instead of shutdown\n";
2269 die "VM is paused - cannot shutdown\n";
2273 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) &&
2274 ($rpcenv->{type
} ne 'ha')) {
2279 my $service = "vm:$vmid";
2281 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2283 print "Requesting HA stop for VM $vmid\n";
2285 PVE
::Tools
::run_command
($cmd);
2290 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2297 syslog
('info', "shutdown VM $vmid: $upid\n");
2299 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2300 $shutdown, $param->{forceStop
}, $keepActive);
2305 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2309 __PACKAGE__-
>register_method({
2310 name
=> 'vm_suspend',
2311 path
=> '{vmid}/status/suspend',
2315 description
=> "Suspend virtual machine.",
2317 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2320 additionalProperties
=> 0,
2322 node
=> get_standard_option
('pve-node'),
2323 vmid
=> get_standard_option
('pve-vmid',
2324 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2325 skiplock
=> get_standard_option
('skiplock'),
2334 my $rpcenv = PVE
::RPCEnvironment
::get
();
2336 my $authuser = $rpcenv->get_user();
2338 my $node = extract_param
($param, 'node');
2340 my $vmid = extract_param
($param, 'vmid');
2342 my $skiplock = extract_param
($param, 'skiplock');
2343 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2344 if $skiplock && $authuser ne 'root@pam';
2346 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2351 syslog
('info', "suspend VM $vmid: $upid\n");
2353 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock);
2358 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
2361 __PACKAGE__-
>register_method({
2362 name
=> 'vm_resume',
2363 path
=> '{vmid}/status/resume',
2367 description
=> "Resume virtual machine.",
2369 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2372 additionalProperties
=> 0,
2374 node
=> get_standard_option
('pve-node'),
2375 vmid
=> get_standard_option
('pve-vmid',
2376 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2377 skiplock
=> get_standard_option
('skiplock'),
2378 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2388 my $rpcenv = PVE
::RPCEnvironment
::get
();
2390 my $authuser = $rpcenv->get_user();
2392 my $node = extract_param
($param, 'node');
2394 my $vmid = extract_param
($param, 'vmid');
2396 my $skiplock = extract_param
($param, 'skiplock');
2397 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2398 if $skiplock && $authuser ne 'root@pam';
2400 my $nocheck = extract_param
($param, 'nocheck');
2402 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2407 syslog
('info', "resume VM $vmid: $upid\n");
2409 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2414 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2417 __PACKAGE__-
>register_method({
2418 name
=> 'vm_sendkey',
2419 path
=> '{vmid}/sendkey',
2423 description
=> "Send key event to virtual machine.",
2425 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2428 additionalProperties
=> 0,
2430 node
=> get_standard_option
('pve-node'),
2431 vmid
=> get_standard_option
('pve-vmid',
2432 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2433 skiplock
=> get_standard_option
('skiplock'),
2435 description
=> "The key (qemu monitor encoding).",
2440 returns
=> { type
=> 'null'},
2444 my $rpcenv = PVE
::RPCEnvironment
::get
();
2446 my $authuser = $rpcenv->get_user();
2448 my $node = extract_param
($param, 'node');
2450 my $vmid = extract_param
($param, 'vmid');
2452 my $skiplock = extract_param
($param, 'skiplock');
2453 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2454 if $skiplock && $authuser ne 'root@pam';
2456 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2461 __PACKAGE__-
>register_method({
2462 name
=> 'vm_feature',
2463 path
=> '{vmid}/feature',
2467 description
=> "Check if feature for virtual machine is available.",
2469 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2472 additionalProperties
=> 0,
2474 node
=> get_standard_option
('pve-node'),
2475 vmid
=> get_standard_option
('pve-vmid'),
2477 description
=> "Feature to check.",
2479 enum
=> [ 'snapshot', 'clone', 'copy' ],
2481 snapname
=> get_standard_option
('pve-snapshot-name', {
2489 hasFeature
=> { type
=> 'boolean' },
2492 items
=> { type
=> 'string' },
2499 my $node = extract_param
($param, 'node');
2501 my $vmid = extract_param
($param, 'vmid');
2503 my $snapname = extract_param
($param, 'snapname');
2505 my $feature = extract_param
($param, 'feature');
2507 my $running = PVE
::QemuServer
::check_running
($vmid);
2509 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2512 my $snap = $conf->{snapshots
}->{$snapname};
2513 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2516 my $storecfg = PVE
::Storage
::config
();
2518 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2519 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2522 hasFeature
=> $hasFeature,
2523 nodes
=> [ keys %$nodelist ],
2527 __PACKAGE__-
>register_method({
2529 path
=> '{vmid}/clone',
2533 description
=> "Create a copy of virtual machine/template.",
2535 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2536 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2537 "'Datastore.AllocateSpace' on any used storage.",
2540 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2542 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2543 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2548 additionalProperties
=> 0,
2550 node
=> get_standard_option
('pve-node'),
2551 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2552 newid
=> get_standard_option
('pve-vmid', {
2553 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2554 description
=> 'VMID for the clone.' }),
2557 type
=> 'string', format
=> 'dns-name',
2558 description
=> "Set a name for the new VM.",
2563 description
=> "Description for the new VM.",
2567 type
=> 'string', format
=> 'pve-poolid',
2568 description
=> "Add the new VM to the specified pool.",
2570 snapname
=> get_standard_option
('pve-snapshot-name', {
2573 storage
=> get_standard_option
('pve-storage-id', {
2574 description
=> "Target storage for full clone.",
2578 description
=> "Target format for file storage. Only valid for full clone.",
2581 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2586 description
=> "Create a full copy of all disks. This is always done when " .
2587 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2589 target
=> get_standard_option
('pve-node', {
2590 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2601 my $rpcenv = PVE
::RPCEnvironment
::get
();
2603 my $authuser = $rpcenv->get_user();
2605 my $node = extract_param
($param, 'node');
2607 my $vmid = extract_param
($param, 'vmid');
2609 my $newid = extract_param
($param, 'newid');
2611 my $pool = extract_param
($param, 'pool');
2613 if (defined($pool)) {
2614 $rpcenv->check_pool_exist($pool);
2617 my $snapname = extract_param
($param, 'snapname');
2619 my $storage = extract_param
($param, 'storage');
2621 my $format = extract_param
($param, 'format');
2623 my $target = extract_param
($param, 'target');
2625 my $localnode = PVE
::INotify
::nodename
();
2627 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2629 PVE
::Cluster
::check_node_exists
($target) if $target;
2631 my $storecfg = PVE
::Storage
::config
();
2634 # check if storage is enabled on local node
2635 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2637 # check if storage is available on target node
2638 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2639 # clone only works if target storage is shared
2640 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2641 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2645 PVE
::Cluster
::check_cfs_quorum
();
2647 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2649 # exclusive lock if VM is running - else shared lock is enough;
2650 my $shared_lock = $running ?
0 : 1;
2654 # do all tests after lock
2655 # we also try to do all tests before we fork the worker
2657 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2659 PVE
::QemuConfig-
>check_lock($conf);
2661 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2663 die "unexpected state change\n" if $verify_running != $running;
2665 die "snapshot '$snapname' does not exist\n"
2666 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2668 my $full = extract_param
($param, 'full');
2669 if (!defined($full)) {
2670 $full = !PVE
::QemuConfig-
>is_template($conf);
2673 die "parameter 'storage' not allowed for linked clones\n"
2674 if defined($storage) && !$full;
2676 die "parameter 'format' not allowed for linked clones\n"
2677 if defined($format) && !$full;
2679 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2681 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2683 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2685 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2687 die "unable to create VM $newid: config file already exists\n"
2690 my $newconf = { lock => 'clone' };
2695 foreach my $opt (keys %$oldconf) {
2696 my $value = $oldconf->{$opt};
2698 # do not copy snapshot related info
2699 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2700 $opt eq 'vmstate' || $opt eq 'snapstate';
2702 # no need to copy unused images, because VMID(owner) changes anyways
2703 next if $opt =~ m/^unused\d+$/;
2705 # always change MAC! address
2706 if ($opt =~ m/^net(\d+)$/) {
2707 my $net = PVE
::QemuServer
::parse_net
($value);
2708 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2709 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2710 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2711 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2712 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2713 die "unable to parse drive options for '$opt'\n" if !$drive;
2714 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2715 $newconf->{$opt} = $value; # simply copy configuration
2717 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2718 die "Full clone feature is not supported for drive '$opt'\n"
2719 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2720 $fullclone->{$opt} = 1;
2722 # not full means clone instead of copy
2723 die "Linked clone feature is not supported for drive '$opt'\n"
2724 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2726 $drives->{$opt} = $drive;
2727 push @$vollist, $drive->{file
};
2730 # copy everything else
2731 $newconf->{$opt} = $value;
2735 # auto generate a new uuid
2736 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2737 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2738 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2740 # auto generate a new vmgenid if the option was set
2741 if ($newconf->{vmgenid
}) {
2742 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2745 delete $newconf->{template
};
2747 if ($param->{name
}) {
2748 $newconf->{name
} = $param->{name
};
2750 if ($oldconf->{name
}) {
2751 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2753 $newconf->{name
} = "Copy-of-VM-$vmid";
2757 if ($param->{description
}) {
2758 $newconf->{description
} = $param->{description
};
2761 # create empty/temp config - this fails if VM already exists on other node
2762 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2767 my $newvollist = [];
2774 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2776 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2778 my $total_jobs = scalar(keys %{$drives});
2781 foreach my $opt (keys %$drives) {
2782 my $drive = $drives->{$opt};
2783 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2785 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2786 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2787 $jobs, $skipcomplete, $oldconf->{agent
});
2789 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2791 PVE
::QemuConfig-
>write_config($newid, $newconf);
2795 delete $newconf->{lock};
2797 # do not write pending changes
2798 if (my @changes = keys %{$newconf->{pending
}}) {
2799 my $pending = join(',', @changes);
2800 warn "found pending changes for '$pending', discarding for clone\n";
2801 delete $newconf->{pending
};
2804 PVE
::QemuConfig-
>write_config($newid, $newconf);
2807 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2808 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2809 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2811 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2812 die "Failed to move config to node '$target' - rename failed: $!\n"
2813 if !rename($conffile, $newconffile);
2816 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2821 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2823 sleep 1; # some storage like rbd need to wait before release volume - really?
2825 foreach my $volid (@$newvollist) {
2826 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2829 die "clone failed: $err";
2835 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2837 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2840 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2841 # Aquire exclusive lock lock for $newid
2842 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2847 __PACKAGE__-
>register_method({
2848 name
=> 'move_vm_disk',
2849 path
=> '{vmid}/move_disk',
2853 description
=> "Move volume to different storage.",
2855 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2857 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2858 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2862 additionalProperties
=> 0,
2864 node
=> get_standard_option
('pve-node'),
2865 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2868 description
=> "The disk you want to move.",
2869 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2871 storage
=> get_standard_option
('pve-storage-id', {
2872 description
=> "Target storage.",
2873 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2877 description
=> "Target Format.",
2878 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2883 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2889 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2897 description
=> "the task ID.",
2902 my $rpcenv = PVE
::RPCEnvironment
::get
();
2904 my $authuser = $rpcenv->get_user();
2906 my $node = extract_param
($param, 'node');
2908 my $vmid = extract_param
($param, 'vmid');
2910 my $digest = extract_param
($param, 'digest');
2912 my $disk = extract_param
($param, 'disk');
2914 my $storeid = extract_param
($param, 'storage');
2916 my $format = extract_param
($param, 'format');
2918 my $storecfg = PVE
::Storage
::config
();
2920 my $updatefn = sub {
2922 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2924 PVE
::QemuConfig-
>check_lock($conf);
2926 die "checksum missmatch (file change by other user?)\n"
2927 if $digest && $digest ne $conf->{digest
};
2929 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2931 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
2933 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
2935 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
2938 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
2939 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2943 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
2944 (!$format || !$oldfmt || $oldfmt eq $format);
2946 # this only checks snapshots because $disk is passed!
2947 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
2948 die "you can't move a disk with snapshots and delete the source\n"
2949 if $snapshotted && $param->{delete};
2951 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2953 my $running = PVE
::QemuServer
::check_running
($vmid);
2955 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
2959 my $newvollist = [];
2965 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2967 warn "moving disk with snapshots, snapshots will not be moved!\n"
2970 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
2971 $vmid, $storeid, $format, 1, $newvollist);
2973 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2975 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2977 # convert moved disk to base if part of template
2978 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
2979 if PVE
::QemuConfig-
>is_template($conf);
2981 PVE
::QemuConfig-
>write_config($vmid, $conf);
2983 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
2984 eval { PVE
::QemuServer
::vm_mon_cmd
($vmid, "guest-fstrim"); };
2988 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2989 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
2996 foreach my $volid (@$newvollist) {
2997 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3000 die "storage migration failed: $err";
3003 if ($param->{delete}) {
3005 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3006 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3012 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3015 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3018 __PACKAGE__-
>register_method({
3019 name
=> 'migrate_vm',
3020 path
=> '{vmid}/migrate',
3024 description
=> "Migrate virtual machine. Creates a new migration task.",
3026 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3029 additionalProperties
=> 0,
3031 node
=> get_standard_option
('pve-node'),
3032 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3033 target
=> get_standard_option
('pve-node', {
3034 description
=> "Target node.",
3035 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3039 description
=> "Use online/live migration.",
3044 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3049 enum
=> ['secure', 'insecure'],
3050 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3053 migration_network
=> {
3054 type
=> 'string', format
=> 'CIDR',
3055 description
=> "CIDR of the (sub) network that is used for migration.",
3058 "with-local-disks" => {
3060 description
=> "Enable live storage migration for local disk",
3063 targetstorage
=> get_standard_option
('pve-storage-id', {
3064 description
=> "Default target storage.",
3066 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3072 description
=> "the task ID.",
3077 my $rpcenv = PVE
::RPCEnvironment
::get
();
3079 my $authuser = $rpcenv->get_user();
3081 my $target = extract_param
($param, 'target');
3083 my $localnode = PVE
::INotify
::nodename
();
3084 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3086 PVE
::Cluster
::check_cfs_quorum
();
3088 PVE
::Cluster
::check_node_exists
($target);
3090 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3092 my $vmid = extract_param
($param, 'vmid');
3094 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3095 if !$param->{online
} && $param->{targetstorage
};
3097 raise_param_exc
({ force
=> "Only root may use this option." })
3098 if $param->{force
} && $authuser ne 'root@pam';
3100 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3101 if $param->{migration_type
} && $authuser ne 'root@pam';
3103 # allow root only until better network permissions are available
3104 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3105 if $param->{migration_network
} && $authuser ne 'root@pam';
3108 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3110 # try to detect errors early
3112 PVE
::QemuConfig-
>check_lock($conf);
3114 if (PVE
::QemuServer
::check_running
($vmid)) {
3115 die "cant migrate running VM without --online\n"
3116 if !$param->{online
};
3119 my $storecfg = PVE
::Storage
::config
();
3121 if( $param->{targetstorage
}) {
3122 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3124 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3127 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3132 my $service = "vm:$vmid";
3134 my $cmd = ['ha-manager', 'migrate', $service, $target];
3136 print "Requesting HA migration for VM $vmid to node $target\n";
3138 PVE
::Tools
::run_command
($cmd);
3143 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3148 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3152 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3155 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3160 __PACKAGE__-
>register_method({
3162 path
=> '{vmid}/monitor',
3166 description
=> "Execute Qemu monitor commands.",
3168 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3169 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3172 additionalProperties
=> 0,
3174 node
=> get_standard_option
('pve-node'),
3175 vmid
=> get_standard_option
('pve-vmid'),
3178 description
=> "The monitor command.",
3182 returns
=> { type
=> 'string'},
3186 my $rpcenv = PVE
::RPCEnvironment
::get
();
3187 my $authuser = $rpcenv->get_user();
3190 my $command = shift;
3191 return $command =~ m/^\s*info(\s+|$)/
3192 || $command =~ m/^\s*help\s*$/;
3195 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3196 if !&$is_ro($param->{command
});
3198 my $vmid = $param->{vmid
};
3200 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3204 $res = PVE
::QemuServer
::vm_human_monitor_command
($vmid, $param->{command
});
3206 $res = "ERROR: $@" if $@;
3211 __PACKAGE__-
>register_method({
3212 name
=> 'resize_vm',
3213 path
=> '{vmid}/resize',
3217 description
=> "Extend volume size.",
3219 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3222 additionalProperties
=> 0,
3224 node
=> get_standard_option
('pve-node'),
3225 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3226 skiplock
=> get_standard_option
('skiplock'),
3229 description
=> "The disk you want to resize.",
3230 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3234 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3235 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.",
3239 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3245 returns
=> { type
=> 'null'},
3249 my $rpcenv = PVE
::RPCEnvironment
::get
();
3251 my $authuser = $rpcenv->get_user();
3253 my $node = extract_param
($param, 'node');
3255 my $vmid = extract_param
($param, 'vmid');
3257 my $digest = extract_param
($param, 'digest');
3259 my $disk = extract_param
($param, 'disk');
3261 my $sizestr = extract_param
($param, 'size');
3263 my $skiplock = extract_param
($param, 'skiplock');
3264 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3265 if $skiplock && $authuser ne 'root@pam';
3267 my $storecfg = PVE
::Storage
::config
();
3269 my $updatefn = sub {
3271 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3273 die "checksum missmatch (file change by other user?)\n"
3274 if $digest && $digest ne $conf->{digest
};
3275 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3277 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3279 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3281 my (undef, undef, undef, undef, undef, undef, $format) =
3282 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3284 die "can't resize volume: $disk if snapshot exists\n"
3285 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3287 my $volid = $drive->{file
};
3289 die "disk '$disk' has no associated volume\n" if !$volid;
3291 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3293 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3295 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3297 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3298 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3300 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3301 my ($ext, $newsize, $unit) = ($1, $2, $4);
3304 $newsize = $newsize * 1024;
3305 } elsif ($unit eq 'M') {
3306 $newsize = $newsize * 1024 * 1024;
3307 } elsif ($unit eq 'G') {
3308 $newsize = $newsize * 1024 * 1024 * 1024;
3309 } elsif ($unit eq 'T') {
3310 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3313 $newsize += $size if $ext;
3314 $newsize = int($newsize);
3316 die "shrinking disks is not supported\n" if $newsize < $size;
3318 return if $size == $newsize;
3320 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3322 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3324 $drive->{size
} = $newsize;
3325 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3327 PVE
::QemuConfig-
>write_config($vmid, $conf);
3330 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3334 __PACKAGE__-
>register_method({
3335 name
=> 'snapshot_list',
3336 path
=> '{vmid}/snapshot',
3338 description
=> "List all snapshots.",
3340 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3343 protected
=> 1, # qemu pid files are only readable by root
3345 additionalProperties
=> 0,
3347 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3348 node
=> get_standard_option
('pve-node'),
3357 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3361 description
=> "Snapshot includes RAM.",
3366 description
=> "Snapshot description.",
3370 description
=> "Snapshot creation time",
3372 renderer
=> 'timestamp',
3376 description
=> "Parent snapshot identifier.",
3382 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3387 my $vmid = $param->{vmid
};
3389 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3390 my $snaphash = $conf->{snapshots
} || {};
3394 foreach my $name (keys %$snaphash) {
3395 my $d = $snaphash->{$name};
3398 snaptime
=> $d->{snaptime
} || 0,
3399 vmstate
=> $d->{vmstate
} ?
1 : 0,
3400 description
=> $d->{description
} || '',
3402 $item->{parent
} = $d->{parent
} if $d->{parent
};
3403 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3407 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3410 digest
=> $conf->{digest
},
3411 running
=> $running,
3412 description
=> "You are here!",
3414 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3416 push @$res, $current;
3421 __PACKAGE__-
>register_method({
3423 path
=> '{vmid}/snapshot',
3427 description
=> "Snapshot a VM.",
3429 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3432 additionalProperties
=> 0,
3434 node
=> get_standard_option
('pve-node'),
3435 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3436 snapname
=> get_standard_option
('pve-snapshot-name'),
3440 description
=> "Save the vmstate",
3445 description
=> "A textual description or comment.",
3451 description
=> "the task ID.",
3456 my $rpcenv = PVE
::RPCEnvironment
::get
();
3458 my $authuser = $rpcenv->get_user();
3460 my $node = extract_param
($param, 'node');
3462 my $vmid = extract_param
($param, 'vmid');
3464 my $snapname = extract_param
($param, 'snapname');
3466 die "unable to use snapshot name 'current' (reserved name)\n"
3467 if $snapname eq 'current';
3470 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3471 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3472 $param->{description
});
3475 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3478 __PACKAGE__-
>register_method({
3479 name
=> 'snapshot_cmd_idx',
3480 path
=> '{vmid}/snapshot/{snapname}',
3487 additionalProperties
=> 0,
3489 vmid
=> get_standard_option
('pve-vmid'),
3490 node
=> get_standard_option
('pve-node'),
3491 snapname
=> get_standard_option
('pve-snapshot-name'),
3500 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3507 push @$res, { cmd
=> 'rollback' };
3508 push @$res, { cmd
=> 'config' };
3513 __PACKAGE__-
>register_method({
3514 name
=> 'update_snapshot_config',
3515 path
=> '{vmid}/snapshot/{snapname}/config',
3519 description
=> "Update snapshot metadata.",
3521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3524 additionalProperties
=> 0,
3526 node
=> get_standard_option
('pve-node'),
3527 vmid
=> get_standard_option
('pve-vmid'),
3528 snapname
=> get_standard_option
('pve-snapshot-name'),
3532 description
=> "A textual description or comment.",
3536 returns
=> { type
=> 'null' },
3540 my $rpcenv = PVE
::RPCEnvironment
::get
();
3542 my $authuser = $rpcenv->get_user();
3544 my $vmid = extract_param
($param, 'vmid');
3546 my $snapname = extract_param
($param, 'snapname');
3548 return undef if !defined($param->{description
});
3550 my $updatefn = sub {
3552 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3554 PVE
::QemuConfig-
>check_lock($conf);
3556 my $snap = $conf->{snapshots
}->{$snapname};
3558 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3560 $snap->{description
} = $param->{description
} if defined($param->{description
});
3562 PVE
::QemuConfig-
>write_config($vmid, $conf);
3565 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3570 __PACKAGE__-
>register_method({
3571 name
=> 'get_snapshot_config',
3572 path
=> '{vmid}/snapshot/{snapname}/config',
3575 description
=> "Get snapshot configuration",
3577 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3580 additionalProperties
=> 0,
3582 node
=> get_standard_option
('pve-node'),
3583 vmid
=> get_standard_option
('pve-vmid'),
3584 snapname
=> get_standard_option
('pve-snapshot-name'),
3587 returns
=> { type
=> "object" },
3591 my $rpcenv = PVE
::RPCEnvironment
::get
();
3593 my $authuser = $rpcenv->get_user();
3595 my $vmid = extract_param
($param, 'vmid');
3597 my $snapname = extract_param
($param, 'snapname');
3599 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3601 my $snap = $conf->{snapshots
}->{$snapname};
3603 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3608 __PACKAGE__-
>register_method({
3610 path
=> '{vmid}/snapshot/{snapname}/rollback',
3614 description
=> "Rollback VM state to specified snapshot.",
3616 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3619 additionalProperties
=> 0,
3621 node
=> get_standard_option
('pve-node'),
3622 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3623 snapname
=> get_standard_option
('pve-snapshot-name'),
3628 description
=> "the task ID.",
3633 my $rpcenv = PVE
::RPCEnvironment
::get
();
3635 my $authuser = $rpcenv->get_user();
3637 my $node = extract_param
($param, 'node');
3639 my $vmid = extract_param
($param, 'vmid');
3641 my $snapname = extract_param
($param, 'snapname');
3644 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3645 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3649 # hold migration lock, this makes sure that nobody create replication snapshots
3650 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3653 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3656 __PACKAGE__-
>register_method({
3657 name
=> 'delsnapshot',
3658 path
=> '{vmid}/snapshot/{snapname}',
3662 description
=> "Delete a VM snapshot.",
3664 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3667 additionalProperties
=> 0,
3669 node
=> get_standard_option
('pve-node'),
3670 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3671 snapname
=> get_standard_option
('pve-snapshot-name'),
3675 description
=> "For removal from config file, even if removing disk snapshots fails.",
3681 description
=> "the task ID.",
3686 my $rpcenv = PVE
::RPCEnvironment
::get
();
3688 my $authuser = $rpcenv->get_user();
3690 my $node = extract_param
($param, 'node');
3692 my $vmid = extract_param
($param, 'vmid');
3694 my $snapname = extract_param
($param, 'snapname');
3697 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3698 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3701 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3704 __PACKAGE__-
>register_method({
3706 path
=> '{vmid}/template',
3710 description
=> "Create a Template.",
3712 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3713 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3716 additionalProperties
=> 0,
3718 node
=> get_standard_option
('pve-node'),
3719 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3723 description
=> "If you want to convert only 1 disk to base image.",
3724 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3729 returns
=> { type
=> 'null'},
3733 my $rpcenv = PVE
::RPCEnvironment
::get
();
3735 my $authuser = $rpcenv->get_user();
3737 my $node = extract_param
($param, 'node');
3739 my $vmid = extract_param
($param, 'vmid');
3741 my $disk = extract_param
($param, 'disk');
3743 my $updatefn = sub {
3745 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3747 PVE
::QemuConfig-
>check_lock($conf);
3749 die "unable to create template, because VM contains snapshots\n"
3750 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
3752 die "you can't convert a template to a template\n"
3753 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
3755 die "you can't convert a VM to template if VM is running\n"
3756 if PVE
::QemuServer
::check_running
($vmid);
3759 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
3762 $conf->{template
} = 1;
3763 PVE
::QemuConfig-
>write_config($vmid, $conf);
3765 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
3768 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);