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
;
23 use PVE
::JSONSchema
qw(get_standard_option);
24 use base
qw(PVE::RESTHandler);
26 use Data
::Dumper
; # fixme: remove
28 __PACKAGE__-
>register_method ({
29 subclass
=> "PVE::API2::LXC::Config",
30 path
=> '{vmid}/config',
33 __PACKAGE__-
>register_method ({
34 subclass
=> "PVE::API2::LXC::Status",
35 path
=> '{vmid}/status',
38 __PACKAGE__-
>register_method ({
39 subclass
=> "PVE::API2::LXC::Snapshot",
40 path
=> '{vmid}/snapshot',
43 __PACKAGE__-
>register_method ({
44 subclass
=> "PVE::API2::Firewall::CT",
45 path
=> '{vmid}/firewall',
48 __PACKAGE__-
>register_method({
52 description
=> "LXC container index (per node).",
54 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
58 protected
=> 1, # /proc files are only readable by root
60 additionalProperties
=> 0,
62 node
=> get_standard_option
('pve-node'),
71 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
76 my $rpcenv = PVE
::RPCEnvironment
::get
();
77 my $authuser = $rpcenv->get_user();
79 my $vmstatus = PVE
::LXC
::vmstatus
();
82 foreach my $vmid (keys %$vmstatus) {
83 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
85 my $data = $vmstatus->{$vmid};
86 $data->{vmid
} = $vmid;
94 __PACKAGE__-
>register_method({
98 description
=> "Create or restore a container.",
100 user
=> 'all', # check inside
101 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
102 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
103 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
108 additionalProperties
=> 0,
109 properties
=> PVE
::LXC
::json_config_properties
({
110 node
=> get_standard_option
('pve-node'),
111 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
113 description
=> "The OS template or backup file.",
116 completion
=> \
&PVE
::LXC
::complete_os_templates
,
121 description
=> "Sets root password inside container.",
124 storage
=> get_standard_option
('pve-storage-id', {
125 description
=> "Default Storage.",
128 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
133 description
=> "Allow to overwrite existing container.",
138 description
=> "Mark this as restore task.",
142 type
=> 'string', format
=> 'pve-poolid',
143 description
=> "Add the VM to the specified pool.",
145 'ignore-unpack-errors' => {
148 description
=> "Ignore errors when extracting the template.",
158 my $rpcenv = PVE
::RPCEnvironment
::get
();
160 my $authuser = $rpcenv->get_user();
162 my $node = extract_param
($param, 'node');
164 my $vmid = extract_param
($param, 'vmid');
166 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
168 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
170 my $same_container_exists = -f
$basecfg_fn;
172 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
173 my $unprivileged = extract_param
($param, 'unprivileged');
175 my $restore = extract_param
($param, 'restore');
178 # fixme: limit allowed parameters
182 my $force = extract_param
($param, 'force');
184 if (!($same_container_exists && $restore && $force)) {
185 PVE
::Cluster
::check_vmid_unused
($vmid);
187 my $conf = PVE
::LXC
::load_config
($vmid);
188 PVE
::LXC
::check_protection
($conf, "unable to restore CT $vmid");
191 my $password = extract_param
($param, 'password');
193 my $pool = extract_param
($param, 'pool');
195 if (defined($pool)) {
196 $rpcenv->check_pool_exist($pool);
197 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
200 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
202 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
204 } elsif ($restore && $force && $same_container_exists &&
205 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
206 # OK: user has VM.Backup permissions, and want to restore an existing VM
211 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
213 my $storage = extract_param
($param, 'storage') // 'local';
215 my $storage_cfg = cfs_read_file
("storage.cfg");
217 my $ostemplate = extract_param
($param, 'ostemplate');
221 if ($ostemplate eq '-') {
222 die "pipe requires cli environment\n"
223 if $rpcenv->{type
} ne 'cli';
224 die "pipe can only be used with restore tasks\n"
227 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
229 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
230 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
233 my $check_and_activate_storage = sub {
236 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
238 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
239 if !$scfg->{content
}->{rootdir
};
241 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
243 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
248 my $no_disk_param = {};
249 foreach my $opt (keys %$param) {
250 my $value = $param->{$opt};
251 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
252 # allow to use simple numbers (add default storage in that case)
253 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
255 $no_disk_param->{$opt} = $value;
259 # check storage access, activate storage
260 PVE
::LXC
::foreach_mountpoint
($param, sub {
261 my ($ms, $mountpoint) = @_;
263 my $volid = $mountpoint->{volume
};
264 my $mp = $mountpoint->{mp
};
266 if ($mountpoint->{type
} ne 'volume') { # bind or device
267 die "Only root can pass arbitrary filesystem paths.\n"
268 if $authuser ne 'root@pam';
270 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
271 &$check_and_activate_storage($sid);
275 # check/activate default storage
276 &$check_and_activate_storage($storage) if !defined($param->{rootfs
});
278 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $no_disk_param);
280 $conf->{unprivileged
} = 1 if $unprivileged;
282 my $check_vmid_usage = sub {
284 die "can't overwrite running container\n"
285 if PVE
::LXC
::check_running
($vmid);
287 PVE
::Cluster
::check_vmid_unused
($vmid);
292 &$check_vmid_usage(); # final check after locking
294 PVE
::Cluster
::check_cfs_quorum
();
298 if (!defined($param->{rootfs
})) {
300 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
301 die "unable to detect disk size - please specify rootfs (size)\n"
303 $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size
304 $param->{rootfs
} = "$storage:$disksize";
306 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
310 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
312 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore, $ignore_unpack_errors);
314 $conf->{hostname
} ||= "CT$vmid";
315 $conf->{memory
} ||= 512;
316 $conf->{swap
} //= 512;
317 PVE
::LXC
::create_config
($vmid, $conf);
320 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
321 PVE
::LXC
::destroy_config
($vmid);
324 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
327 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
329 &$check_vmid_usage(); # first check before locking
331 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
332 $vmid, $authuser, $realcmd);
336 __PACKAGE__-
>register_method({
341 description
=> "Directory index",
346 additionalProperties
=> 0,
348 node
=> get_standard_option
('pve-node'),
349 vmid
=> get_standard_option
('pve-vmid'),
357 subdir
=> { type
=> 'string' },
360 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
366 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
369 { subdir
=> 'config' },
370 { subdir
=> 'status' },
371 { subdir
=> 'vncproxy' },
372 { subdir
=> 'vncwebsocket' },
373 { subdir
=> 'spiceproxy' },
374 { subdir
=> 'migrate' },
375 # { subdir => 'initlog' },
377 { subdir
=> 'rrddata' },
378 { subdir
=> 'firewall' },
379 { subdir
=> 'snapshot' },
380 { subdir
=> 'resize' },
386 __PACKAGE__-
>register_method({
388 path
=> '{vmid}/rrd',
390 protected
=> 1, # fixme: can we avoid that?
392 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
394 description
=> "Read VM RRD statistics (returns PNG)",
396 additionalProperties
=> 0,
398 node
=> get_standard_option
('pve-node'),
399 vmid
=> get_standard_option
('pve-vmid'),
401 description
=> "Specify the time frame you are interested in.",
403 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
406 description
=> "The list of datasources you want to display.",
407 type
=> 'string', format
=> 'pve-configid-list',
410 description
=> "The RRD consolidation function",
412 enum
=> [ 'AVERAGE', 'MAX' ],
420 filename
=> { type
=> 'string' },
426 return PVE
::Cluster
::create_rrd_graph
(
427 "pve2-vm/$param->{vmid}", $param->{timeframe
},
428 $param->{ds
}, $param->{cf
});
432 __PACKAGE__-
>register_method({
434 path
=> '{vmid}/rrddata',
436 protected
=> 1, # fixme: can we avoid that?
438 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
440 description
=> "Read VM RRD statistics",
442 additionalProperties
=> 0,
444 node
=> get_standard_option
('pve-node'),
445 vmid
=> get_standard_option
('pve-vmid'),
447 description
=> "Specify the time frame you are interested in.",
449 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
452 description
=> "The RRD consolidation function",
454 enum
=> [ 'AVERAGE', 'MAX' ],
469 return PVE
::Cluster
::create_rrd_data
(
470 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
473 __PACKAGE__-
>register_method({
474 name
=> 'destroy_vm',
479 description
=> "Destroy the container (also delete all uses files).",
481 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
484 additionalProperties
=> 0,
486 node
=> get_standard_option
('pve-node'),
487 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
496 my $rpcenv = PVE
::RPCEnvironment
::get
();
498 my $authuser = $rpcenv->get_user();
500 my $vmid = $param->{vmid
};
502 # test if container exists
503 my $conf = PVE
::LXC
::load_config
($vmid);
505 my $storage_cfg = cfs_read_file
("storage.cfg");
507 PVE
::LXC
::check_protection
($conf, "can't remove CT $vmid");
509 die "unable to remove CT $vmid - used in HA resources\n"
510 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
512 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
514 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
517 # reload config after lock
518 $conf = PVE
::LXC
::load_config
($vmid);
519 PVE
::LXC
::check_lock
($conf);
521 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
523 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
524 PVE
::AccessControl
::remove_vm_access
($vmid);
525 PVE
::Firewall
::remove_vmfw_conf
($vmid);
528 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
530 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
535 __PACKAGE__-
>register_method ({
537 path
=> '{vmid}/vncproxy',
541 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
543 description
=> "Creates a TCP VNC proxy connections.",
545 additionalProperties
=> 0,
547 node
=> get_standard_option
('pve-node'),
548 vmid
=> get_standard_option
('pve-vmid'),
552 description
=> "use websocket instead of standard VNC.",
557 additionalProperties
=> 0,
559 user
=> { type
=> 'string' },
560 ticket
=> { type
=> 'string' },
561 cert
=> { type
=> 'string' },
562 port
=> { type
=> 'integer' },
563 upid
=> { type
=> 'string' },
569 my $rpcenv = PVE
::RPCEnvironment
::get
();
571 my $authuser = $rpcenv->get_user();
573 my $vmid = $param->{vmid
};
574 my $node = $param->{node
};
576 my $authpath = "/vms/$vmid";
578 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
580 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
583 my ($remip, $family);
585 if ($node ne PVE
::INotify
::nodename
()) {
586 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
588 $family = PVE
::Tools
::get_host_address_family
($node);
591 my $port = PVE
::Tools
::next_vnc_port
($family);
593 # NOTE: vncterm VNC traffic is already TLS encrypted,
594 # so we select the fastest chipher here (or 'none'?)
595 my $remcmd = $remip ?
596 ['/usr/bin/ssh', '-t', $remip] : [];
598 my $conf = PVE
::LXC
::load_config
($vmid, $node);
599 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
601 my $shcmd = [ '/usr/bin/dtach', '-A',
602 "/var/run/dtach/vzctlconsole$vmid",
603 '-r', 'winch', '-z', @$concmd];
608 syslog
('info', "starting lxc vnc proxy $upid\n");
612 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
613 '-timeout', $timeout, '-authpath', $authpath,
614 '-perm', 'VM.Console'];
616 if ($param->{websocket
}) {
617 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
618 push @$cmd, '-notls', '-listen', 'localhost';
621 push @$cmd, '-c', @$remcmd, @$shcmd;
628 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
630 PVE
::Tools
::wait_for_vnc_port
($port);
641 __PACKAGE__-
>register_method({
642 name
=> 'vncwebsocket',
643 path
=> '{vmid}/vncwebsocket',
646 description
=> "You also need to pass a valid ticket (vncticket).",
647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
649 description
=> "Opens a weksocket for VNC traffic.",
651 additionalProperties
=> 0,
653 node
=> get_standard_option
('pve-node'),
654 vmid
=> get_standard_option
('pve-vmid'),
656 description
=> "Ticket from previous call to vncproxy.",
661 description
=> "Port number returned by previous vncproxy call.",
671 port
=> { type
=> 'string' },
677 my $rpcenv = PVE
::RPCEnvironment
::get
();
679 my $authuser = $rpcenv->get_user();
681 my $authpath = "/vms/$param->{vmid}";
683 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
685 my $port = $param->{port
};
687 return { port
=> $port };
690 __PACKAGE__-
>register_method ({
691 name
=> 'spiceproxy',
692 path
=> '{vmid}/spiceproxy',
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
699 description
=> "Returns a SPICE configuration to connect to the CT.",
701 additionalProperties
=> 0,
703 node
=> get_standard_option
('pve-node'),
704 vmid
=> get_standard_option
('pve-vmid'),
705 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
708 returns
=> get_standard_option
('remote-viewer-config'),
712 my $vmid = $param->{vmid
};
713 my $node = $param->{node
};
714 my $proxy = $param->{proxy
};
716 my $authpath = "/vms/$vmid";
717 my $permissions = 'VM.Console';
719 my $conf = PVE
::LXC
::load_config
($vmid);
721 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
723 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
725 my $shcmd = ['/usr/bin/dtach', '-A',
726 "/var/run/dtach/vzctlconsole$vmid",
727 '-r', 'winch', '-z', @$concmd];
729 my $title = "CT $vmid";
731 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
735 __PACKAGE__-
>register_method({
736 name
=> 'migrate_vm',
737 path
=> '{vmid}/migrate',
741 description
=> "Migrate the container to another node. Creates a new migration task.",
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
746 additionalProperties
=> 0,
748 node
=> get_standard_option
('pve-node'),
749 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
750 target
=> get_standard_option
('pve-node', {
751 description
=> "Target node.",
752 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
756 description
=> "Use online/live migration.",
763 description
=> "the task ID.",
768 my $rpcenv = PVE
::RPCEnvironment
::get
();
770 my $authuser = $rpcenv->get_user();
772 my $target = extract_param
($param, 'target');
774 my $localnode = PVE
::INotify
::nodename
();
775 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
777 PVE
::Cluster
::check_cfs_quorum
();
779 PVE
::Cluster
::check_node_exists
($target);
781 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
783 my $vmid = extract_param
($param, 'vmid');
786 PVE
::LXC
::load_config
($vmid);
788 # try to detect errors early
789 if (PVE
::LXC
::check_running
($vmid)) {
790 die "can't migrate running container without --online\n"
791 if !$param->{online
};
794 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
799 my $service = "ct:$vmid";
801 my $cmd = ['ha-manager', 'migrate', $service, $target];
803 print "Executing HA migrate for CT $vmid to node $target\n";
805 PVE
::Tools
::run_command
($cmd);
810 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
817 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
822 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
826 __PACKAGE__-
>register_method({
827 name
=> 'vm_feature',
828 path
=> '{vmid}/feature',
832 description
=> "Check if feature for virtual machine is available.",
834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
837 additionalProperties
=> 0,
839 node
=> get_standard_option
('pve-node'),
840 vmid
=> get_standard_option
('pve-vmid'),
842 description
=> "Feature to check.",
844 enum
=> [ 'snapshot' ],
846 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
854 hasFeature
=> { type
=> 'boolean' },
857 #items => { type => 'string' },
864 my $node = extract_param
($param, 'node');
866 my $vmid = extract_param
($param, 'vmid');
868 my $snapname = extract_param
($param, 'snapname');
870 my $feature = extract_param
($param, 'feature');
872 my $conf = PVE
::LXC
::load_config
($vmid);
875 my $snap = $conf->{snapshots
}->{$snapname};
876 die "snapshot '$snapname' does not exist\n" if !defined($snap);
879 my $storage_cfg = PVE
::Storage
::config
();
881 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
882 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
885 hasFeature
=> $hasFeature,
886 #nodes => [ keys %$nodelist ],
890 __PACKAGE__-
>register_method({
892 path
=> '{vmid}/template',
896 description
=> "Create a Template.",
898 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
899 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
902 additionalProperties
=> 0,
904 node
=> get_standard_option
('pve-node'),
905 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
908 returns
=> { type
=> 'null'},
912 my $rpcenv = PVE
::RPCEnvironment
::get
();
914 my $authuser = $rpcenv->get_user();
916 my $node = extract_param
($param, 'node');
918 my $vmid = extract_param
($param, 'vmid');
922 my $conf = PVE
::LXC
::load_config
($vmid);
923 PVE
::LXC
::check_lock
($conf);
925 die "unable to create template, because CT contains snapshots\n"
926 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
928 die "you can't convert a template to a template\n"
929 if PVE
::LXC
::is_template
($conf);
931 die "you can't convert a CT to template if the CT is running\n"
932 if PVE
::LXC
::check_running
($vmid);
935 PVE
::LXC
::template_create
($vmid, $conf);
938 $conf->{template
} = 1;
940 PVE
::LXC
::write_config
($vmid, $conf);
941 # and remove lxc config
942 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
944 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
947 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);
952 __PACKAGE__-
>register_method({
954 path
=> '{vmid}/resize',
958 description
=> "Resize a container mountpoint.",
960 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
963 additionalProperties
=> 0,
965 node
=> get_standard_option
('pve-node'),
966 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
969 description
=> "The disk you want to resize.",
970 enum
=> [PVE
::LXC
::mountpoint_names
()],
974 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
975 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.",
979 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
987 description
=> "the task ID.",
992 my $rpcenv = PVE
::RPCEnvironment
::get
();
994 my $authuser = $rpcenv->get_user();
996 my $node = extract_param
($param, 'node');
998 my $vmid = extract_param
($param, 'vmid');
1000 my $digest = extract_param
($param, 'digest');
1002 my $sizestr = extract_param
($param, 'size');
1003 my $ext = ($sizestr =~ s/^\+//);
1004 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1005 die "invalid size string" if !defined($newsize);
1007 die "no options specified\n" if !scalar(keys %$param);
1009 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1011 my $storage_cfg = cfs_read_file
("storage.cfg");
1015 my $conf = PVE
::LXC
::load_config
($vmid);
1016 PVE
::LXC
::check_lock
($conf);
1018 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1020 my $running = PVE
::LXC
::check_running
($vmid);
1022 my $disk = $param->{disk
};
1023 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::parse_ct_rootfs
($conf->{$disk}) :
1024 PVE
::LXC
::parse_ct_mountpoint
($conf->{$disk});
1026 my $volid = $mp->{volume
};
1028 my (undef, undef, $owner, undef, undef, undef, $format) =
1029 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1031 die "can't resize mountpoint owned by another container ($owner)"
1034 die "can't resize volume: $disk if snapshot exists\n"
1035 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1037 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1039 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1041 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1042 $newsize += $size if $ext;
1043 $newsize = int($newsize);
1045 die "unable to shrink disk size\n" if $newsize < $size;
1047 return if $size == $newsize;
1049 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1051 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1052 # we pass 0 here (parameter only makes sense for qemu)
1053 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1055 $mp->{size
} = $newsize;
1056 $conf->{$disk} = PVE
::LXC
::print_ct_mountpoint
($mp, $disk eq 'rootfs');
1058 PVE
::LXC
::write_config
($vmid, $conf);
1060 if ($format eq 'raw') {
1061 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1065 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1066 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1067 die "internal error: CT running but mountpoint not attached to a loop device"
1069 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1071 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1072 # to be visible to it in its namespace.
1073 # To not interfere with the rest of the system we unshare the current mount namespace,
1074 # mount over /tmp and then run resize2fs.
1076 # interestingly we don't need to e2fsck on mounted systems...
1077 my $quoted = PVE
::Tools
::shellquote
($path);
1078 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1079 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1081 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1082 PVE
::Tools
::run_command
(['resize2fs', $path]);
1087 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1090 return PVE
::LXC
::lock_container
($vmid, undef, $code);;