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.",
153 my $rpcenv = PVE
::RPCEnvironment
::get
();
155 my $authuser = $rpcenv->get_user();
157 my $node = extract_param
($param, 'node');
159 my $vmid = extract_param
($param, 'vmid');
161 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
163 my $same_container_exists = -f
$basecfg_fn;
165 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
166 my $unprivileged = extract_param
($param, 'unprivileged');
168 my $restore = extract_param
($param, 'restore');
171 # fixme: limit allowed parameters
175 my $force = extract_param
($param, 'force');
177 if (!($same_container_exists && $restore && $force)) {
178 PVE
::Cluster
::check_vmid_unused
($vmid);
180 my $conf = PVE
::LXC
::load_config
($vmid);
181 PVE
::LXC
::check_protection
($conf, "unable to restore CT $vmid");
184 my $password = extract_param
($param, 'password');
186 my $pool = extract_param
($param, 'pool');
188 if (defined($pool)) {
189 $rpcenv->check_pool_exist($pool);
190 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
193 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
195 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
197 } elsif ($restore && $force && $same_container_exists &&
198 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
199 # OK: user has VM.Backup permissions, and want to restore an existing VM
204 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
206 my $storage = extract_param
($param, 'storage') // 'local';
208 my $storage_cfg = cfs_read_file
("storage.cfg");
210 my $ostemplate = extract_param
($param, 'ostemplate');
214 if ($ostemplate eq '-') {
215 die "pipe requires cli environment\n"
216 if $rpcenv->{type
} ne 'cli';
217 die "pipe can only be used with restore tasks\n"
220 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
222 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
223 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
226 my $check_and_activate_storage = sub {
229 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
231 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
232 if !$scfg->{content
}->{rootdir
};
234 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
236 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
241 my $no_disk_param = {};
242 foreach my $opt (keys %$param) {
243 my $value = $param->{$opt};
244 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
245 # allow to use simple numbers (add default storage in that case)
246 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
248 $no_disk_param->{$opt} = $value;
252 # check storage access, activate storage
253 PVE
::LXC
::foreach_mountpoint
($param, sub {
254 my ($ms, $mountpoint) = @_;
256 my $volid = $mountpoint->{volume
};
257 my $mp = $mountpoint->{mp
};
259 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
261 &$check_and_activate_storage($sid) if $sid;
264 # check/activate default storage
265 &$check_and_activate_storage($storage) if !defined($param->{rootfs
});
267 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $no_disk_param);
269 $conf->{unprivileged
} = 1 if $unprivileged;
271 my $check_vmid_usage = sub {
273 die "can't overwrite running container\n"
274 if PVE
::LXC
::check_running
($vmid);
276 PVE
::Cluster
::check_vmid_unused
($vmid);
281 &$check_vmid_usage(); # final check after locking
283 PVE
::Cluster
::check_cfs_quorum
();
287 if (!defined($param->{rootfs
})) {
289 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
290 die "unable to detect disk size - please specify rootfs (size)\n"
292 $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size
293 $param->{rootfs
} = "$storage:$disksize";
295 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
299 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
301 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore);
303 $conf->{hostname
} ||= "CT$vmid";
304 $conf->{memory
} ||= 512;
305 $conf->{swap
} //= 512;
306 PVE
::LXC
::create_config
($vmid, $conf);
309 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
310 PVE
::LXC
::destroy_config
($vmid);
313 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
316 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
318 &$check_vmid_usage(); # first check before locking
320 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
321 $vmid, $authuser, $realcmd);
325 __PACKAGE__-
>register_method({
330 description
=> "Directory index",
335 additionalProperties
=> 0,
337 node
=> get_standard_option
('pve-node'),
338 vmid
=> get_standard_option
('pve-vmid'),
346 subdir
=> { type
=> 'string' },
349 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
355 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
358 { subdir
=> 'config' },
359 { subdir
=> 'status' },
360 { subdir
=> 'vncproxy' },
361 { subdir
=> 'vncwebsocket' },
362 { subdir
=> 'spiceproxy' },
363 { subdir
=> 'migrate' },
364 # { subdir => 'initlog' },
366 { subdir
=> 'rrddata' },
367 { subdir
=> 'firewall' },
368 { subdir
=> 'snapshot' },
369 { subdir
=> 'resize' },
375 __PACKAGE__-
>register_method({
377 path
=> '{vmid}/rrd',
379 protected
=> 1, # fixme: can we avoid that?
381 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
383 description
=> "Read VM RRD statistics (returns PNG)",
385 additionalProperties
=> 0,
387 node
=> get_standard_option
('pve-node'),
388 vmid
=> get_standard_option
('pve-vmid'),
390 description
=> "Specify the time frame you are interested in.",
392 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
395 description
=> "The list of datasources you want to display.",
396 type
=> 'string', format
=> 'pve-configid-list',
399 description
=> "The RRD consolidation function",
401 enum
=> [ 'AVERAGE', 'MAX' ],
409 filename
=> { type
=> 'string' },
415 return PVE
::Cluster
::create_rrd_graph
(
416 "pve2-vm/$param->{vmid}", $param->{timeframe
},
417 $param->{ds
}, $param->{cf
});
421 __PACKAGE__-
>register_method({
423 path
=> '{vmid}/rrddata',
425 protected
=> 1, # fixme: can we avoid that?
427 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
429 description
=> "Read VM RRD statistics",
431 additionalProperties
=> 0,
433 node
=> get_standard_option
('pve-node'),
434 vmid
=> get_standard_option
('pve-vmid'),
436 description
=> "Specify the time frame you are interested in.",
438 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
441 description
=> "The RRD consolidation function",
443 enum
=> [ 'AVERAGE', 'MAX' ],
458 return PVE
::Cluster
::create_rrd_data
(
459 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
462 __PACKAGE__-
>register_method({
463 name
=> 'destroy_vm',
468 description
=> "Destroy the container (also delete all uses files).",
470 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
473 additionalProperties
=> 0,
475 node
=> get_standard_option
('pve-node'),
476 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
485 my $rpcenv = PVE
::RPCEnvironment
::get
();
487 my $authuser = $rpcenv->get_user();
489 my $vmid = $param->{vmid
};
491 # test if container exists
492 my $conf = PVE
::LXC
::load_config
($vmid);
494 my $storage_cfg = cfs_read_file
("storage.cfg");
496 PVE
::LXC
::check_protection
($conf, "can't remove CT $vmid");
498 die "unable to remove CT $vmid - used in HA resources\n"
499 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
501 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
503 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
506 # reload config after lock
507 $conf = PVE
::LXC
::load_config
($vmid);
508 PVE
::LXC
::check_lock
($conf);
510 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
512 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
513 PVE
::AccessControl
::remove_vm_access
($vmid);
514 PVE
::Firewall
::remove_vmfw_conf
($vmid);
517 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
519 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
524 __PACKAGE__-
>register_method ({
526 path
=> '{vmid}/vncproxy',
530 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
532 description
=> "Creates a TCP VNC proxy connections.",
534 additionalProperties
=> 0,
536 node
=> get_standard_option
('pve-node'),
537 vmid
=> get_standard_option
('pve-vmid'),
541 description
=> "use websocket instead of standard VNC.",
546 additionalProperties
=> 0,
548 user
=> { type
=> 'string' },
549 ticket
=> { type
=> 'string' },
550 cert
=> { type
=> 'string' },
551 port
=> { type
=> 'integer' },
552 upid
=> { type
=> 'string' },
558 my $rpcenv = PVE
::RPCEnvironment
::get
();
560 my $authuser = $rpcenv->get_user();
562 my $vmid = $param->{vmid
};
563 my $node = $param->{node
};
565 my $authpath = "/vms/$vmid";
567 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
569 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
572 my ($remip, $family);
574 if ($node ne PVE
::INotify
::nodename
()) {
575 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
577 $family = PVE
::Tools
::get_host_address_family
($node);
580 my $port = PVE
::Tools
::next_vnc_port
($family);
582 # NOTE: vncterm VNC traffic is already TLS encrypted,
583 # so we select the fastest chipher here (or 'none'?)
584 my $remcmd = $remip ?
585 ['/usr/bin/ssh', '-t', $remip] : [];
587 my $conf = PVE
::LXC
::load_config
($vmid, $node);
588 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
590 my $shcmd = [ '/usr/bin/dtach', '-A',
591 "/var/run/dtach/vzctlconsole$vmid",
592 '-r', 'winch', '-z', @$concmd];
597 syslog
('info', "starting lxc vnc proxy $upid\n");
601 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
602 '-timeout', $timeout, '-authpath', $authpath,
603 '-perm', 'VM.Console'];
605 if ($param->{websocket
}) {
606 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
607 push @$cmd, '-notls', '-listen', 'localhost';
610 push @$cmd, '-c', @$remcmd, @$shcmd;
617 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
619 PVE
::Tools
::wait_for_vnc_port
($port);
630 __PACKAGE__-
>register_method({
631 name
=> 'vncwebsocket',
632 path
=> '{vmid}/vncwebsocket',
635 description
=> "You also need to pass a valid ticket (vncticket).",
636 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
638 description
=> "Opens a weksocket for VNC traffic.",
640 additionalProperties
=> 0,
642 node
=> get_standard_option
('pve-node'),
643 vmid
=> get_standard_option
('pve-vmid'),
645 description
=> "Ticket from previous call to vncproxy.",
650 description
=> "Port number returned by previous vncproxy call.",
660 port
=> { type
=> 'string' },
666 my $rpcenv = PVE
::RPCEnvironment
::get
();
668 my $authuser = $rpcenv->get_user();
670 my $authpath = "/vms/$param->{vmid}";
672 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
674 my $port = $param->{port
};
676 return { port
=> $port };
679 __PACKAGE__-
>register_method ({
680 name
=> 'spiceproxy',
681 path
=> '{vmid}/spiceproxy',
686 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
688 description
=> "Returns a SPICE configuration to connect to the CT.",
690 additionalProperties
=> 0,
692 node
=> get_standard_option
('pve-node'),
693 vmid
=> get_standard_option
('pve-vmid'),
694 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
697 returns
=> get_standard_option
('remote-viewer-config'),
701 my $vmid = $param->{vmid
};
702 my $node = $param->{node
};
703 my $proxy = $param->{proxy
};
705 my $authpath = "/vms/$vmid";
706 my $permissions = 'VM.Console';
708 my $conf = PVE
::LXC
::load_config
($vmid);
710 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
712 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
714 my $shcmd = ['/usr/bin/dtach', '-A',
715 "/var/run/dtach/vzctlconsole$vmid",
716 '-r', 'winch', '-z', @$concmd];
718 my $title = "CT $vmid";
720 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
724 __PACKAGE__-
>register_method({
725 name
=> 'migrate_vm',
726 path
=> '{vmid}/migrate',
730 description
=> "Migrate the container to another node. Creates a new migration task.",
732 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
738 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
739 target
=> get_standard_option
('pve-node', {
740 description
=> "Target node.",
741 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
745 description
=> "Use online/live migration.",
752 description
=> "the task ID.",
757 my $rpcenv = PVE
::RPCEnvironment
::get
();
759 my $authuser = $rpcenv->get_user();
761 my $target = extract_param
($param, 'target');
763 my $localnode = PVE
::INotify
::nodename
();
764 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
766 PVE
::Cluster
::check_cfs_quorum
();
768 PVE
::Cluster
::check_node_exists
($target);
770 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
772 my $vmid = extract_param
($param, 'vmid');
775 PVE
::LXC
::load_config
($vmid);
777 # try to detect errors early
778 if (PVE
::LXC
::check_running
($vmid)) {
779 die "can't migrate running container without --online\n"
780 if !$param->{online
};
783 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
788 my $service = "ct:$vmid";
790 my $cmd = ['ha-manager', 'migrate', $service, $target];
792 print "Executing HA migrate for CT $vmid to node $target\n";
794 PVE
::Tools
::run_command
($cmd);
799 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
806 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
811 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
815 __PACKAGE__-
>register_method({
816 name
=> 'vm_feature',
817 path
=> '{vmid}/feature',
821 description
=> "Check if feature for virtual machine is available.",
823 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
826 additionalProperties
=> 0,
828 node
=> get_standard_option
('pve-node'),
829 vmid
=> get_standard_option
('pve-vmid'),
831 description
=> "Feature to check.",
833 enum
=> [ 'snapshot' ],
835 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
843 hasFeature
=> { type
=> 'boolean' },
846 #items => { type => 'string' },
853 my $node = extract_param
($param, 'node');
855 my $vmid = extract_param
($param, 'vmid');
857 my $snapname = extract_param
($param, 'snapname');
859 my $feature = extract_param
($param, 'feature');
861 my $conf = PVE
::LXC
::load_config
($vmid);
864 my $snap = $conf->{snapshots
}->{$snapname};
865 die "snapshot '$snapname' does not exist\n" if !defined($snap);
868 my $storage_cfg = PVE
::Storage
::config
();
870 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
871 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
874 hasFeature
=> $hasFeature,
875 #nodes => [ keys %$nodelist ],
879 __PACKAGE__-
>register_method({
881 path
=> '{vmid}/template',
885 description
=> "Create a Template.",
887 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
888 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
891 additionalProperties
=> 0,
893 node
=> get_standard_option
('pve-node'),
894 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
897 returns
=> { type
=> 'null'},
901 my $rpcenv = PVE
::RPCEnvironment
::get
();
903 my $authuser = $rpcenv->get_user();
905 my $node = extract_param
($param, 'node');
907 my $vmid = extract_param
($param, 'vmid');
911 my $conf = PVE
::LXC
::load_config
($vmid);
912 PVE
::LXC
::check_lock
($conf);
914 die "unable to create template, because CT contains snapshots\n"
915 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
917 die "you can't convert a template to a template\n"
918 if PVE
::LXC
::is_template
($conf);
920 die "you can't convert a CT to template if the CT is running\n"
921 if PVE
::LXC
::check_running
($vmid);
924 PVE
::LXC
::template_create
($vmid, $conf);
927 $conf->{template
} = 1;
929 PVE
::LXC
::write_config
($vmid, $conf);
930 # and remove lxc config
931 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
933 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
936 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);
941 __PACKAGE__-
>register_method({
943 path
=> '{vmid}/resize',
947 description
=> "Resize a container mountpoint.",
949 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
952 additionalProperties
=> 0,
954 node
=> get_standard_option
('pve-node'),
955 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
958 description
=> "The disk you want to resize.",
959 enum
=> [PVE
::LXC
::mountpoint_names
()],
963 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
964 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.",
968 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
976 description
=> "the task ID.",
981 my $rpcenv = PVE
::RPCEnvironment
::get
();
983 my $authuser = $rpcenv->get_user();
985 my $node = extract_param
($param, 'node');
987 my $vmid = extract_param
($param, 'vmid');
989 my $digest = extract_param
($param, 'digest');
991 my $sizestr = extract_param
($param, 'size');
992 my $ext = ($sizestr =~ s/^\+//);
993 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
994 die "invalid size string" if !defined($newsize);
996 die "no options specified\n" if !scalar(keys %$param);
998 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1000 my $storage_cfg = cfs_read_file
("storage.cfg");
1002 my $query_loopdev = sub {
1007 if ($line =~ m
@^(/dev/loop\d
+):@) {
1011 my $cmd = ['losetup', '--associated', $path];
1012 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
1018 my $conf = PVE
::LXC
::load_config
($vmid);
1019 PVE
::LXC
::check_lock
($conf);
1021 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1023 my $running = PVE
::LXC
::check_running
($vmid);
1025 my $disk = $param->{disk
};
1026 my $mp = PVE
::LXC
::parse_ct_mountpoint
($conf->{$disk});
1027 my $volid = $mp->{volume
};
1029 my (undef, undef, $owner, undef, undef, undef, $format) =
1030 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1032 die "can't resize mountpoint owned by another container ($owner)"
1035 die "can't resize volume: $disk if snapshot exists\n"
1036 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1038 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1040 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1042 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1043 $newsize += $size if $ext;
1044 $newsize = int($newsize);
1046 die "unable to shrink disk size\n" if $newsize < $size;
1048 return if $size == $newsize;
1050 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1052 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1053 # we pass 0 here (parameter only makes sense for qemu)
1054 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1056 $mp->{size
} = $newsize;
1057 $conf->{$disk} = PVE
::LXC
::print_ct_mountpoint
($mp, $disk eq 'rootfs');
1059 PVE
::LXC
::write_config
($vmid, $conf);
1061 if ($format eq 'raw') {
1062 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1066 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1067 $path = &$query_loopdev($path) if $use_loopdev;
1068 die "internal error: CT running but mountpoint not attached to a loop device"
1070 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1072 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1073 # to be visible to it in its namespace.
1074 # To not interfere with the rest of the system we unshare the current mount namespace,
1075 # mount over /tmp and then run resize2fs.
1077 # interestingly we don't need to e2fsck on mounted systems...
1078 my $quoted = PVE
::Tools
::shellquote
($path);
1079 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1080 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1082 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1083 PVE
::Tools
::run_command
(['resize2fs', $path]);
1088 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1091 return PVE
::LXC
::lock_container
($vmid, undef, $code);;