use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
use PVE::Network;
use PVE::AccessControl;
+use PVE::ProcFSTools;
use Data::Dumper;
enum => ['shell', 'console', 'tty'],
default => 'tty',
},
+ protection => {
+ optional => 1,
+ type => 'boolean',
+ description => "Sets the protection flag of the container. This will prevent the remove operation.",
+ default => 0,
+ },
};
my $valid_lxc_conf_keys = {
return int($size);
};
+my $format_size = sub {
+ my ($size) = @_;
+
+ $size = int($size);
+
+ my $kb = int($size/1024);
+ return $size if $kb*1024 != $size;
+
+ my $mb = int($kb/1024);
+ return "${kb}K" if $mb*1024 != $kb;
+
+ my $gb = int($mb/1024);
+ return "${mb}M" if $gb*1024 != $mb;
+
+ return "${gb}G";
+};
+
sub parse_ct_mountpoint {
my ($data) = @_;
}
sub print_ct_mountpoint {
- my ($info) = @_;
+ my ($info, $nomp) = @_;
my $opts = '';
die "missing volume\n" if !$info->{volume};
- foreach my $o ('size', 'backup') {
+ foreach my $o (qw(backup)) {
$opts .= ",$o=$info->{$o}" if defined($info->{$o});
}
+ if ($info->{size}) {
+ $opts .= ",size=" . &$format_size($info->{size});
+ }
+
+ $opts .= ",mp=$info->{mp}" if !$nomp;
+
return "$info->{volume}$opts";
}
my @nohotplug;
+ my $new_disks = [];
+
my $rootdir;
if ($running) {
my $pid = find_lxc_pid($vmid);
next if !$running;
my $netid = $1;
PVE::Network::veth_delete("veth${vmid}i$netid");
- } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
+ } elsif ($opt eq 'protection') {
+ delete $conf->{$opt};
+ } elsif ($opt =~ m/^mp(\d+)$/) {
+ delete $conf->{$opt};
+ push @nohotplug, $opt;
+ next if $running;
+ } elsif ($opt eq 'rootfs') {
die "implement me"
} else {
die "implement me"
} else {
update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
}
- } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
+ } elsif ($opt eq 'protection') {
+ $conf->{$opt} = $value ? 1 : 0;
+ } elsif ($opt =~ m/^mp(\d+)$/) {
+ $conf->{$opt} = $value;
+ push @$new_disks, $opt;
+ push @nohotplug, $opt;
+ next;
+ } elsif ($opt eq 'rootfs') {
die "implement me: $opt";
} else {
die "implement me: $opt";
if ($running && scalar(@nohotplug)) {
die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
}
+
+ if (@$new_disks) {
+ my $storage_cfg = PVE::Storage::config();
+ create_disks($storage_cfg, $vmid, $conf, $conf);
+ mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
+ umount_all($vmid, $storage_cfg, $conf, 0);
+ }
}
sub has_dev_console {
if (!$keepActive) {
my $vollist = get_vm_volumes($conf);
- my $loopdevlist = get_vm_volumes($conf, 'rootfs');
-
- dettach_loops($storage_cfg, $loopdevlist);
PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
}
};
my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
$rootinfo->{volume} = $template_volid;
- $conf->{rootfs} = print_ct_mountpoint($rootinfo);
+ $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
write_config($vmid, $conf);
}
foreach_mountpoint_full($conf, 1, $func);
}
-sub loopdevices_list {
-
- my $loopdev = {};
- my $parser = sub {
- my $line = shift;
- if ($line =~ m/^(\/dev\/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
- $loopdev->{$1} = $2;
- }
- };
-
- PVE::Tools::run_command(['losetup'], outfunc => $parser);
-
- return $loopdev;
-}
-
-sub blockdevices_list {
-
- my $bdevs = {};
- dir_glob_foreach("/sys/dev/block/", '(\d+):(\d+)', sub {
- my (undef, $major, $minor) = @_;
- my $bdev = readlink("/sys/dev/block/$major:$minor");
- $bdev =~ s/\.\.\/\.\.\/devices\/virtual\/block\//\/dev\//;
- $bdevs->{$bdev}->{major} = $major;
- $bdevs->{$bdev}->{minor} = $minor;
- });
- return $bdevs;
-}
-
-sub find_loopdev {
- my ($loopdevs, $path) = @_;
-
- foreach my $dev (keys %$loopdevs){
- return $dev if $loopdevs->{$dev} eq $path;
- }
-}
-
sub check_ct_modify_config_perm {
my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
return 1;
}
-sub attach_loops {
- my ($storage_cfg, $vollist, $snapname) = @_;
-
- my $loopdevs = {};
-
- foreach my $volid (@$vollist) {
-
- my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
- my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
-
- my ($vtype, undef, undef, undef, undef, $isBase, $format) =
- PVE::Storage::parse_volname($storage_cfg, $volid);
-
- if ($format eq 'raw' && $scfg->{path}) {
- my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
- my $loopdev;
-
- my $parser = sub {
- my $line = shift;
- $loopdev = $line if $line =~m|^/dev/loop\d+$|;
- $loopdevs->{$loopdev} = $path;
- };
-
- PVE::Tools::run_command(['losetup', '--find', '--show', $path], outfunc => $parser);
- }
- }
-
- return $loopdevs;
-}
-
-sub dettach_loops {
- my ($storage_cfg, $vollist, $snapname) = @_;
-
- my $loopdevs = loopdevices_list();
-
- foreach my $volid (@$vollist) {
-
- my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
- my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
-
- my ($vtype, undef, undef, undef, undef, $isBase, $format) =
- PVE::Storage::parse_volname($storage_cfg, $volid);
-
- if ($format eq 'raw' && $scfg->{path}) {
- my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
- foreach my $dev (keys %$loopdevs){
- PVE::Tools::run_command(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
- }
- }
- }
-}
-
sub umount_all {
my ($vmid, $storage_cfg, $conf, $noerr) = @_;
- my $loopdevs = loopdevices_list();
-
my $rootdir = "/var/lib/lxc/$vmid/rootfs";
my $volid_list = get_vm_volumes($conf);
my $mount_path = "$rootdir/$mount";
$mount_path =~ s!/+!/!g;
- # fixme: test if mounted?
+ return if !PVE::ProcFSTools::is_mounted($mount_path);
+
eval {
PVE::Tools::run_command(['umount', '-d', $mount_path]);
};
}
}
});
-
- PVE::LXC::dettach_loops($storage_cfg, $volid_list);
}
sub mount_all {
- my ($vmid, $storage_cfg, $conf, $format_raw_images) = @_;
+ my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
my $rootdir = "/var/lib/lxc/$vmid/rootfs";
+ File::Path::make_path($rootdir);
my $volid_list = get_vm_volumes($conf);
PVE::Storage::activate_volumes($storage_cfg, $volid_list);
eval {
- my $loopdevs = attach_loops($storage_cfg, $volid_list);
-
foreach_mountpoint($conf, sub {
my ($ms, $mountpoint) = @_;
die "unable to mount base volume - internal error" if $isBase;
- if ($format_raw_images && $format eq 'raw') {
- my $cmd = ['mkfs.ext4', '-O', 'mmp', $image_path];
- PVE::Tools::run_command($cmd);
- }
-
- mountpoint_mount($mountpoint, $rootdir, $storage_cfg, $loopdevs);
+ File::Path::make_path "$rootdir/$mount" if $mkdirs;
+ mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
});
};
if (my $err = $@) {
sub mountpoint_mount_path {
- my ($mountpoint, $storage_cfg, $loopdevs, $snapname) = @_;
+ my ($mountpoint, $storage_cfg, $snapname) = @_;
- return mountpoint_mount($mountpoint, undef, $storage_cfg, $loopdevs, $snapname);
+ return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
}
# use $rootdir = undef to just return the corresponding mount path
sub mountpoint_mount {
- my ($mountpoint, $rootdir, $storage_cfg, $loopdevs, $snapname) = @_;
+ my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
my $volid = $mountpoint->{volume};
my $mount = $mountpoint->{mp};
my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
+ return $path if !$mount_path;
my ($vtype, undef, undef, undef, undef, $isBase, $format) =
PVE::Storage::parse_volname($storage_cfg, $volid);
if ($format eq 'subvol') {
-
- if ($mount_path) {
- if ($snapname) {
- if ($scfg->{type} eq 'zfspool') {
- my $path_arg = $path;
- $path_arg =~ s!^/+!!;
- PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
- } else {
- die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
- }
+ if ($snapname) {
+ if ($scfg->{type} eq 'zfspool') {
+ my $path_arg = $path;
+ $path_arg =~ s!^/+!!;
+ PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
} else {
- PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
- }
+ die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
+ }
+ } else {
+ PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
}
return $path;
-
} elsif ($format eq 'raw') {
-
+ my @extra_opts;
if ($scfg->{path}) {
- $path = find_loopdev($loopdevs, $path) if $loopdevs;
+ push @extra_opts, '-o', 'loop';
} elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
# do nothing
} else {
die "unsupported storage type '$scfg->{type}'\n";
}
- if ($mount_path) {
- if ($isBase || defined($snapname)) {
- PVE::Tools::run_command(['mount', '-o', 'ro', $path, $mount_path]);
- } else {
- PVE::Tools::run_command(['mount', $path, $mount_path]);
- }
+ if ($isBase || defined($snapname)) {
+ PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
+ } else {
+ PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
}
return $path;
} else {
return $vollist;
}
+sub mkfs {
+ my ($dev) = @_;
+
+ PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
+}
+
+sub format_disk {
+ my ($storage_cfg, $volid) = @_;
+
+ if ($volid =~ m!^/dev/.+!) {
+ mkfs($volid);
+ return;
+ }
+
+ my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+ die "cannot format volume '$volid' with no storage\n" if !$storage;
+
+ my $path = PVE::Storage::path($storage_cfg, $volid);
+
+ my ($vtype, undef, undef, undef, undef, $isBase, $format) =
+ PVE::Storage::parse_volname($storage_cfg, $volid);
+
+ die "cannot format volume '$volid' (format == $format)\n"
+ if $format ne 'raw';
+
+ mkfs($path);
+}
+
+sub destroy_disks {
+ my ($storecfg, $vollist) = @_;
+
+ foreach my $volid (@$vollist) {
+ eval { PVE::Storage::vdisk_free($storecfg, $volid); };
+ warn $@ if $@;
+ }
+}
+
+sub create_disks {
+ my ($storecfg, $vmid, $settings, $conf) = @_;
+
+ my $vollist = [];
+
+ eval {
+ foreach_mountpoint($settings, sub {
+ my ($ms, $mountpoint) = @_;
+
+ my $volid = $mountpoint->{volume};
+ my $mp = $mountpoint->{mp};
+
+ my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+
+ return if !$storage;
+
+ if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
+ my ($storeid, $size_gb) = ($1, $2);
+
+ my $size_kb = int(${size_gb}*1024) * 1024;
+
+ my $scfg = PVE::Storage::storage_config($storecfg, $storage);
+ # fixme: use better naming ct-$vmid-disk-X.raw?
+
+ if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
+ if ($size_kb > 0) {
+ $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
+ undef, $size_kb);
+ format_disk($storecfg, $volid);
+ } else {
+ $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+ undef, 0);
+ }
+ } elsif ($scfg->{type} eq 'zfspool') {
+
+ $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
+ undef, $size_kb);
+ } elsif ($scfg->{type} eq 'drbd') {
+
+ $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+ format_disk($storecfg, $volid);
+
+ } elsif ($scfg->{type} eq 'rbd') {
+
+ die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
+ $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
+ format_disk($storecfg, $volid);
+ } else {
+ die "unable to create containers on storage type '$scfg->{type}'\n";
+ }
+ push @$vollist, $volid;
+ my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
+ $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
+ } else {
+ # use specified/existing volid
+ }
+ });
+ };
+ # free allocated images on error
+ if (my $err = $@) {
+ destroy_disks($storecfg, $vollist);
+ die $err;
+ }
+ return $vollist;
+}
+
# bash completion helper
sub complete_os_templates {
my $cfg = PVE::Storage::config();
- my $storeid;
+ my $storeid;
if ($cvalue =~ m/^([^:]+):/) {
$storeid = $1;
return $res;
}
-sub complete_migration_target {
-
- my $res = [];
-
- my $nodelist = PVE::Cluster::get_nodelist();
- foreach my $node (@$nodelist) {
- next if $node eq $nodename;
- push @$res, $node;
- }
-
- return $res;
-}
-
-sub complete_next_vmid {
-
- my $vmlist = PVE::Cluster::get_vmlist() || {};
- my $idlist = $vmlist->{ids} || {};
-
- for (my $i = 100; $i < 10000; $i++) {
- return [$i] if !defined($idlist->{$i});
- }
-
- return [];
-}
-
my $complete_ctid_full = sub {
my ($running) = @_;