1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
18 use PVE
::LXC
::Migrate
;
19 use PVE
::API2
::LXC
::Config
;
20 use PVE
::API2
::LXC
::Status
;
21 use PVE
::API2
::LXC
::Snapshot
;
22 use PVE
::HA
::Env
::PVE2
;
24 use PVE
::JSONSchema
qw(get_standard_option);
25 use base
qw(PVE::RESTHandler);
27 use Data
::Dumper
; # fixme: remove
29 __PACKAGE__-
>register_method ({
30 subclass
=> "PVE::API2::LXC::Config",
31 path
=> '{vmid}/config',
34 __PACKAGE__-
>register_method ({
35 subclass
=> "PVE::API2::LXC::Status",
36 path
=> '{vmid}/status',
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::LXC::Snapshot",
41 path
=> '{vmid}/snapshot',
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::Firewall::CT",
46 path
=> '{vmid}/firewall',
49 __PACKAGE__-
>register_method({
53 description
=> "LXC container index (per node).",
55 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
59 protected
=> 1, # /proc files are only readable by root
61 additionalProperties
=> 0,
63 node
=> get_standard_option
('pve-node'),
72 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
77 my $rpcenv = PVE
::RPCEnvironment
::get
();
78 my $authuser = $rpcenv->get_user();
80 my $vmstatus = PVE
::LXC
::vmstatus
();
83 foreach my $vmid (keys %$vmstatus) {
84 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
86 my $data = $vmstatus->{$vmid};
87 $data->{vmid
} = $vmid;
95 __PACKAGE__-
>register_method({
99 description
=> "Create or restore a container.",
101 user
=> 'all', # check inside
102 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
103 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
104 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
109 additionalProperties
=> 0,
110 properties
=> PVE
::LXC
::Config-
>json_config_properties({
111 node
=> get_standard_option
('pve-node'),
112 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
114 description
=> "The OS template or backup file.",
117 completion
=> \
&PVE
::LXC
::complete_os_templates
,
122 description
=> "Sets root password inside container.",
125 storage
=> get_standard_option
('pve-storage-id', {
126 description
=> "Default Storage.",
129 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
134 description
=> "Allow to overwrite existing container.",
139 description
=> "Mark this as restore task.",
143 type
=> 'string', format
=> 'pve-poolid',
144 description
=> "Add the VM to the specified pool.",
146 'ignore-unpack-errors' => {
149 description
=> "Ignore errors when extracting the template.",
151 'ssh-public-keys' => {
154 description
=> "Setup public SSH keys (one key per line, " .
165 my $rpcenv = PVE
::RPCEnvironment
::get
();
167 my $authuser = $rpcenv->get_user();
169 my $node = extract_param
($param, 'node');
171 my $vmid = extract_param
($param, 'vmid');
173 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
175 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
177 my $same_container_exists = -f
$basecfg_fn;
179 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
180 my $unprivileged = extract_param
($param, 'unprivileged');
182 my $restore = extract_param
($param, 'restore');
185 # fixme: limit allowed parameters
189 my $force = extract_param
($param, 'force');
191 if (!($same_container_exists && $restore && $force)) {
192 PVE
::Cluster
::check_vmid_unused
($vmid);
194 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
195 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
198 my $password = extract_param
($param, 'password');
200 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
201 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
203 my $pool = extract_param
($param, 'pool');
205 if (defined($pool)) {
206 $rpcenv->check_pool_exist($pool);
207 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
210 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
212 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
214 } elsif ($restore && $force && $same_container_exists &&
215 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
216 # OK: user has VM.Backup permissions, and want to restore an existing VM
221 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
223 my $storage = extract_param
($param, 'storage') // 'local';
225 my $storage_cfg = cfs_read_file
("storage.cfg");
227 my $ostemplate = extract_param
($param, 'ostemplate');
231 if ($ostemplate eq '-') {
232 die "pipe requires cli environment\n"
233 if $rpcenv->{type
} ne 'cli';
234 die "pipe can only be used with restore tasks\n"
237 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
239 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
240 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
243 my $check_and_activate_storage = sub {
246 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
248 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
249 if !$scfg->{content
}->{rootdir
};
251 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
253 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
258 my $no_disk_param = {};
260 my $storage_only_mode = 1;
261 foreach my $opt (keys %$param) {
262 my $value = $param->{$opt};
263 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
264 # allow to use simple numbers (add default storage in that case)
265 if ($value =~ m/^\d+(\.\d+)?$/) {
266 $mp_param->{$opt} = "$storage:$value";
268 $mp_param->{$opt} = $value;
270 $storage_only_mode = 0;
271 } elsif ($opt =~ m/^unused\d+$/) {
272 warn "ignoring '$opt', cannot create/restore with unused volume\n";
273 delete $param->{$opt};
275 $no_disk_param->{$opt} = $value;
279 die "mountpoints configured, but 'rootfs' not set - aborting\n"
280 if !$storage_only_mode && !defined($mp_param->{rootfs
});
282 # check storage access, activate storage
283 my $delayed_mp_param = {};
284 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
285 my ($ms, $mountpoint) = @_;
287 my $volid = $mountpoint->{volume
};
288 my $mp = $mountpoint->{mp
};
290 if ($mountpoint->{type
} ne 'volume') { # bind or device
291 die "Only root can pass arbitrary filesystem paths.\n"
292 if $authuser ne 'root@pam';
294 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
295 &$check_and_activate_storage($sid);
299 # check/activate default storage
300 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
302 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
304 $conf->{unprivileged
} = 1 if $unprivileged;
306 my $check_vmid_usage = sub {
308 die "can't overwrite running container\n"
309 if PVE
::LXC
::check_running
($vmid);
311 PVE
::Cluster
::check_vmid_unused
($vmid);
316 &$check_vmid_usage(); # final check after locking
318 PVE
::Cluster
::check_cfs_quorum
();
322 if ($storage_only_mode) {
324 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
325 die "rootfs configuration could not be recovered, please check and specify manually!\n"
326 if !defined($mp_param->{rootfs
});
327 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
328 my ($ms, $mountpoint) = @_;
329 my $type = $mountpoint->{type
};
330 if ($type eq 'volume') {
331 die "unable to detect disk size - please specify $ms (size)\n"
332 if !defined($mountpoint->{size
});
333 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
334 delete $mountpoint->{size
};
335 $mountpoint->{volume
} = "$storage:$disksize";
336 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
338 my $type = $mountpoint->{type
};
339 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
340 if ($ms eq 'rootfs');
342 if ($mountpoint->{backup
}) {
343 warn "WARNING - unsupported configuration!\n";
344 warn "backup was enabled for $type mountpoint $ms ('$mountpoint->{mp}')\n";
345 warn "mountpoint configuration will be restored after archive extraction!\n";
346 warn "contained files will be restored to wrong directory!\n";
348 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
352 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
356 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
358 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
360 die "container exists" if !$restore; # just to be sure
361 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
363 # destroy old container volumes
364 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf);
366 PVE
::LXC
::Config-
>write_config($vmid, $conf);
369 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
370 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
373 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf);
375 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
376 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
377 $lxc_setup->post_create_hook($password, $ssh_keys);
381 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
382 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
385 $conf->{hostname
} ||= "CT$vmid";
386 $conf->{memory
} ||= 512;
387 $conf->{swap
} //= 512;
388 foreach my $mp (keys %$delayed_mp_param) {
389 $conf->{$mp} = $delayed_mp_param->{$mp};
391 PVE
::LXC
::Config-
>write_config($vmid, $conf);
394 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
395 PVE
::LXC
::destroy_config
($vmid);
398 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
401 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
403 &$check_vmid_usage(); # first check before locking
405 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
406 $vmid, $authuser, $realcmd);
410 __PACKAGE__-
>register_method({
415 description
=> "Directory index",
420 additionalProperties
=> 0,
422 node
=> get_standard_option
('pve-node'),
423 vmid
=> get_standard_option
('pve-vmid'),
431 subdir
=> { type
=> 'string' },
434 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
440 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
443 { subdir
=> 'config' },
444 { subdir
=> 'status' },
445 { subdir
=> 'vncproxy' },
446 { subdir
=> 'vncwebsocket' },
447 { subdir
=> 'spiceproxy' },
448 { subdir
=> 'migrate' },
449 { subdir
=> 'clone' },
450 # { subdir => 'initlog' },
452 { subdir
=> 'rrddata' },
453 { subdir
=> 'firewall' },
454 { subdir
=> 'snapshot' },
455 { subdir
=> 'resize' },
462 __PACKAGE__-
>register_method({
464 path
=> '{vmid}/rrd',
466 protected
=> 1, # fixme: can we avoid that?
468 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
470 description
=> "Read VM RRD statistics (returns PNG)",
472 additionalProperties
=> 0,
474 node
=> get_standard_option
('pve-node'),
475 vmid
=> get_standard_option
('pve-vmid'),
477 description
=> "Specify the time frame you are interested in.",
479 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
482 description
=> "The list of datasources you want to display.",
483 type
=> 'string', format
=> 'pve-configid-list',
486 description
=> "The RRD consolidation function",
488 enum
=> [ 'AVERAGE', 'MAX' ],
496 filename
=> { type
=> 'string' },
502 return PVE
::Cluster
::create_rrd_graph
(
503 "pve2-vm/$param->{vmid}", $param->{timeframe
},
504 $param->{ds
}, $param->{cf
});
508 __PACKAGE__-
>register_method({
510 path
=> '{vmid}/rrddata',
512 protected
=> 1, # fixme: can we avoid that?
514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
516 description
=> "Read VM RRD statistics",
518 additionalProperties
=> 0,
520 node
=> get_standard_option
('pve-node'),
521 vmid
=> get_standard_option
('pve-vmid'),
523 description
=> "Specify the time frame you are interested in.",
525 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
528 description
=> "The RRD consolidation function",
530 enum
=> [ 'AVERAGE', 'MAX' ],
545 return PVE
::Cluster
::create_rrd_data
(
546 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
549 __PACKAGE__-
>register_method({
550 name
=> 'destroy_vm',
555 description
=> "Destroy the container (also delete all uses files).",
557 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
560 additionalProperties
=> 0,
562 node
=> get_standard_option
('pve-node'),
563 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
572 my $rpcenv = PVE
::RPCEnvironment
::get
();
574 my $authuser = $rpcenv->get_user();
576 my $vmid = $param->{vmid
};
578 # test if container exists
579 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
581 my $storage_cfg = cfs_read_file
("storage.cfg");
583 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
585 die "unable to remove CT $vmid - used in HA resources\n"
586 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
588 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
590 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
593 # reload config after lock
594 $conf = PVE
::LXC
::Config-
>load_config($vmid);
595 PVE
::LXC
::Config-
>check_lock($conf);
597 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
599 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
600 PVE
::AccessControl
::remove_vm_access
($vmid);
601 PVE
::Firewall
::remove_vmfw_conf
($vmid);
604 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
606 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
611 __PACKAGE__-
>register_method ({
613 path
=> '{vmid}/vncproxy',
617 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
619 description
=> "Creates a TCP VNC proxy connections.",
621 additionalProperties
=> 0,
623 node
=> get_standard_option
('pve-node'),
624 vmid
=> get_standard_option
('pve-vmid'),
628 description
=> "use websocket instead of standard VNC.",
633 additionalProperties
=> 0,
635 user
=> { type
=> 'string' },
636 ticket
=> { type
=> 'string' },
637 cert
=> { type
=> 'string' },
638 port
=> { type
=> 'integer' },
639 upid
=> { type
=> 'string' },
645 my $rpcenv = PVE
::RPCEnvironment
::get
();
647 my $authuser = $rpcenv->get_user();
649 my $vmid = $param->{vmid
};
650 my $node = $param->{node
};
652 my $authpath = "/vms/$vmid";
654 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
656 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
659 my ($remip, $family);
661 if ($node ne PVE
::INotify
::nodename
()) {
662 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
664 $family = PVE
::Tools
::get_host_address_family
($node);
667 my $port = PVE
::Tools
::next_vnc_port
($family);
669 # NOTE: vncterm VNC traffic is already TLS encrypted,
670 # so we select the fastest chipher here (or 'none'?)
671 my $remcmd = $remip ?
672 ['/usr/bin/ssh', '-t', $remip] : [];
674 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
675 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
677 my $shcmd = [ '/usr/bin/dtach', '-A',
678 "/var/run/dtach/vzctlconsole$vmid",
679 '-r', 'winch', '-z', @$concmd];
684 syslog
('info', "starting lxc vnc proxy $upid\n");
688 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
689 '-timeout', $timeout, '-authpath', $authpath,
690 '-perm', 'VM.Console'];
692 if ($param->{websocket
}) {
693 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
694 push @$cmd, '-notls', '-listen', 'localhost';
697 push @$cmd, '-c', @$remcmd, @$shcmd;
704 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
706 PVE
::Tools
::wait_for_vnc_port
($port);
717 __PACKAGE__-
>register_method({
718 name
=> 'vncwebsocket',
719 path
=> '{vmid}/vncwebsocket',
722 description
=> "You also need to pass a valid ticket (vncticket).",
723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
725 description
=> "Opens a weksocket for VNC traffic.",
727 additionalProperties
=> 0,
729 node
=> get_standard_option
('pve-node'),
730 vmid
=> get_standard_option
('pve-vmid'),
732 description
=> "Ticket from previous call to vncproxy.",
737 description
=> "Port number returned by previous vncproxy call.",
747 port
=> { type
=> 'string' },
753 my $rpcenv = PVE
::RPCEnvironment
::get
();
755 my $authuser = $rpcenv->get_user();
757 my $authpath = "/vms/$param->{vmid}";
759 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
761 my $port = $param->{port
};
763 return { port
=> $port };
766 __PACKAGE__-
>register_method ({
767 name
=> 'spiceproxy',
768 path
=> '{vmid}/spiceproxy',
773 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
775 description
=> "Returns a SPICE configuration to connect to the CT.",
777 additionalProperties
=> 0,
779 node
=> get_standard_option
('pve-node'),
780 vmid
=> get_standard_option
('pve-vmid'),
781 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
784 returns
=> get_standard_option
('remote-viewer-config'),
788 my $vmid = $param->{vmid
};
789 my $node = $param->{node
};
790 my $proxy = $param->{proxy
};
792 my $authpath = "/vms/$vmid";
793 my $permissions = 'VM.Console';
795 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
797 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
799 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
801 my $shcmd = ['/usr/bin/dtach', '-A',
802 "/var/run/dtach/vzctlconsole$vmid",
803 '-r', 'winch', '-z', @$concmd];
805 my $title = "CT $vmid";
807 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
811 __PACKAGE__-
>register_method({
812 name
=> 'migrate_vm',
813 path
=> '{vmid}/migrate',
817 description
=> "Migrate the container to another node. Creates a new migration task.",
819 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
822 additionalProperties
=> 0,
824 node
=> get_standard_option
('pve-node'),
825 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
826 target
=> get_standard_option
('pve-node', {
827 description
=> "Target node.",
828 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
832 description
=> "Use online/live migration.",
837 description
=> "Force migration despite local bind / device" .
838 " mounts. WARNING: identical bind / device mounts need to ".
839 " be available on the target node.",
846 description
=> "the task ID.",
851 my $rpcenv = PVE
::RPCEnvironment
::get
();
853 my $authuser = $rpcenv->get_user();
855 my $target = extract_param
($param, 'target');
857 my $localnode = PVE
::INotify
::nodename
();
858 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
860 PVE
::Cluster
::check_cfs_quorum
();
862 PVE
::Cluster
::check_node_exists
($target);
864 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
866 my $vmid = extract_param
($param, 'vmid');
869 PVE
::LXC
::Config-
>load_config($vmid);
871 # try to detect errors early
872 if (PVE
::LXC
::check_running
($vmid)) {
873 die "can't migrate running container without --online\n"
874 if !$param->{online
};
877 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
882 my $service = "ct:$vmid";
884 my $cmd = ['ha-manager', 'migrate', $service, $target];
886 print "Executing HA migrate for CT $vmid to node $target\n";
888 PVE
::Tools
::run_command
($cmd);
893 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
900 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
905 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
909 __PACKAGE__-
>register_method({
910 name
=> 'vm_feature',
911 path
=> '{vmid}/feature',
915 description
=> "Check if feature for virtual machine is available.",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid'),
925 description
=> "Feature to check.",
927 enum
=> [ 'snapshot' ],
929 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
937 hasFeature
=> { type
=> 'boolean' },
940 #items => { type => 'string' },
947 my $node = extract_param
($param, 'node');
949 my $vmid = extract_param
($param, 'vmid');
951 my $snapname = extract_param
($param, 'snapname');
953 my $feature = extract_param
($param, 'feature');
955 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
958 my $snap = $conf->{snapshots
}->{$snapname};
959 die "snapshot '$snapname' does not exist\n" if !defined($snap);
962 my $storage_cfg = PVE
::Storage
::config
();
964 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
965 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
968 hasFeature
=> $hasFeature,
969 #nodes => [ keys %$nodelist ],
973 __PACKAGE__-
>register_method({
975 path
=> '{vmid}/template',
979 description
=> "Create a Template.",
981 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
982 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
985 additionalProperties
=> 0,
987 node
=> get_standard_option
('pve-node'),
988 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
991 description
=> "The template feature is experimental, set this " .
992 "flag if you know what you are doing.",
997 returns
=> { type
=> 'null'},
1001 my $rpcenv = PVE
::RPCEnvironment
::get
();
1003 my $authuser = $rpcenv->get_user();
1005 my $node = extract_param
($param, 'node');
1007 my $vmid = extract_param
($param, 'vmid');
1009 my $updatefn = sub {
1011 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1012 PVE
::LXC
::Config-
>check_lock($conf);
1014 die "unable to create template, because CT contains snapshots\n"
1015 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1017 die "you can't convert a template to a template\n"
1018 if PVE
::LXC
::Config-
>is_template($conf);
1020 die "you can't convert a CT to template if the CT is running\n"
1021 if PVE
::LXC
::check_running
($vmid);
1024 PVE
::LXC
::template_create
($vmid, $conf);
1027 $conf->{template
} = 1;
1029 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1030 # and remove lxc config
1031 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
1033 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1036 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1041 __PACKAGE__-
>register_method({
1043 path
=> '{vmid}/clone',
1047 description
=> "Create a container clone/copy",
1049 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1050 "and 'VM.Allocate' permissions " .
1051 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1052 "'Datastore.AllocateSpace' on any used storage.",
1055 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1057 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1058 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1063 additionalProperties
=> 0,
1065 node
=> get_standard_option
('pve-node'),
1066 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1067 newid
=> get_standard_option
('pve-vmid', {
1068 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1069 description
=> 'VMID for the clone.' }),
1072 type
=> 'string', format
=> 'dns-name',
1073 description
=> "Set a hostname for the new CT.",
1078 description
=> "Description for the new CT.",
1082 type
=> 'string', format
=> 'pve-poolid',
1083 description
=> "Add the new CT to the specified pool.",
1085 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1088 storage
=> get_standard_option
('pve-storage-id', {
1089 description
=> "Target storage for full clone.",
1096 description
=> "Create a full copy of all disk. This is always done when " .
1097 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1102 description
=> "The clone feature is experimental, set this " .
1103 "flag if you know what you are doing.",
1106 # target => get_standard_option('pve-node', {
1107 # description => "Target node. Only allowed if the original VM is on shared storage.",
1118 my $rpcenv = PVE
::RPCEnvironment
::get
();
1120 my $authuser = $rpcenv->get_user();
1122 my $node = extract_param
($param, 'node');
1124 my $vmid = extract_param
($param, 'vmid');
1126 my $newid = extract_param
($param, 'newid');
1128 my $pool = extract_param
($param, 'pool');
1130 if (defined($pool)) {
1131 $rpcenv->check_pool_exist($pool);
1134 my $snapname = extract_param
($param, 'snapname');
1136 my $storage = extract_param
($param, 'storage');
1138 my $localnode = PVE
::INotify
::nodename
();
1140 my $storecfg = PVE
::Storage
::config
();
1143 # check if storage is enabled on local node
1144 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1147 PVE
::Cluster
::check_cfs_quorum
();
1149 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1153 # do all tests after lock
1154 # we also try to do all tests before we fork the worker
1155 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1157 PVE
::LXC
::Config-
>check_lock($conf);
1159 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1161 die "unexpected state change\n" if $verify_running != $running;
1163 die "snapshot '$snapname' does not exist\n"
1164 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1166 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1168 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1169 die "unable to create CT $newid: config file already exists\n"
1172 my $newconf = { lock => 'clone' };
1173 my $mountpoints = {};
1177 foreach my $opt (keys %$oldconf) {
1178 my $value = $oldconf->{$opt};
1180 # no need to copy unused images, because VMID(owner) changes anyways
1181 next if $opt =~ m/^unused\d+$/;
1183 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1184 my $mp = $opt eq 'rootfs' ?
1185 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1186 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1188 if ($mp->{type
} eq 'volume') {
1189 my $volid = $mp->{volume
};
1190 if ($param->{full
}) {
1191 die "fixme: full clone not implemented";
1193 die "Full clone feature for '$volid' is not available\n"
1194 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1195 $fullclone->{$opt} = 1;
1197 # not full means clone instead of copy
1198 die "Linked clone feature for '$volid' is not available\n"
1199 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1202 $mountpoints->{$opt} = $mp;
1203 push @$vollist, $volid;
1206 # TODO: allow bind mounts?
1207 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1211 # copy everything else
1212 $newconf->{$opt} = $value;
1216 delete $newconf->{template
};
1217 if ($param->{hostname
}) {
1218 $newconf->{hostname
} = $param->{hostname
};
1221 if ($param->{description
}) {
1222 $newconf->{description
} = $param->{description
};
1225 # create empty/temp config - this fails if CT already exists on other node
1226 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1231 my $newvollist = [];
1234 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1236 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1238 foreach my $opt (keys %$mountpoints) {
1239 my $mp = $mountpoints->{$opt};
1240 my $volid = $mp->{volume
};
1242 if ($fullclone->{$opt}) {
1243 die "fixme: full clone not implemented\n";
1245 print "create linked clone of mountpoint $opt ($volid)\n";
1246 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1247 push @$newvollist, $newvolid;
1248 $mp->{volume
} = $newvolid;
1250 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1251 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1255 delete $newconf->{lock};
1256 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1258 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1263 sleep 1; # some storage like rbd need to wait before release volume - really?
1265 foreach my $volid (@$newvollist) {
1266 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1269 die "clone failed: $err";
1275 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1277 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1281 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1285 __PACKAGE__-
>register_method({
1286 name
=> 'resize_vm',
1287 path
=> '{vmid}/resize',
1291 description
=> "Resize a container mountpoint.",
1293 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1296 additionalProperties
=> 0,
1298 node
=> get_standard_option
('pve-node'),
1299 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1302 description
=> "The disk you want to resize.",
1303 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1307 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1308 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.",
1312 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1320 description
=> "the task ID.",
1325 my $rpcenv = PVE
::RPCEnvironment
::get
();
1327 my $authuser = $rpcenv->get_user();
1329 my $node = extract_param
($param, 'node');
1331 my $vmid = extract_param
($param, 'vmid');
1333 my $digest = extract_param
($param, 'digest');
1335 my $sizestr = extract_param
($param, 'size');
1336 my $ext = ($sizestr =~ s/^\+//);
1337 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1338 die "invalid size string" if !defined($newsize);
1340 die "no options specified\n" if !scalar(keys %$param);
1342 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1344 my $storage_cfg = cfs_read_file
("storage.cfg");
1348 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1349 PVE
::LXC
::Config-
>check_lock($conf);
1351 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1353 my $running = PVE
::LXC
::check_running
($vmid);
1355 my $disk = $param->{disk
};
1356 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1357 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1359 my $volid = $mp->{volume
};
1361 my (undef, undef, $owner, undef, undef, undef, $format) =
1362 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1364 die "can't resize mountpoint owned by another container ($owner)"
1367 die "can't resize volume: $disk if snapshot exists\n"
1368 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1370 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1372 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1374 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1376 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1377 $newsize += $size if $ext;
1378 $newsize = int($newsize);
1380 die "unable to shrink disk size\n" if $newsize < $size;
1382 return if $size == $newsize;
1384 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1386 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1387 # we pass 0 here (parameter only makes sense for qemu)
1388 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1390 $mp->{size
} = $newsize;
1391 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1393 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1395 if ($format eq 'raw') {
1396 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1400 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1401 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1402 die "internal error: CT running but mountpoint not attached to a loop device"
1404 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1406 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1407 # to be visible to it in its namespace.
1408 # To not interfere with the rest of the system we unshare the current mount namespace,
1409 # mount over /tmp and then run resize2fs.
1411 # interestingly we don't need to e2fsck on mounted systems...
1412 my $quoted = PVE
::Tools
::shellquote
($path);
1413 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1415 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1417 warn "Failed to update the container's filesystem: $@\n" if $@;
1420 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1421 PVE
::Tools
::run_command
(['resize2fs', $path]);
1423 warn "Failed to update the container's filesystem: $@\n" if $@;
1428 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1431 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;