1 package PVE
::API2
::Qemu
;
11 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
14 use PVE
::Tools
qw(extract_param);
15 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
17 use PVE
::JSONSchema
qw(get_standard_option);
19 use PVE
::ReplicationConfig
;
20 use PVE
::GuestHelpers
;
23 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
25 use PVE
::RPCEnvironment
;
26 use PVE
::AccessControl
;
30 use PVE
::API2
::Firewall
::VM
;
31 use PVE
::API2
::Qemu
::Agent
;
32 use PVE
::VZDump
::Plugin
;
33 use PVE
::DataCenterConfig
;
37 if (!$ENV{PVE_GENERATING_DOCS
}) {
38 require PVE
::HA
::Env
::PVE2
;
39 import PVE
::HA
::Env
::PVE2
;
40 require PVE
::HA
::Config
;
41 import PVE
::HA
::Config
;
45 use Data
::Dumper
; # fixme: remove
47 use base
qw(PVE::RESTHandler);
49 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
51 my $resolve_cdrom_alias = sub {
54 if (my $value = $param->{cdrom
}) {
55 $value .= ",media=cdrom" if $value !~ m/media=/;
56 $param->{ide2
} = $value;
57 delete $param->{cdrom
};
61 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
62 my $check_storage_access = sub {
63 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
65 PVE
::QemuServer
::foreach_drive
($settings, sub {
66 my ($ds, $drive) = @_;
68 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
70 my $volid = $drive->{file
};
71 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
73 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || $volname eq 'cloudinit')) {
75 } elsif ($isCDROM && ($volid eq 'cdrom')) {
76 $rpcenv->check($authuser, "/", ['Sys.Console']);
77 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
78 my ($storeid, $size) = ($2 || $default_storage, $3);
79 die "no storage ID specified (and no default storage)\n" if !$storeid;
80 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
81 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
82 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
83 if !$scfg->{content
}->{images
};
85 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
89 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
90 if defined($settings->{vmstatestorage
});
93 my $check_storage_access_clone = sub {
94 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
98 PVE
::QemuServer
::foreach_drive
($conf, sub {
99 my ($ds, $drive) = @_;
101 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
103 my $volid = $drive->{file
};
105 return if !$volid || $volid eq 'none';
108 if ($volid eq 'cdrom') {
109 $rpcenv->check($authuser, "/", ['Sys.Console']);
111 # we simply allow access
112 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
113 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
114 $sharedvm = 0 if !$scfg->{shared
};
118 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
119 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
120 $sharedvm = 0 if !$scfg->{shared
};
122 $sid = $storage if $storage;
123 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
127 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
128 if defined($conf->{vmstatestorage
});
133 # Note: $pool is only needed when creating a VM, because pool permissions
134 # are automatically inherited if VM already exists inside a pool.
135 my $create_disks = sub {
136 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
143 my ($ds, $disk) = @_;
145 my $volid = $disk->{file
};
146 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
148 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
149 delete $disk->{size
};
150 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
151 } elsif ($volname eq 'cloudinit') {
152 $storeid = $storeid // $default_storage;
153 die "no storage ID specified (and no default storage)\n" if !$storeid;
154 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
155 my $name = "vm-$vmid-cloudinit";
159 $fmt = $disk->{format
} // "qcow2";
162 $fmt = $disk->{format
} // "raw";
165 # Initial disk created with 4 MB and aligned to 4MB on regeneration
166 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
167 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
168 $disk->{file
} = $volid;
169 $disk->{media
} = 'cdrom';
170 push @$vollist, $volid;
171 delete $disk->{format
}; # no longer needed
172 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
173 } elsif ($volid =~ $NEW_DISK_RE) {
174 my ($storeid, $size) = ($2 || $default_storage, $3);
175 die "no storage ID specified (and no default storage)\n" if !$storeid;
176 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
177 my $fmt = $disk->{format
} || $defformat;
179 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
182 if ($ds eq 'efidisk0') {
183 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
185 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
187 push @$vollist, $volid;
188 $disk->{file
} = $volid;
189 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
190 delete $disk->{format
}; # no longer needed
191 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
194 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
196 my $volid_is_new = 1;
199 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
200 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
205 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
207 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
209 die "volume $volid does not exists\n" if !$size;
211 $disk->{size
} = $size;
214 $res->{$ds} = PVE
::QemuServer
::print_drive
($vmid, $disk);
218 eval { PVE
::QemuServer
::foreach_drive
($settings, $code); };
220 # free allocated images on error
222 syslog
('err', "VM $vmid creating disks failed");
223 foreach my $volid (@$vollist) {
224 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
230 # modify vm config if everything went well
231 foreach my $ds (keys %$res) {
232 $conf->{$ds} = $res->{$ds};
249 my $memoryoptions = {
255 my $hwtypeoptions = {
268 my $generaloptions = {
275 'migrate_downtime' => 1,
276 'migrate_speed' => 1,
289 my $vmpoweroptions = {
296 'vmstatestorage' => 1,
299 my $cloudinitoptions = {
309 my $check_vm_modify_config_perm = sub {
310 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
312 return 1 if $authuser eq 'root@pam';
314 foreach my $opt (@$key_list) {
315 # some checks (e.g., disk, serial port, usb) need to be done somewhere
316 # else, as there the permission can be value dependend
317 next if PVE
::QemuServer
::is_valid_drivename
($opt);
318 next if $opt eq 'cdrom';
319 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
322 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
323 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
324 } elsif ($memoryoptions->{$opt}) {
325 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
326 } elsif ($hwtypeoptions->{$opt}) {
327 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
328 } elsif ($generaloptions->{$opt}) {
329 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
330 # special case for startup since it changes host behaviour
331 if ($opt eq 'startup') {
332 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
334 } elsif ($vmpoweroptions->{$opt}) {
335 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
336 } elsif ($diskoptions->{$opt}) {
337 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
338 } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
339 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
341 # catches hostpci\d+, args, lock, etc.
342 # new options will be checked here
343 die "only root can set '$opt' config\n";
350 __PACKAGE__-
>register_method({
354 description
=> "Virtual machine index (per node).",
356 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
360 protected
=> 1, # qemu pid files are only readable by root
362 additionalProperties
=> 0,
364 node
=> get_standard_option
('pve-node'),
368 description
=> "Determine the full status of active VMs.",
376 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
378 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
383 my $rpcenv = PVE
::RPCEnvironment
::get
();
384 my $authuser = $rpcenv->get_user();
386 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
389 foreach my $vmid (keys %$vmstatus) {
390 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
392 my $data = $vmstatus->{$vmid};
401 __PACKAGE__-
>register_method({
405 description
=> "Create or restore a virtual machine.",
407 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
408 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
409 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
410 user
=> 'all', # check inside
415 additionalProperties
=> 0,
416 properties
=> PVE
::QemuServer
::json_config_properties
(
418 node
=> get_standard_option
('pve-node'),
419 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
421 description
=> "The backup file.",
425 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
427 storage
=> get_standard_option
('pve-storage-id', {
428 description
=> "Default storage.",
430 completion
=> \
&PVE
::QemuServer
::complete_storage
,
435 description
=> "Allow to overwrite existing VM.",
436 requires
=> 'archive',
441 description
=> "Assign a unique random ethernet address.",
442 requires
=> 'archive',
446 type
=> 'string', format
=> 'pve-poolid',
447 description
=> "Add the VM to the specified pool.",
450 description
=> "Override I/O bandwidth limit (in KiB/s).",
454 default => 'restore limit from datacenter or storage config',
460 description
=> "Start VM after it was created successfully.",
470 my $rpcenv = PVE
::RPCEnvironment
::get
();
471 my $authuser = $rpcenv->get_user();
473 my $node = extract_param
($param, 'node');
474 my $vmid = extract_param
($param, 'vmid');
476 my $archive = extract_param
($param, 'archive');
477 my $is_restore = !!$archive;
479 my $bwlimit = extract_param
($param, 'bwlimit');
480 my $force = extract_param
($param, 'force');
481 my $pool = extract_param
($param, 'pool');
482 my $start_after_create = extract_param
($param, 'start');
483 my $storage = extract_param
($param, 'storage');
484 my $unique = extract_param
($param, 'unique');
486 if (defined(my $ssh_keys = $param->{sshkeys
})) {
487 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
488 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
491 PVE
::Cluster
::check_cfs_quorum
();
493 my $filename = PVE
::QemuConfig-
>config_file($vmid);
494 my $storecfg = PVE
::Storage
::config
();
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);
558 PVE
::QemuServer
::restore_archive
($archive, $vmid, $authuser, {
564 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
565 # Convert restored VM to template if backup was VM template
566 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
567 warn "Convert to template.\n";
568 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
572 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
574 if ($start_after_create) {
575 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
580 # ensure no old replication state are exists
581 PVE
::ReplicationState
::delete_guest_states
($vmid);
583 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
587 # ensure no old replication state are exists
588 PVE
::ReplicationState
::delete_guest_states
($vmid);
596 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
600 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
602 if (!$conf->{bootdisk
}) {
603 my $firstdisk = PVE
::QemuServer
::resolve_first_disk
($conf);
604 $conf->{bootdisk
} = $firstdisk if $firstdisk;
607 # auto generate uuid if user did not specify smbios1 option
608 if (!$conf->{smbios1
}) {
609 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
612 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
613 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
616 PVE
::QemuConfig-
>write_config($vmid, $conf);
622 foreach my $volid (@$vollist) {
623 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
629 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
632 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
634 if ($start_after_create) {
635 print "Execute autostart\n";
636 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
641 my ($code, $worker_name);
643 $worker_name = 'qmrestore';
645 eval { $restorefn->() };
647 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
653 $worker_name = 'qmcreate';
655 eval { $createfn->() };
658 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
659 unlink($conffile) or die "failed to remove config file: $!\n";
667 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
670 __PACKAGE__-
>register_method({
675 description
=> "Directory index",
680 additionalProperties
=> 0,
682 node
=> get_standard_option
('pve-node'),
683 vmid
=> get_standard_option
('pve-vmid'),
691 subdir
=> { type
=> 'string' },
694 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
700 { subdir
=> 'config' },
701 { subdir
=> 'pending' },
702 { subdir
=> 'status' },
703 { subdir
=> 'unlink' },
704 { subdir
=> 'vncproxy' },
705 { subdir
=> 'termproxy' },
706 { subdir
=> 'migrate' },
707 { subdir
=> 'resize' },
708 { subdir
=> 'move' },
710 { subdir
=> 'rrddata' },
711 { subdir
=> 'monitor' },
712 { subdir
=> 'agent' },
713 { subdir
=> 'snapshot' },
714 { subdir
=> 'spiceproxy' },
715 { subdir
=> 'sendkey' },
716 { subdir
=> 'firewall' },
722 __PACKAGE__-
>register_method ({
723 subclass
=> "PVE::API2::Firewall::VM",
724 path
=> '{vmid}/firewall',
727 __PACKAGE__-
>register_method ({
728 subclass
=> "PVE::API2::Qemu::Agent",
729 path
=> '{vmid}/agent',
732 __PACKAGE__-
>register_method({
734 path
=> '{vmid}/rrd',
736 protected
=> 1, # fixme: can we avoid that?
738 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
740 description
=> "Read VM RRD statistics (returns PNG)",
742 additionalProperties
=> 0,
744 node
=> get_standard_option
('pve-node'),
745 vmid
=> get_standard_option
('pve-vmid'),
747 description
=> "Specify the time frame you are interested in.",
749 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
752 description
=> "The list of datasources you want to display.",
753 type
=> 'string', format
=> 'pve-configid-list',
756 description
=> "The RRD consolidation function",
758 enum
=> [ 'AVERAGE', 'MAX' ],
766 filename
=> { type
=> 'string' },
772 return PVE
::RRD
::create_rrd_graph
(
773 "pve2-vm/$param->{vmid}", $param->{timeframe
},
774 $param->{ds
}, $param->{cf
});
778 __PACKAGE__-
>register_method({
780 path
=> '{vmid}/rrddata',
782 protected
=> 1, # fixme: can we avoid that?
784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
786 description
=> "Read VM RRD statistics",
788 additionalProperties
=> 0,
790 node
=> get_standard_option
('pve-node'),
791 vmid
=> get_standard_option
('pve-vmid'),
793 description
=> "Specify the time frame you are interested in.",
795 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
798 description
=> "The RRD consolidation function",
800 enum
=> [ 'AVERAGE', 'MAX' ],
815 return PVE
::RRD
::create_rrd_data
(
816 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
820 __PACKAGE__-
>register_method({
822 path
=> '{vmid}/config',
825 description
=> "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
827 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
830 additionalProperties
=> 0,
832 node
=> get_standard_option
('pve-node'),
833 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
835 description
=> "Get current values (instead of pending values).",
840 snapshot
=> get_standard_option
('pve-snapshot-name', {
841 description
=> "Fetch config values from given snapshot.",
844 my ($cmd, $pname, $cur, $args) = @_;
845 PVE
::QemuConfig-
>snapshot_list($args->[0]);
851 description
=> "The current VM configuration.",
853 properties
=> PVE
::QemuServer
::json_config_properties
({
856 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
863 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
864 current
=> "cannot use 'snapshot' parameter with 'current'"})
865 if ($param->{snapshot
} && $param->{current
});
868 if ($param->{snapshot
}) {
869 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
871 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
873 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
878 __PACKAGE__-
>register_method({
879 name
=> 'vm_pending',
880 path
=> '{vmid}/pending',
883 description
=> "Get virtual machine configuration, including pending changes.",
885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
888 additionalProperties
=> 0,
890 node
=> get_standard_option
('pve-node'),
891 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
900 description
=> "Configuration option name.",
904 description
=> "Current value.",
909 description
=> "Pending value.",
914 description
=> "Indicates a pending delete request if present and not 0. " .
915 "The value 2 indicates a force-delete request.",
927 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
929 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
931 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
932 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
934 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
937 # POST/PUT {vmid}/config implementation
939 # The original API used PUT (idempotent) an we assumed that all operations
940 # are fast. But it turned out that almost any configuration change can
941 # involve hot-plug actions, or disk alloc/free. Such actions can take long
942 # time to complete and have side effects (not idempotent).
944 # The new implementation uses POST and forks a worker process. We added
945 # a new option 'background_delay'. If specified we wait up to
946 # 'background_delay' second for the worker task to complete. It returns null
947 # if the task is finished within that time, else we return the UPID.
949 my $update_vm_api = sub {
950 my ($param, $sync) = @_;
952 my $rpcenv = PVE
::RPCEnvironment
::get
();
954 my $authuser = $rpcenv->get_user();
956 my $node = extract_param
($param, 'node');
958 my $vmid = extract_param
($param, 'vmid');
960 my $digest = extract_param
($param, 'digest');
962 my $background_delay = extract_param
($param, 'background_delay');
964 if (defined(my $cipassword = $param->{cipassword
})) {
965 # Same logic as in cloud-init (but with the regex fixed...)
966 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
967 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
970 my @paramarr = (); # used for log message
971 foreach my $key (sort keys %$param) {
972 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
973 push @paramarr, "-$key", $value;
976 my $skiplock = extract_param
($param, 'skiplock');
977 raise_param_exc
({ skiplock
=> "Only root may use this option." })
978 if $skiplock && $authuser ne 'root@pam';
980 my $delete_str = extract_param
($param, 'delete');
982 my $revert_str = extract_param
($param, 'revert');
984 my $force = extract_param
($param, 'force');
986 if (defined(my $ssh_keys = $param->{sshkeys
})) {
987 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
988 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
991 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
993 my $storecfg = PVE
::Storage
::config
();
995 my $defaults = PVE
::QemuServer
::load_defaults
();
997 &$resolve_cdrom_alias($param);
999 # now try to verify all parameters
1002 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1003 if (!PVE
::QemuServer
::option_exists
($opt)) {
1004 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1007 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1008 "-revert $opt' at the same time" })
1009 if defined($param->{$opt});
1011 $revert->{$opt} = 1;
1015 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1016 $opt = 'ide2' if $opt eq 'cdrom';
1018 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1019 "-delete $opt' at the same time" })
1020 if defined($param->{$opt});
1022 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1023 "-revert $opt' at the same time" })
1026 if (!PVE
::QemuServer
::option_exists
($opt)) {
1027 raise_param_exc
({ delete => "unknown option '$opt'" });
1033 my $repl_conf = PVE
::ReplicationConfig-
>new();
1034 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1035 my $check_replication = sub {
1037 return if !$is_replicated;
1038 my $volid = $drive->{file
};
1039 return if !$volid || !($drive->{replicate
}//1);
1040 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1042 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1043 return if $volname eq 'cloudinit';
1046 if ($volid =~ $NEW_DISK_RE) {
1048 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1050 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1052 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1053 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1054 return if $scfg->{shared
};
1055 die "cannot add non-replicatable volume to a replicated VM\n";
1058 foreach my $opt (keys %$param) {
1059 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1060 # cleanup drive path
1061 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1062 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1063 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1064 $check_replication->($drive);
1065 $param->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $drive);
1066 } elsif ($opt =~ m/^net(\d+)$/) {
1068 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1069 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1070 } elsif ($opt eq 'vmgenid') {
1071 if ($param->{$opt} eq '1') {
1072 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1074 } elsif ($opt eq 'hookscript') {
1075 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1076 raise_param_exc
({ $opt => $@ }) if $@;
1080 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1082 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1084 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1086 my $updatefn = sub {
1088 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1090 die "checksum missmatch (file change by other user?)\n"
1091 if $digest && $digest ne $conf->{digest
};
1093 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1095 foreach my $opt (keys %$revert) {
1096 if (defined($conf->{$opt})) {
1097 $param->{$opt} = $conf->{$opt};
1098 } elsif (defined($conf->{pending
}->{$opt})) {
1103 if ($param->{memory
} || defined($param->{balloon
})) {
1104 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1105 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1107 die "balloon value too large (must be smaller than assigned memory)\n"
1108 if $balloon && $balloon > $maxmem;
1111 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1115 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1117 # write updates to pending section
1119 my $modified = {}; # record what $option we modify
1121 foreach my $opt (@delete) {
1122 $modified->{$opt} = 1;
1123 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1125 # value of what we want to delete, independent if pending or not
1126 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1127 if (!defined($val)) {
1128 warn "cannot delete '$opt' - not set in current configuration!\n";
1129 $modified->{$opt} = 0;
1132 my $is_pending_val = defined($conf->{pending
}->{$opt});
1133 delete $conf->{pending
}->{$opt};
1135 if ($opt =~ m/^unused/) {
1136 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1137 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1138 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1139 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1140 delete $conf->{$opt};
1141 PVE
::QemuConfig-
>write_config($vmid, $conf);
1143 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1144 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1145 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1146 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1148 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1149 PVE
::QemuConfig-
>write_config($vmid, $conf);
1150 } elsif ($opt =~ m/^serial\d+$/) {
1151 if ($val eq 'socket') {
1152 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1153 } elsif ($authuser ne 'root@pam') {
1154 die "only root can delete '$opt' config for real devices\n";
1156 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1157 PVE
::QemuConfig-
>write_config($vmid, $conf);
1158 } elsif ($opt =~ m/^usb\d+$/) {
1159 if ($val =~ m/spice/) {
1160 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1161 } elsif ($authuser ne 'root@pam') {
1162 die "only root can delete '$opt' config for real devices\n";
1164 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1165 PVE
::QemuConfig-
>write_config($vmid, $conf);
1167 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1168 PVE
::QemuConfig-
>write_config($vmid, $conf);
1172 foreach my $opt (keys %$param) { # add/change
1173 $modified->{$opt} = 1;
1174 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1175 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1177 my ($arch, undef) = PVE
::QemuServer
::get_basic_machine_info
($conf);
1179 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1180 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1181 # FIXME: cloudinit: CDROM or Disk?
1182 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1183 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1185 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1187 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1188 if defined($conf->{pending
}->{$opt});
1190 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1191 } elsif ($opt =~ m/^serial\d+/) {
1192 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1193 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1194 } elsif ($authuser ne 'root@pam') {
1195 die "only root can modify '$opt' config for real devices\n";
1197 $conf->{pending
}->{$opt} = $param->{$opt};
1198 } elsif ($opt =~ m/^usb\d+/) {
1199 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1200 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1201 } elsif ($authuser ne 'root@pam') {
1202 die "only root can modify '$opt' config for real devices\n";
1204 $conf->{pending
}->{$opt} = $param->{$opt};
1206 $conf->{pending
}->{$opt} = $param->{$opt};
1208 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1209 PVE
::QemuConfig-
>write_config($vmid, $conf);
1212 # remove pending changes when nothing changed
1213 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1214 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1215 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1217 return if !scalar(keys %{$conf->{pending
}});
1219 my $running = PVE
::QemuServer
::check_running
($vmid);
1221 # apply pending changes
1223 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1227 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1228 raise_param_exc
($errors) if scalar(keys %$errors);
1230 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running);
1240 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1242 if ($background_delay) {
1244 # Note: It would be better to do that in the Event based HTTPServer
1245 # to avoid blocking call to sleep.
1247 my $end_time = time() + $background_delay;
1249 my $task = PVE
::Tools
::upid_decode
($upid);
1252 while (time() < $end_time) {
1253 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1255 sleep(1); # this gets interrupted when child process ends
1259 my $status = PVE
::Tools
::upid_read_status
($upid);
1260 return undef if $status eq 'OK';
1269 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1272 my $vm_config_perm_list = [
1277 'VM.Config.Network',
1279 'VM.Config.Options',
1282 __PACKAGE__-
>register_method({
1283 name
=> 'update_vm_async',
1284 path
=> '{vmid}/config',
1288 description
=> "Set virtual machine options (asynchrounous API).",
1290 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1293 additionalProperties
=> 0,
1294 properties
=> PVE
::QemuServer
::json_config_properties
(
1296 node
=> get_standard_option
('pve-node'),
1297 vmid
=> get_standard_option
('pve-vmid'),
1298 skiplock
=> get_standard_option
('skiplock'),
1300 type
=> 'string', format
=> 'pve-configid-list',
1301 description
=> "A list of settings you want to delete.",
1305 type
=> 'string', format
=> 'pve-configid-list',
1306 description
=> "Revert a pending change.",
1311 description
=> $opt_force_description,
1313 requires
=> 'delete',
1317 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1321 background_delay
=> {
1323 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1334 code
=> $update_vm_api,
1337 __PACKAGE__-
>register_method({
1338 name
=> 'update_vm',
1339 path
=> '{vmid}/config',
1343 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1345 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1348 additionalProperties
=> 0,
1349 properties
=> PVE
::QemuServer
::json_config_properties
(
1351 node
=> get_standard_option
('pve-node'),
1352 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1353 skiplock
=> get_standard_option
('skiplock'),
1355 type
=> 'string', format
=> 'pve-configid-list',
1356 description
=> "A list of settings you want to delete.",
1360 type
=> 'string', format
=> 'pve-configid-list',
1361 description
=> "Revert a pending change.",
1366 description
=> $opt_force_description,
1368 requires
=> 'delete',
1372 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1378 returns
=> { type
=> 'null' },
1381 &$update_vm_api($param, 1);
1386 __PACKAGE__-
>register_method({
1387 name
=> 'destroy_vm',
1392 description
=> "Destroy the vm (also delete all used/owned volumes).",
1394 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1397 additionalProperties
=> 0,
1399 node
=> get_standard_option
('pve-node'),
1400 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1401 skiplock
=> get_standard_option
('skiplock'),
1404 description
=> "Remove vmid from backup cron jobs.",
1415 my $rpcenv = PVE
::RPCEnvironment
::get
();
1416 my $authuser = $rpcenv->get_user();
1417 my $vmid = $param->{vmid
};
1419 my $skiplock = $param->{skiplock
};
1420 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1421 if $skiplock && $authuser ne 'root@pam';
1424 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1425 my $storecfg = PVE
::Storage
::config
();
1426 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1427 die "unable to remove VM $vmid - used in HA resources\n"
1428 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1430 if (!$param->{purge
}) {
1431 # don't allow destroy if with replication jobs but no purge param
1432 my $repl_conf = PVE
::ReplicationConfig-
>new();
1433 $repl_conf->check_for_existing_jobs($vmid);
1436 # early tests (repeat after locking)
1437 die "VM $vmid is running - destroy failed\n"
1438 if PVE
::QemuServer
::check_running
($vmid);
1443 syslog
('info', "destroy VM $vmid: $upid\n");
1444 PVE
::QemuConfig-
>lock_config($vmid, sub {
1445 die "VM $vmid is running - destroy failed\n"
1446 if (PVE
::QemuServer
::check_running
($vmid));
1448 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1450 PVE
::AccessControl
::remove_vm_access
($vmid);
1451 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1452 if ($param->{purge
}) {
1453 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1454 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1457 # only now remove the zombie config, else we can have reuse race
1458 PVE
::QemuConfig-
>destroy_config($vmid);
1462 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1465 __PACKAGE__-
>register_method({
1467 path
=> '{vmid}/unlink',
1471 description
=> "Unlink/delete disk images.",
1473 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1476 additionalProperties
=> 0,
1478 node
=> get_standard_option
('pve-node'),
1479 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1481 type
=> 'string', format
=> 'pve-configid-list',
1482 description
=> "A list of disk IDs you want to delete.",
1486 description
=> $opt_force_description,
1491 returns
=> { type
=> 'null'},
1495 $param->{delete} = extract_param
($param, 'idlist');
1497 __PACKAGE__-
>update_vm($param);
1504 __PACKAGE__-
>register_method({
1506 path
=> '{vmid}/vncproxy',
1510 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1512 description
=> "Creates a TCP VNC proxy connections.",
1514 additionalProperties
=> 0,
1516 node
=> get_standard_option
('pve-node'),
1517 vmid
=> get_standard_option
('pve-vmid'),
1521 description
=> "starts websockify instead of vncproxy",
1526 additionalProperties
=> 0,
1528 user
=> { type
=> 'string' },
1529 ticket
=> { type
=> 'string' },
1530 cert
=> { type
=> 'string' },
1531 port
=> { type
=> 'integer' },
1532 upid
=> { type
=> 'string' },
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1540 my $authuser = $rpcenv->get_user();
1542 my $vmid = $param->{vmid
};
1543 my $node = $param->{node
};
1544 my $websocket = $param->{websocket
};
1546 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1547 my $use_serial = ($conf->{vga
} && ($conf->{vga
} =~ m/^serial\d+$/));
1549 my $authpath = "/vms/$vmid";
1551 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1553 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1559 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1560 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1561 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1562 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1563 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, $use_serial ?
'-t' : '-T');
1565 $family = PVE
::Tools
::get_host_address_family
($node);
1568 my $port = PVE
::Tools
::next_vnc_port
($family);
1575 syslog
('info', "starting vnc proxy $upid\n");
1581 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga
}, '-escape', '0' ];
1583 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1584 '-timeout', $timeout, '-authpath', $authpath,
1585 '-perm', 'Sys.Console'];
1587 if ($param->{websocket
}) {
1588 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1589 push @$cmd, '-notls', '-listen', 'localhost';
1592 push @$cmd, '-c', @$remcmd, @$termcmd;
1594 PVE
::Tools
::run_command
($cmd);
1598 $ENV{LC_PVE_TICKET
} = $ticket if $websocket; # set ticket with "qm vncproxy"
1600 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1602 my $sock = IO
::Socket
::IP-
>new(
1607 GetAddrInfoFlags
=> 0,
1608 ) or die "failed to create socket: $!\n";
1609 # Inside the worker we shouldn't have any previous alarms
1610 # running anyway...:
1612 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1614 accept(my $cli, $sock) or die "connection failed: $!\n";
1617 if (PVE
::Tools
::run_command
($cmd,
1618 output
=> '>&'.fileno($cli),
1619 input
=> '<&'.fileno($cli),
1622 die "Failed to run vncproxy.\n";
1629 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1631 PVE
::Tools
::wait_for_vnc_port
($port);
1642 __PACKAGE__-
>register_method({
1643 name
=> 'termproxy',
1644 path
=> '{vmid}/termproxy',
1648 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1650 description
=> "Creates a TCP proxy connections.",
1652 additionalProperties
=> 0,
1654 node
=> get_standard_option
('pve-node'),
1655 vmid
=> get_standard_option
('pve-vmid'),
1659 enum
=> [qw(serial0 serial1 serial2 serial3)],
1660 description
=> "opens a serial terminal (defaults to display)",
1665 additionalProperties
=> 0,
1667 user
=> { type
=> 'string' },
1668 ticket
=> { type
=> 'string' },
1669 port
=> { type
=> 'integer' },
1670 upid
=> { type
=> 'string' },
1676 my $rpcenv = PVE
::RPCEnvironment
::get
();
1678 my $authuser = $rpcenv->get_user();
1680 my $vmid = $param->{vmid
};
1681 my $node = $param->{node
};
1682 my $serial = $param->{serial
};
1684 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1686 if (!defined($serial)) {
1687 if ($conf->{vga
} && $conf->{vga
} =~ m/^serial\d+$/) {
1688 $serial = $conf->{vga
};
1692 my $authpath = "/vms/$vmid";
1694 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1699 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1700 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1701 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1702 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1703 push @$remcmd, '--';
1705 $family = PVE
::Tools
::get_host_address_family
($node);
1708 my $port = PVE
::Tools
::next_vnc_port
($family);
1710 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1711 push @$termcmd, '-iface', $serial if $serial;
1716 syslog
('info', "starting qemu termproxy $upid\n");
1718 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1719 '--perm', 'VM.Console', '--'];
1720 push @$cmd, @$remcmd, @$termcmd;
1722 PVE
::Tools
::run_command
($cmd);
1725 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1727 PVE
::Tools
::wait_for_vnc_port
($port);
1737 __PACKAGE__-
>register_method({
1738 name
=> 'vncwebsocket',
1739 path
=> '{vmid}/vncwebsocket',
1742 description
=> "You also need to pass a valid ticket (vncticket).",
1743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1745 description
=> "Opens a weksocket for VNC traffic.",
1747 additionalProperties
=> 0,
1749 node
=> get_standard_option
('pve-node'),
1750 vmid
=> get_standard_option
('pve-vmid'),
1752 description
=> "Ticket from previous call to vncproxy.",
1757 description
=> "Port number returned by previous vncproxy call.",
1767 port
=> { type
=> 'string' },
1773 my $rpcenv = PVE
::RPCEnvironment
::get
();
1775 my $authuser = $rpcenv->get_user();
1777 my $vmid = $param->{vmid
};
1778 my $node = $param->{node
};
1780 my $authpath = "/vms/$vmid";
1782 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1784 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1786 # Note: VNC ports are acessible from outside, so we do not gain any
1787 # security if we verify that $param->{port} belongs to VM $vmid. This
1788 # check is done by verifying the VNC ticket (inside VNC protocol).
1790 my $port = $param->{port
};
1792 return { port
=> $port };
1795 __PACKAGE__-
>register_method({
1796 name
=> 'spiceproxy',
1797 path
=> '{vmid}/spiceproxy',
1802 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1804 description
=> "Returns a SPICE configuration to connect to the VM.",
1806 additionalProperties
=> 0,
1808 node
=> get_standard_option
('pve-node'),
1809 vmid
=> get_standard_option
('pve-vmid'),
1810 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1813 returns
=> get_standard_option
('remote-viewer-config'),
1817 my $rpcenv = PVE
::RPCEnvironment
::get
();
1819 my $authuser = $rpcenv->get_user();
1821 my $vmid = $param->{vmid
};
1822 my $node = $param->{node
};
1823 my $proxy = $param->{proxy
};
1825 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1826 my $title = "VM $vmid";
1827 $title .= " - ". $conf->{name
} if $conf->{name
};
1829 my $port = PVE
::QemuServer
::spice_port
($vmid);
1831 my ($ticket, undef, $remote_viewer_config) =
1832 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
1834 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
1835 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
1837 return $remote_viewer_config;
1840 __PACKAGE__-
>register_method({
1842 path
=> '{vmid}/status',
1845 description
=> "Directory index",
1850 additionalProperties
=> 0,
1852 node
=> get_standard_option
('pve-node'),
1853 vmid
=> get_standard_option
('pve-vmid'),
1861 subdir
=> { type
=> 'string' },
1864 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
1870 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1873 { subdir
=> 'current' },
1874 { subdir
=> 'start' },
1875 { subdir
=> 'stop' },
1876 { subdir
=> 'reset' },
1877 { subdir
=> 'shutdown' },
1878 { subdir
=> 'suspend' },
1879 { subdir
=> 'reboot' },
1885 __PACKAGE__-
>register_method({
1886 name
=> 'vm_status',
1887 path
=> '{vmid}/status/current',
1890 protected
=> 1, # qemu pid files are only readable by root
1891 description
=> "Get virtual machine status.",
1893 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1896 additionalProperties
=> 0,
1898 node
=> get_standard_option
('pve-node'),
1899 vmid
=> get_standard_option
('pve-vmid'),
1905 %$PVE::QemuServer
::vmstatus_return_properties
,
1907 description
=> "HA manager service status.",
1911 description
=> "Qemu VGA configuration supports spice.",
1916 description
=> "Qemu GuestAgent enabled in config.",
1926 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
1928 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
1929 my $status = $vmstatus->{$param->{vmid
}};
1931 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
1933 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
1934 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
1939 __PACKAGE__-
>register_method({
1941 path
=> '{vmid}/status/start',
1945 description
=> "Start virtual machine.",
1947 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1950 additionalProperties
=> 0,
1952 node
=> get_standard_option
('pve-node'),
1953 vmid
=> get_standard_option
('pve-vmid',
1954 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1955 skiplock
=> get_standard_option
('skiplock'),
1956 stateuri
=> get_standard_option
('pve-qm-stateuri'),
1957 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
1960 enum
=> ['secure', 'insecure'],
1961 description
=> "Migration traffic is encrypted using an SSH " .
1962 "tunnel by default. On secure, completely private networks " .
1963 "this can be disabled to increase performance.",
1966 migration_network
=> {
1967 type
=> 'string', format
=> 'CIDR',
1968 description
=> "CIDR of the (sub) network that is used for migration.",
1971 machine
=> get_standard_option
('pve-qm-machine'),
1973 description
=> "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
1985 my $rpcenv = PVE
::RPCEnvironment
::get
();
1986 my $authuser = $rpcenv->get_user();
1988 my $node = extract_param
($param, 'node');
1989 my $vmid = extract_param
($param, 'vmid');
1991 my $machine = extract_param
($param, 'machine');
1993 my $get_root_param = sub {
1994 my $value = extract_param
($param, $_[0]);
1995 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
1996 if $value && $authuser ne 'root@pam';
2000 my $stateuri = $get_root_param->('stateuri');
2001 my $skiplock = $get_root_param->('skiplock');
2002 my $migratedfrom = $get_root_param->('migratedfrom');
2003 my $migration_type = $get_root_param->('migration_type');
2004 my $migration_network = $get_root_param->('migration_network');
2005 my $targetstorage = $get_root_param->('targetstorage');
2007 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2008 if $targetstorage && !$migratedfrom;
2010 # read spice ticket from STDIN
2012 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2013 if (defined(my $line = <STDIN
>)) {
2015 $spice_ticket = $line;
2019 PVE
::Cluster
::check_cfs_quorum
();
2021 my $storecfg = PVE
::Storage
::config
();
2023 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2027 print "Requesting HA start for VM $vmid\n";
2029 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2030 PVE
::Tools
::run_command
($cmd);
2034 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2041 syslog
('info', "start VM $vmid: $upid\n");
2043 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2044 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
2048 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2052 __PACKAGE__-
>register_method({
2054 path
=> '{vmid}/status/stop',
2058 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2059 "is akin to pulling the power plug of a running computer and may damage the VM data",
2061 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2064 additionalProperties
=> 0,
2066 node
=> get_standard_option
('pve-node'),
2067 vmid
=> get_standard_option
('pve-vmid',
2068 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2069 skiplock
=> get_standard_option
('skiplock'),
2070 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2072 description
=> "Wait maximal timeout seconds.",
2078 description
=> "Do not deactivate storage volumes.",
2091 my $rpcenv = PVE
::RPCEnvironment
::get
();
2092 my $authuser = $rpcenv->get_user();
2094 my $node = extract_param
($param, 'node');
2095 my $vmid = extract_param
($param, 'vmid');
2097 my $skiplock = extract_param
($param, 'skiplock');
2098 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2099 if $skiplock && $authuser ne 'root@pam';
2101 my $keepActive = extract_param
($param, 'keepActive');
2102 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2103 if $keepActive && $authuser ne 'root@pam';
2105 my $migratedfrom = extract_param
($param, 'migratedfrom');
2106 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2107 if $migratedfrom && $authuser ne 'root@pam';
2110 my $storecfg = PVE
::Storage
::config
();
2112 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2117 print "Requesting HA stop for VM $vmid\n";
2119 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2120 PVE
::Tools
::run_command
($cmd);
2124 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2130 syslog
('info', "stop VM $vmid: $upid\n");
2132 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2133 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2137 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2141 __PACKAGE__-
>register_method({
2143 path
=> '{vmid}/status/reset',
2147 description
=> "Reset virtual machine.",
2149 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2152 additionalProperties
=> 0,
2154 node
=> get_standard_option
('pve-node'),
2155 vmid
=> get_standard_option
('pve-vmid',
2156 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2157 skiplock
=> get_standard_option
('skiplock'),
2166 my $rpcenv = PVE
::RPCEnvironment
::get
();
2168 my $authuser = $rpcenv->get_user();
2170 my $node = extract_param
($param, 'node');
2172 my $vmid = extract_param
($param, 'vmid');
2174 my $skiplock = extract_param
($param, 'skiplock');
2175 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2176 if $skiplock && $authuser ne 'root@pam';
2178 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2183 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2188 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2191 __PACKAGE__-
>register_method({
2192 name
=> 'vm_shutdown',
2193 path
=> '{vmid}/status/shutdown',
2197 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2198 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2200 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2203 additionalProperties
=> 0,
2205 node
=> get_standard_option
('pve-node'),
2206 vmid
=> get_standard_option
('pve-vmid',
2207 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2208 skiplock
=> get_standard_option
('skiplock'),
2210 description
=> "Wait maximal timeout seconds.",
2216 description
=> "Make sure the VM stops.",
2222 description
=> "Do not deactivate storage volumes.",
2235 my $rpcenv = PVE
::RPCEnvironment
::get
();
2236 my $authuser = $rpcenv->get_user();
2238 my $node = extract_param
($param, 'node');
2239 my $vmid = extract_param
($param, 'vmid');
2241 my $skiplock = extract_param
($param, 'skiplock');
2242 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2243 if $skiplock && $authuser ne 'root@pam';
2245 my $keepActive = extract_param
($param, 'keepActive');
2246 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2247 if $keepActive && $authuser ne 'root@pam';
2249 my $storecfg = PVE
::Storage
::config
();
2253 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2254 # otherwise, we will infer a shutdown command, but run into the timeout,
2255 # then when the vm is resumed, it will instantly shutdown
2257 # checking the qmp status here to get feedback to the gui/cli/api
2258 # and the status query should not take too long
2259 my $qmpstatus = eval {
2260 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2261 mon_cmd
($vmid, "query-status");
2265 if (!$err && $qmpstatus->{status
} eq "paused") {
2266 if ($param->{forceStop
}) {
2267 warn "VM is paused - stop instead of shutdown\n";
2270 die "VM is paused - cannot shutdown\n";
2274 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2276 my $timeout = $param->{timeout
} // 60;
2280 print "Requesting HA stop for VM $vmid\n";
2282 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2283 PVE
::Tools
::run_command
($cmd);
2287 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2294 syslog
('info', "shutdown VM $vmid: $upid\n");
2296 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2297 $shutdown, $param->{forceStop
}, $keepActive);
2301 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2305 __PACKAGE__-
>register_method({
2306 name
=> 'vm_reboot',
2307 path
=> '{vmid}/status/reboot',
2311 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2313 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2316 additionalProperties
=> 0,
2318 node
=> get_standard_option
('pve-node'),
2319 vmid
=> get_standard_option
('pve-vmid',
2320 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2322 description
=> "Wait maximal timeout seconds for the shutdown.",
2335 my $rpcenv = PVE
::RPCEnvironment
::get
();
2336 my $authuser = $rpcenv->get_user();
2338 my $node = extract_param
($param, 'node');
2339 my $vmid = extract_param
($param, 'vmid');
2341 my $qmpstatus = eval {
2342 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2343 mon_cmd
($vmid, "query-status");
2347 if (!$err && $qmpstatus->{status
} eq "paused") {
2348 die "VM is paused - cannot shutdown\n";
2351 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2356 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2357 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2361 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2364 __PACKAGE__-
>register_method({
2365 name
=> 'vm_suspend',
2366 path
=> '{vmid}/status/suspend',
2370 description
=> "Suspend virtual machine.",
2372 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2375 additionalProperties
=> 0,
2377 node
=> get_standard_option
('pve-node'),
2378 vmid
=> get_standard_option
('pve-vmid',
2379 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2380 skiplock
=> get_standard_option
('skiplock'),
2385 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2387 statestorage
=> get_standard_option
('pve-storage-id', {
2388 description
=> "The storage for the VM state",
2389 requires
=> 'todisk',
2391 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2401 my $rpcenv = PVE
::RPCEnvironment
::get
();
2402 my $authuser = $rpcenv->get_user();
2404 my $node = extract_param
($param, 'node');
2405 my $vmid = extract_param
($param, 'vmid');
2407 my $todisk = extract_param
($param, 'todisk') // 0;
2409 my $statestorage = extract_param
($param, 'statestorage');
2411 my $skiplock = extract_param
($param, 'skiplock');
2412 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2413 if $skiplock && $authuser ne 'root@pam';
2415 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2417 die "Cannot suspend HA managed VM to disk\n"
2418 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2423 syslog
('info', "suspend VM $vmid: $upid\n");
2425 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2430 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2431 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2434 __PACKAGE__-
>register_method({
2435 name
=> 'vm_resume',
2436 path
=> '{vmid}/status/resume',
2440 description
=> "Resume virtual machine.",
2442 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2445 additionalProperties
=> 0,
2447 node
=> get_standard_option
('pve-node'),
2448 vmid
=> get_standard_option
('pve-vmid',
2449 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2450 skiplock
=> get_standard_option
('skiplock'),
2451 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2461 my $rpcenv = PVE
::RPCEnvironment
::get
();
2463 my $authuser = $rpcenv->get_user();
2465 my $node = extract_param
($param, 'node');
2467 my $vmid = extract_param
($param, 'vmid');
2469 my $skiplock = extract_param
($param, 'skiplock');
2470 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2471 if $skiplock && $authuser ne 'root@pam';
2473 my $nocheck = extract_param
($param, 'nocheck');
2475 my $to_disk_suspended;
2477 PVE
::QemuConfig-
>lock_config($vmid, sub {
2478 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2479 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2483 die "VM $vmid not running\n"
2484 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2489 syslog
('info', "resume VM $vmid: $upid\n");
2491 if (!$to_disk_suspended) {
2492 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2494 my $storecfg = PVE
::Storage
::config
();
2495 PVE
::QemuServer
::vm_start
($storecfg, $vmid, undef, $skiplock);
2501 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2504 __PACKAGE__-
>register_method({
2505 name
=> 'vm_sendkey',
2506 path
=> '{vmid}/sendkey',
2510 description
=> "Send key event to virtual machine.",
2512 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2515 additionalProperties
=> 0,
2517 node
=> get_standard_option
('pve-node'),
2518 vmid
=> get_standard_option
('pve-vmid',
2519 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2520 skiplock
=> get_standard_option
('skiplock'),
2522 description
=> "The key (qemu monitor encoding).",
2527 returns
=> { type
=> 'null'},
2531 my $rpcenv = PVE
::RPCEnvironment
::get
();
2533 my $authuser = $rpcenv->get_user();
2535 my $node = extract_param
($param, 'node');
2537 my $vmid = extract_param
($param, 'vmid');
2539 my $skiplock = extract_param
($param, 'skiplock');
2540 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2541 if $skiplock && $authuser ne 'root@pam';
2543 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2548 __PACKAGE__-
>register_method({
2549 name
=> 'vm_feature',
2550 path
=> '{vmid}/feature',
2554 description
=> "Check if feature for virtual machine is available.",
2556 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2559 additionalProperties
=> 0,
2561 node
=> get_standard_option
('pve-node'),
2562 vmid
=> get_standard_option
('pve-vmid'),
2564 description
=> "Feature to check.",
2566 enum
=> [ 'snapshot', 'clone', 'copy' ],
2568 snapname
=> get_standard_option
('pve-snapshot-name', {
2576 hasFeature
=> { type
=> 'boolean' },
2579 items
=> { type
=> 'string' },
2586 my $node = extract_param
($param, 'node');
2588 my $vmid = extract_param
($param, 'vmid');
2590 my $snapname = extract_param
($param, 'snapname');
2592 my $feature = extract_param
($param, 'feature');
2594 my $running = PVE
::QemuServer
::check_running
($vmid);
2596 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2599 my $snap = $conf->{snapshots
}->{$snapname};
2600 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2603 my $storecfg = PVE
::Storage
::config
();
2605 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2606 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2609 hasFeature
=> $hasFeature,
2610 nodes
=> [ keys %$nodelist ],
2614 __PACKAGE__-
>register_method({
2616 path
=> '{vmid}/clone',
2620 description
=> "Create a copy of virtual machine/template.",
2622 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2623 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2624 "'Datastore.AllocateSpace' on any used storage.",
2627 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2629 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2630 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2635 additionalProperties
=> 0,
2637 node
=> get_standard_option
('pve-node'),
2638 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2639 newid
=> get_standard_option
('pve-vmid', {
2640 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2641 description
=> 'VMID for the clone.' }),
2644 type
=> 'string', format
=> 'dns-name',
2645 description
=> "Set a name for the new VM.",
2650 description
=> "Description for the new VM.",
2654 type
=> 'string', format
=> 'pve-poolid',
2655 description
=> "Add the new VM to the specified pool.",
2657 snapname
=> get_standard_option
('pve-snapshot-name', {
2660 storage
=> get_standard_option
('pve-storage-id', {
2661 description
=> "Target storage for full clone.",
2665 description
=> "Target format for file storage. Only valid for full clone.",
2668 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2673 description
=> "Create a full copy of all disks. This is always done when " .
2674 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2676 target
=> get_standard_option
('pve-node', {
2677 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2681 description
=> "Override I/O bandwidth limit (in KiB/s).",
2685 default => 'clone limit from datacenter or storage config',
2695 my $rpcenv = PVE
::RPCEnvironment
::get
();
2697 my $authuser = $rpcenv->get_user();
2699 my $node = extract_param
($param, 'node');
2701 my $vmid = extract_param
($param, 'vmid');
2703 my $newid = extract_param
($param, 'newid');
2705 my $pool = extract_param
($param, 'pool');
2707 if (defined($pool)) {
2708 $rpcenv->check_pool_exist($pool);
2711 my $snapname = extract_param
($param, 'snapname');
2713 my $storage = extract_param
($param, 'storage');
2715 my $format = extract_param
($param, 'format');
2717 my $target = extract_param
($param, 'target');
2719 my $localnode = PVE
::INotify
::nodename
();
2721 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
2723 PVE
::Cluster
::check_node_exists
($target) if $target;
2725 my $storecfg = PVE
::Storage
::config
();
2728 # check if storage is enabled on local node
2729 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2731 # check if storage is available on target node
2732 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2733 # clone only works if target storage is shared
2734 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2735 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2739 PVE
::Cluster
::check_cfs_quorum
();
2741 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2743 # exclusive lock if VM is running - else shared lock is enough;
2744 my $shared_lock = $running ?
0 : 1;
2748 # do all tests after lock
2749 # we also try to do all tests before we fork the worker
2751 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2753 PVE
::QemuConfig-
>check_lock($conf);
2755 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2757 die "unexpected state change\n" if $verify_running != $running;
2759 die "snapshot '$snapname' does not exist\n"
2760 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2762 my $full = extract_param
($param, 'full');
2763 if (!defined($full)) {
2764 $full = !PVE
::QemuConfig-
>is_template($conf);
2767 die "parameter 'storage' not allowed for linked clones\n"
2768 if defined($storage) && !$full;
2770 die "parameter 'format' not allowed for linked clones\n"
2771 if defined($format) && !$full;
2773 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2775 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2777 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
2779 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2781 die "unable to create VM $newid: config file already exists\n"
2784 my $newconf = { lock => 'clone' };
2789 foreach my $opt (keys %$oldconf) {
2790 my $value = $oldconf->{$opt};
2792 # do not copy snapshot related info
2793 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2794 $opt eq 'vmstate' || $opt eq 'snapstate';
2796 # no need to copy unused images, because VMID(owner) changes anyways
2797 next if $opt =~ m/^unused\d+$/;
2799 # always change MAC! address
2800 if ($opt =~ m/^net(\d+)$/) {
2801 my $net = PVE
::QemuServer
::parse_net
($value);
2802 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
2803 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
2804 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
2805 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
2806 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
2807 die "unable to parse drive options for '$opt'\n" if !$drive;
2808 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
2809 $newconf->{$opt} = $value; # simply copy configuration
2811 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
2812 die "Full clone feature is not supported for drive '$opt'\n"
2813 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
2814 $fullclone->{$opt} = 1;
2816 # not full means clone instead of copy
2817 die "Linked clone feature is not supported for drive '$opt'\n"
2818 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
2820 $drives->{$opt} = $drive;
2821 push @$vollist, $drive->{file
};
2824 # copy everything else
2825 $newconf->{$opt} = $value;
2829 # auto generate a new uuid
2830 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
2831 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
2832 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
2834 # auto generate a new vmgenid if the option was set
2835 if ($newconf->{vmgenid
}) {
2836 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
2839 delete $newconf->{template
};
2841 if ($param->{name
}) {
2842 $newconf->{name
} = $param->{name
};
2844 if ($oldconf->{name
}) {
2845 $newconf->{name
} = "Copy-of-$oldconf->{name}";
2847 $newconf->{name
} = "Copy-of-VM-$vmid";
2851 if ($param->{description
}) {
2852 $newconf->{description
} = $param->{description
};
2855 # create empty/temp config - this fails if VM already exists on other node
2856 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
2861 my $newvollist = [];
2868 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
2870 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
2872 my $bwlimit = extract_param
($param, 'bwlimit');
2874 my $total_jobs = scalar(keys %{$drives});
2877 foreach my $opt (keys %$drives) {
2878 my $drive = $drives->{$opt};
2879 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2881 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
2882 my $storage_list = [ $src_sid ];
2883 push @$storage_list, $storage if defined($storage);
2884 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
2886 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
2887 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2888 $jobs, $skipcomplete, $oldconf->{agent
}, $clonelimit);
2890 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
2892 PVE
::QemuConfig-
>write_config($newid, $newconf);
2896 delete $newconf->{lock};
2898 # do not write pending changes
2899 if (my @changes = keys %{$newconf->{pending
}}) {
2900 my $pending = join(',', @changes);
2901 warn "found pending changes for '$pending', discarding for clone\n";
2902 delete $newconf->{pending
};
2905 PVE
::QemuConfig-
>write_config($newid, $newconf);
2908 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2909 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
2910 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
2912 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
2913 die "Failed to move config to node '$target' - rename failed: $!\n"
2914 if !rename($conffile, $newconffile);
2917 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
2922 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
2924 sleep 1; # some storage like rbd need to wait before release volume - really?
2926 foreach my $volid (@$newvollist) {
2927 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2930 die "clone failed: $err";
2936 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
2938 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
2941 return PVE
::QemuConfig-
>lock_config_mode($vmid, 1, $shared_lock, sub {
2942 # Aquire exclusive lock lock for $newid
2943 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
2948 __PACKAGE__-
>register_method({
2949 name
=> 'move_vm_disk',
2950 path
=> '{vmid}/move_disk',
2954 description
=> "Move volume to different storage.",
2956 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2958 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2959 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2963 additionalProperties
=> 0,
2965 node
=> get_standard_option
('pve-node'),
2966 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2969 description
=> "The disk you want to move.",
2970 enum
=> [ PVE
::QemuServer
::valid_drive_names
() ],
2972 storage
=> get_standard_option
('pve-storage-id', {
2973 description
=> "Target storage.",
2974 completion
=> \
&PVE
::QemuServer
::complete_storage
,
2978 description
=> "Target Format.",
2979 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
2984 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2990 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2995 description
=> "Override I/O bandwidth limit (in KiB/s).",
2999 default => 'move limit from datacenter or storage config',
3005 description
=> "the task ID.",
3010 my $rpcenv = PVE
::RPCEnvironment
::get
();
3012 my $authuser = $rpcenv->get_user();
3014 my $node = extract_param
($param, 'node');
3016 my $vmid = extract_param
($param, 'vmid');
3018 my $digest = extract_param
($param, 'digest');
3020 my $disk = extract_param
($param, 'disk');
3022 my $storeid = extract_param
($param, 'storage');
3024 my $format = extract_param
($param, 'format');
3026 my $storecfg = PVE
::Storage
::config
();
3028 my $updatefn = sub {
3030 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3032 PVE
::QemuConfig-
>check_lock($conf);
3034 die "checksum missmatch (file change by other user?)\n"
3035 if $digest && $digest ne $conf->{digest
};
3037 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3039 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3041 my $old_volid = $drive->{file
} || die "disk '$disk' has no associated volume\n";
3043 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3046 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3047 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3051 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
3052 (!$format || !$oldfmt || $oldfmt eq $format);
3054 # this only checks snapshots because $disk is passed!
3055 my $snapshotted = PVE
::QemuServer
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3056 die "you can't move a disk with snapshots and delete the source\n"
3057 if $snapshotted && $param->{delete};
3059 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3061 my $running = PVE
::QemuServer
::check_running
($vmid);
3063 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3067 my $newvollist = [];
3073 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3075 warn "moving disk with snapshots, snapshots will not be moved!\n"
3078 my $bwlimit = extract_param
($param, 'bwlimit');
3079 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3081 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3082 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
3084 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $newdrive);
3086 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3088 # convert moved disk to base if part of template
3089 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3090 if PVE
::QemuConfig-
>is_template($conf);
3092 PVE
::QemuConfig-
>write_config($vmid, $conf);
3094 if ($running && PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
} && PVE
::QemuServer
::qga_check_running
($vmid)) {
3095 eval { mon_cmd
($vmid, "guest-fstrim"); };
3099 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3100 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3107 foreach my $volid (@$newvollist) {
3108 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3111 die "storage migration failed: $err";
3114 if ($param->{delete}) {
3116 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3117 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3123 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3126 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3129 my $check_vm_disks_local = sub {
3130 my ($storecfg, $vmconf, $vmid) = @_;
3132 my $local_disks = {};
3134 # add some more information to the disks e.g. cdrom
3135 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3136 my ($volid, $attr) = @_;
3138 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3140 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3141 return if $scfg->{shared
};
3143 # The shared attr here is just a special case where the vdisk
3144 # is marked as shared manually
3145 return if $attr->{shared
};
3146 return if $attr->{cdrom
} and $volid eq "none";
3148 if (exists $local_disks->{$volid}) {
3149 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3151 $local_disks->{$volid} = $attr;
3152 # ensure volid is present in case it's needed
3153 $local_disks->{$volid}->{volid
} = $volid;
3157 return $local_disks;
3160 __PACKAGE__-
>register_method({
3161 name
=> 'migrate_vm_precondition',
3162 path
=> '{vmid}/migrate',
3166 description
=> "Get preconditions for migration.",
3168 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3171 additionalProperties
=> 0,
3173 node
=> get_standard_option
('pve-node'),
3174 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3175 target
=> get_standard_option
('pve-node', {
3176 description
=> "Target node.",
3177 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3185 running
=> { type
=> 'boolean' },
3189 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3191 not_allowed_nodes
=> {
3194 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3198 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3200 local_resources
=> {
3202 description
=> "List local resources e.g. pci, usb"
3209 my $rpcenv = PVE
::RPCEnvironment
::get
();
3211 my $authuser = $rpcenv->get_user();
3213 PVE
::Cluster
::check_cfs_quorum
();
3217 my $vmid = extract_param
($param, 'vmid');
3218 my $target = extract_param
($param, 'target');
3219 my $localnode = PVE
::INotify
::nodename
();
3223 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3224 my $storecfg = PVE
::Storage
::config
();
3227 # try to detect errors early
3228 PVE
::QemuConfig-
>check_lock($vmconf);
3230 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3232 # if vm is not running, return target nodes where local storage is available
3233 # for offline migration
3234 if (!$res->{running
}) {
3235 $res->{allowed_nodes
} = [];
3236 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3237 delete $checked_nodes->{$localnode};
3239 foreach my $node (keys %$checked_nodes) {
3240 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3241 push @{$res->{allowed_nodes
}}, $node;
3245 $res->{not_allowed_nodes
} = $checked_nodes;
3249 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3250 $res->{local_disks
} = [ values %$local_disks ];;
3252 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3254 $res->{local_resources
} = $local_resources;
3261 __PACKAGE__-
>register_method({
3262 name
=> 'migrate_vm',
3263 path
=> '{vmid}/migrate',
3267 description
=> "Migrate virtual machine. Creates a new migration task.",
3269 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3272 additionalProperties
=> 0,
3274 node
=> get_standard_option
('pve-node'),
3275 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3276 target
=> get_standard_option
('pve-node', {
3277 description
=> "Target node.",
3278 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3282 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3287 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3292 enum
=> ['secure', 'insecure'],
3293 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3296 migration_network
=> {
3297 type
=> 'string', format
=> 'CIDR',
3298 description
=> "CIDR of the (sub) network that is used for migration.",
3301 "with-local-disks" => {
3303 description
=> "Enable live storage migration for local disk",
3306 targetstorage
=> get_standard_option
('pve-storage-id', {
3307 description
=> "Default target storage.",
3309 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3312 description
=> "Override I/O bandwidth limit (in KiB/s).",
3316 default => 'migrate limit from datacenter or storage config',
3322 description
=> "the task ID.",
3327 my $rpcenv = PVE
::RPCEnvironment
::get
();
3328 my $authuser = $rpcenv->get_user();
3330 my $target = extract_param
($param, 'target');
3332 my $localnode = PVE
::INotify
::nodename
();
3333 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3335 PVE
::Cluster
::check_cfs_quorum
();
3337 PVE
::Cluster
::check_node_exists
($target);
3339 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3341 my $vmid = extract_param
($param, 'vmid');
3343 raise_param_exc
({ force
=> "Only root may use this option." })
3344 if $param->{force
} && $authuser ne 'root@pam';
3346 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3347 if $param->{migration_type
} && $authuser ne 'root@pam';
3349 # allow root only until better network permissions are available
3350 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3351 if $param->{migration_network
} && $authuser ne 'root@pam';
3354 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3356 # try to detect errors early
3358 PVE
::QemuConfig-
>check_lock($conf);
3360 if (PVE
::QemuServer
::check_running
($vmid)) {
3361 die "can't migrate running VM without --online\n" if !$param->{online
};
3363 warn "VM isn't running. Doing offline migration instead\n." if $param->{online
};
3364 $param->{online
} = 0;
3367 raise_param_exc
({ targetstorage
=> "Live storage migration can only be done online." })
3368 if !$param->{online
} && $param->{targetstorage
};
3370 my $storecfg = PVE
::Storage
::config
();
3372 if( $param->{targetstorage
}) {
3373 PVE
::Storage
::storage_check_node
($storecfg, $param->{targetstorage
}, $target);
3375 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3378 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3383 print "Requesting HA migration for VM $vmid to node $target\n";
3385 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3386 PVE
::Tools
::run_command
($cmd);
3390 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3395 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3399 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3402 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3407 __PACKAGE__-
>register_method({
3409 path
=> '{vmid}/monitor',
3413 description
=> "Execute Qemu monitor commands.",
3415 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3416 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3419 additionalProperties
=> 0,
3421 node
=> get_standard_option
('pve-node'),
3422 vmid
=> get_standard_option
('pve-vmid'),
3425 description
=> "The monitor command.",
3429 returns
=> { type
=> 'string'},
3433 my $rpcenv = PVE
::RPCEnvironment
::get
();
3434 my $authuser = $rpcenv->get_user();
3437 my $command = shift;
3438 return $command =~ m/^\s*info(\s+|$)/
3439 || $command =~ m/^\s*help\s*$/;
3442 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3443 if !&$is_ro($param->{command
});
3445 my $vmid = $param->{vmid
};
3447 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3451 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3453 $res = "ERROR: $@" if $@;
3458 __PACKAGE__-
>register_method({
3459 name
=> 'resize_vm',
3460 path
=> '{vmid}/resize',
3464 description
=> "Extend volume size.",
3466 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3469 additionalProperties
=> 0,
3471 node
=> get_standard_option
('pve-node'),
3472 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3473 skiplock
=> get_standard_option
('skiplock'),
3476 description
=> "The disk you want to resize.",
3477 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3481 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3482 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.",
3486 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3492 returns
=> { type
=> 'null'},
3496 my $rpcenv = PVE
::RPCEnvironment
::get
();
3498 my $authuser = $rpcenv->get_user();
3500 my $node = extract_param
($param, 'node');
3502 my $vmid = extract_param
($param, 'vmid');
3504 my $digest = extract_param
($param, 'digest');
3506 my $disk = extract_param
($param, 'disk');
3508 my $sizestr = extract_param
($param, 'size');
3510 my $skiplock = extract_param
($param, 'skiplock');
3511 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3512 if $skiplock && $authuser ne 'root@pam';
3514 my $storecfg = PVE
::Storage
::config
();
3516 my $updatefn = sub {
3518 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3520 die "checksum missmatch (file change by other user?)\n"
3521 if $digest && $digest ne $conf->{digest
};
3522 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3524 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3526 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3528 my (undef, undef, undef, undef, undef, undef, $format) =
3529 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3531 die "can't resize volume: $disk if snapshot exists\n"
3532 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3534 my $volid = $drive->{file
};
3536 die "disk '$disk' has no associated volume\n" if !$volid;
3538 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3540 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3542 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3544 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3545 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3547 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3549 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3550 my ($ext, $newsize, $unit) = ($1, $2, $4);
3553 $newsize = $newsize * 1024;
3554 } elsif ($unit eq 'M') {
3555 $newsize = $newsize * 1024 * 1024;
3556 } elsif ($unit eq 'G') {
3557 $newsize = $newsize * 1024 * 1024 * 1024;
3558 } elsif ($unit eq 'T') {
3559 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3562 $newsize += $size if $ext;
3563 $newsize = int($newsize);
3565 die "shrinking disks is not supported\n" if $newsize < $size;
3567 return if $size == $newsize;
3569 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3571 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3573 $drive->{size
} = $newsize;
3574 $conf->{$disk} = PVE
::QemuServer
::print_drive
($vmid, $drive);
3576 PVE
::QemuConfig-
>write_config($vmid, $conf);
3579 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3583 __PACKAGE__-
>register_method({
3584 name
=> 'snapshot_list',
3585 path
=> '{vmid}/snapshot',
3587 description
=> "List all snapshots.",
3589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3592 protected
=> 1, # qemu pid files are only readable by root
3594 additionalProperties
=> 0,
3596 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3597 node
=> get_standard_option
('pve-node'),
3606 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3610 description
=> "Snapshot includes RAM.",
3615 description
=> "Snapshot description.",
3619 description
=> "Snapshot creation time",
3621 renderer
=> 'timestamp',
3625 description
=> "Parent snapshot identifier.",
3631 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3636 my $vmid = $param->{vmid
};
3638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3639 my $snaphash = $conf->{snapshots
} || {};
3643 foreach my $name (keys %$snaphash) {
3644 my $d = $snaphash->{$name};
3647 snaptime
=> $d->{snaptime
} || 0,
3648 vmstate
=> $d->{vmstate
} ?
1 : 0,
3649 description
=> $d->{description
} || '',
3651 $item->{parent
} = $d->{parent
} if $d->{parent
};
3652 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3656 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3659 digest
=> $conf->{digest
},
3660 running
=> $running,
3661 description
=> "You are here!",
3663 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3665 push @$res, $current;
3670 __PACKAGE__-
>register_method({
3672 path
=> '{vmid}/snapshot',
3676 description
=> "Snapshot a VM.",
3678 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3681 additionalProperties
=> 0,
3683 node
=> get_standard_option
('pve-node'),
3684 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3685 snapname
=> get_standard_option
('pve-snapshot-name'),
3689 description
=> "Save the vmstate",
3694 description
=> "A textual description or comment.",
3700 description
=> "the task ID.",
3705 my $rpcenv = PVE
::RPCEnvironment
::get
();
3707 my $authuser = $rpcenv->get_user();
3709 my $node = extract_param
($param, 'node');
3711 my $vmid = extract_param
($param, 'vmid');
3713 my $snapname = extract_param
($param, 'snapname');
3715 die "unable to use snapshot name 'current' (reserved name)\n"
3716 if $snapname eq 'current';
3719 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3720 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3721 $param->{description
});
3724 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3727 __PACKAGE__-
>register_method({
3728 name
=> 'snapshot_cmd_idx',
3729 path
=> '{vmid}/snapshot/{snapname}',
3736 additionalProperties
=> 0,
3738 vmid
=> get_standard_option
('pve-vmid'),
3739 node
=> get_standard_option
('pve-node'),
3740 snapname
=> get_standard_option
('pve-snapshot-name'),
3749 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3756 push @$res, { cmd
=> 'rollback' };
3757 push @$res, { cmd
=> 'config' };
3762 __PACKAGE__-
>register_method({
3763 name
=> 'update_snapshot_config',
3764 path
=> '{vmid}/snapshot/{snapname}/config',
3768 description
=> "Update snapshot metadata.",
3770 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3773 additionalProperties
=> 0,
3775 node
=> get_standard_option
('pve-node'),
3776 vmid
=> get_standard_option
('pve-vmid'),
3777 snapname
=> get_standard_option
('pve-snapshot-name'),
3781 description
=> "A textual description or comment.",
3785 returns
=> { type
=> 'null' },
3789 my $rpcenv = PVE
::RPCEnvironment
::get
();
3791 my $authuser = $rpcenv->get_user();
3793 my $vmid = extract_param
($param, 'vmid');
3795 my $snapname = extract_param
($param, 'snapname');
3797 return undef if !defined($param->{description
});
3799 my $updatefn = sub {
3801 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3803 PVE
::QemuConfig-
>check_lock($conf);
3805 my $snap = $conf->{snapshots
}->{$snapname};
3807 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3809 $snap->{description
} = $param->{description
} if defined($param->{description
});
3811 PVE
::QemuConfig-
>write_config($vmid, $conf);
3814 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3819 __PACKAGE__-
>register_method({
3820 name
=> 'get_snapshot_config',
3821 path
=> '{vmid}/snapshot/{snapname}/config',
3824 description
=> "Get snapshot configuration",
3826 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3829 additionalProperties
=> 0,
3831 node
=> get_standard_option
('pve-node'),
3832 vmid
=> get_standard_option
('pve-vmid'),
3833 snapname
=> get_standard_option
('pve-snapshot-name'),
3836 returns
=> { type
=> "object" },
3840 my $rpcenv = PVE
::RPCEnvironment
::get
();
3842 my $authuser = $rpcenv->get_user();
3844 my $vmid = extract_param
($param, 'vmid');
3846 my $snapname = extract_param
($param, 'snapname');
3848 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3850 my $snap = $conf->{snapshots
}->{$snapname};
3852 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3857 __PACKAGE__-
>register_method({
3859 path
=> '{vmid}/snapshot/{snapname}/rollback',
3863 description
=> "Rollback VM state to specified snapshot.",
3865 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
3868 additionalProperties
=> 0,
3870 node
=> get_standard_option
('pve-node'),
3871 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3872 snapname
=> get_standard_option
('pve-snapshot-name'),
3877 description
=> "the task ID.",
3882 my $rpcenv = PVE
::RPCEnvironment
::get
();
3884 my $authuser = $rpcenv->get_user();
3886 my $node = extract_param
($param, 'node');
3888 my $vmid = extract_param
($param, 'vmid');
3890 my $snapname = extract_param
($param, 'snapname');
3893 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
3894 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
3898 # hold migration lock, this makes sure that nobody create replication snapshots
3899 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3902 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
3905 __PACKAGE__-
>register_method({
3906 name
=> 'delsnapshot',
3907 path
=> '{vmid}/snapshot/{snapname}',
3911 description
=> "Delete a VM snapshot.",
3913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3916 additionalProperties
=> 0,
3918 node
=> get_standard_option
('pve-node'),
3919 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3920 snapname
=> get_standard_option
('pve-snapshot-name'),
3924 description
=> "For removal from config file, even if removing disk snapshots fails.",
3930 description
=> "the task ID.",
3935 my $rpcenv = PVE
::RPCEnvironment
::get
();
3937 my $authuser = $rpcenv->get_user();
3939 my $node = extract_param
($param, 'node');
3941 my $vmid = extract_param
($param, 'vmid');
3943 my $snapname = extract_param
($param, 'snapname');
3946 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
3947 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
3950 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
3953 __PACKAGE__-
>register_method({
3955 path
=> '{vmid}/template',
3959 description
=> "Create a Template.",
3961 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
3962 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
3965 additionalProperties
=> 0,
3967 node
=> get_standard_option
('pve-node'),
3968 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
3972 description
=> "If you want to convert only 1 disk to base image.",
3973 enum
=> [PVE
::QemuServer
::valid_drive_names
()],
3978 returns
=> { type
=> 'null'},
3982 my $rpcenv = PVE
::RPCEnvironment
::get
();
3984 my $authuser = $rpcenv->get_user();
3986 my $node = extract_param
($param, 'node');
3988 my $vmid = extract_param
($param, 'vmid');
3990 my $disk = extract_param
($param, 'disk');
3992 my $updatefn = sub {
3994 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3996 PVE
::QemuConfig-
>check_lock($conf);
3998 die "unable to create template, because VM contains snapshots\n"
3999 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4001 die "you can't convert a template to a template\n"
4002 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4004 die "you can't convert a VM to template if VM is running\n"
4005 if PVE
::QemuServer
::check_running
($vmid);
4008 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4011 $conf->{template
} = 1;
4012 PVE
::QemuConfig-
>write_config($vmid, $conf);
4014 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4017 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4021 __PACKAGE__-
>register_method({
4022 name
=> 'cloudinit_generated_config_dump',
4023 path
=> '{vmid}/cloudinit/dump',
4026 description
=> "Get automatically generated cloudinit config.",
4028 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4031 additionalProperties
=> 0,
4033 node
=> get_standard_option
('pve-node'),
4034 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4036 description
=> 'Config type.',
4038 enum
=> ['user', 'network', 'meta'],
4048 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4050 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});