/
bin
/
Upload File
HOME
#!/bin/bash #-- # Copyright 2014-2017 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #++ # Purpose: This script sets up the storage for container runtimes. # Author: Andy Grimm <agrimm@redhat.com> set -e # container-storage-setup version information _CSS_MAJOR_VERSION="0" _CSS_MINOR_VERSION="11" _CSS_SUBLEVEL="0" _CSS_EXTRA_VERSION="" _CSS_VERSION="${_CSS_MAJOR_VERSION}.${_CSS_MINOR_VERSION}.${_CSS_SUBLEVEL}" [ -n "$_CSS_EXTRA_VERSION" ] && _CSS_VERSION="${_CSS_VERSION}-${_CSS_EXTRA_VERSION}" # Locking related _LOCKFD=300 _LOCKDIR="/var/lock/container-storage-setup" _LOCKFILE="lock" _CONFIG_NAME="" _CONFIG_DIR="/var/lib/container-storage-setup/" # Partition type related _MAX_MBR_SIZE_BYTES="2199023255040" # Metadata related stuff _METADATA_VERSION=1 _INFILE_NAME="infile" _OUTFILE_NAME="outfile" _METAFILE_NAME="metadata" _STATUSFILE_NAME="status" # This section reads the config file $INPUTFILE # Read man page for a description of currently supported options: # 'man container-storage-setup' _DOCKER_ROOT_LV_NAME="docker-root-lv" _DOCKER_ROOT_DIR="/var/lib/docker" _DOCKER_METADATA_DIR="/var/lib/docker" DOCKER_ROOT_VOLUME_SIZE=40%FREE _DOCKER_COMPAT_MODE="" _STORAGE_IN_FILE="" _STORAGE_OUT_FILE="" _STORAGE_DRIVERS="devicemapper overlay overlay2" # Command related variables _COMMAND_LIST="create activate deactivate remove list export add-dev" _COMMAND="" _PIPE1=/run/css-$$-fifo1 _PIPE2=/run/css-$$-fifo2 _TEMPDIR=$(mktemp --tmpdir -d) # Keeps track of resolved device paths _DEVS_RESOLVED="" # Will have currently configured storage options in ${_STORAGE_OUT_FILE} _CURRENT_STORAGE_OPTIONS="" _STORAGE_OPTIONS="STORAGE_OPTIONS" # Keeps track of if we created a volume group or not. _VG_CREATED= get_docker_version() { local version # docker version command exits with error as daemon is not running at this # point of time. So continue despite the error. version=`docker version --format='{{.Client.Version}}' 2>/dev/null` || true echo $version } get_deferred_removal_string() { local version major minor if ! version=$(get_docker_version);then return 0 fi [ -z "$version" ] && return 0 major=$(echo $version | cut -d "." -f1) minor=$(echo $version | cut -d "." -f2) [ -z "$major" ] && return 0 [ -z "$minor" ] && return 0 # docker 1.7 onwards supports deferred device removal. Enable it. if [ $major -gt 1 ] || ([ $major -eq 1 ] && [ $minor -ge 7 ]);then echo "--storage-opt dm.use_deferred_removal=true" fi } get_deferred_deletion_string() { local version major minor if ! version=$(get_docker_version);then return 0 fi [ -z "$version" ] && return 0 major=$(echo $version | cut -d "." -f1) minor=$(echo $version | cut -d "." -f2) [ -z "$major" ] && return 0 [ -z "$minor" ] && return 0 if should_enable_deferred_deletion $major $minor; then echo "--storage-opt dm.use_deferred_deletion=true" fi } should_enable_deferred_deletion() { # docker 1.9 onwards supports deferred device deletion. Enable it. local major=$1 local minor=$2 if [ $major -lt 1 ] || ([ $major -eq 1 ] && [ $minor -lt 9 ]);then return 1 fi if platform_supports_deferred_deletion; then return 0 fi return 1 } platform_supports_deferred_deletion() { local deferred_deletion_supported=1 trap cleanup_pipes EXIT local child_exec="$_SRCDIR/css-child-read-write.sh" [ ! -x "$child_exec" ] && child_exec="/usr/share/container-storage-setup/css-child-read-write" if [ ! -x "$child_exec" ];then return 1 fi mkfifo $_PIPE1 mkfifo $_PIPE2 unshare -m ${child_exec} $_PIPE1 $_PIPE2 "$_TEMPDIR" & read -t 10 n <>$_PIPE1 if [ "$n" != "start" ];then return 1 fi rmdir $_TEMPDIR > /dev/null 2>&1 deferred_deletion_supported=$? echo "finish" > $_PIPE2 return $deferred_deletion_supported } cleanup_pipes(){ rm -f $_PIPE1 rm -f $_PIPE2 rmdir $_TEMPDIR 2>/dev/null } extra_options_has_dm_fs() { local option for option in ${EXTRA_STORAGE_OPTIONS}; do if grep -q "dm.fs=" <<< $option; then return 0 fi done return 1 } # Given a dm device name in /dev/mapper/ dir # (ex. /dev/mapper/docker-vg--docker-pool), get associated volume group get_dmdev_vg() { local dmdev=${1##"/dev/mapper/"} local vg vg=`dmsetup splitname $dmdev --noheadings | cut -d ":" -f1` echo $vg } # Wait for a device for certain time interval. If device is found 0 is # returned otherwise 1. wait_for_dev() { local devpath=$1 local timeout=$DEVICE_WAIT_TIMEOUT if [ -b "$devpath" ];then Info "Device node $devpath exists." return 0 fi if [ -z "$DEVICE_WAIT_TIMEOUT" ] || [ "$DEVICE_WAIT_TIMEOUT" == "0" ];then Info "Not waiting for device $devpath as DEVICE_WAIT_TIMEOUT=${DEVICE_WAIT_TIMEOUT}." return 0 fi while [ $timeout -gt 0 ]; do Info "Waiting for device $devpath to be available. Wait time remaining is $timeout seconds" if [ $timeout -le 5 ];then sleep $timeout else sleep 5 fi timeout=$((timeout-5)) if [ -b "$devpath" ]; then Info "Device node $devpath exists." return 0 fi done Info "Timed out waiting for device $devpath" return 1 } get_devicemapper_config_options() { local storage_options local dm_fs="--storage-opt dm.fs=xfs" # docker expects device mapper device and not lvm device. Do the conversion. eval $( lvs --nameprefixes --noheadings -o lv_name,kernel_major,kernel_minor $VG | while read line; do eval $line if [ "$LVM2_LV_NAME" = "$CONTAINER_THINPOOL" ]; then echo _POOL_DEVICE_PATH=/dev/mapper/$( cat /sys/dev/block/${LVM2_LV_KERNEL_MAJOR}:${LVM2_LV_KERNEL_MINOR}/dm/name ) fi done ) if extra_options_has_dm_fs; then # dm.fs option defined in ${EXTRA_STORAGE_OPTIONS} dm_fs="" fi storage_options="${_STORAGE_OPTIONS}=\"--storage-driver devicemapper ${dm_fs} --storage-opt dm.thinpooldev=$_POOL_DEVICE_PATH $(get_deferred_removal_string) $(get_deferred_deletion_string) ${EXTRA_STORAGE_OPTIONS}\"" echo $storage_options } get_config_options() { if [ "$1" == "devicemapper" ]; then get_devicemapper_config_options return $? fi echo "${_STORAGE_OPTIONS}=\"--storage-driver $1 ${EXTRA_STORAGE_OPTIONS}\"" return 0 } # Check if multiple overlay directories are supported and if overlay module # itself is supported on the system. can_mount_overlay() { local dir="/run/container-storage-setup/" local lower1="$dir/lower1" local lower2="$dir/lower2" local upper="$dir/upper" local work="$dir/work" local merged="$dir/merged" local cmd # Create multiple directories in /run if ! mkdir -p $dir; then Error "Failed to create directory $dir" return 1 fi cmd="mkdir -p $lower1 $lower2 $upper $work $merged" if ! $cmd; then Error "Failed to run $cmd" return 1 fi cmd="unshare -m mount -t overlay -o lowerdir=$lower1:$lower2,upperdir=$upper,workdir=$work none $merged" if ! $cmd; then Error "Failed to run $cmd" return 1 fi return 0 } is_xfs_ftype_enabled() { local mountroot=$1 local fstype fstype=$(stat -f -c '%T' $mountroot) [ "$fstype" != "xfs" ] && return 0 # For xfs, see https://bugzilla.redhat.com/show_bug.cgi?id=1288162#c8 if test "$(xfs_info $mountroot | grep -o 'ftype=[01]')" = "ftype=0"; then return 1 fi return 0 } write_storage_config_file () { local storage_driver=$1 local storage_out_file=$2 local storage_options if [ -z "$storage_driver" ];then touch "$storage_out_file" return 0 fi if ! storage_options=$(get_config_options $storage_driver); then return 1 fi cat <<EOF > ${storage_out_file}.tmp $storage_options EOF mv -Z ${storage_out_file}.tmp ${storage_out_file} } convert_size_in_bytes() { local size=$1 prefix suffix # if it is all numeric, it is valid as by default it will be MiB. if [[ $size =~ ^[[:digit:]]+$ ]]; then echo $(($size*1024*1024)) return 0 fi # supprt G, G[bB] or Gi[bB] inputs. prefix=${size%[bBsSkKmMgGtTpPeE]i[bB]} prefix=${prefix%[bBsSkKmMgGtTpPeE][bB]} prefix=${prefix%[bBsSkKmMgGtTpPeE]} # if prefix is not all numeric now, it is an error. if ! [[ $prefix =~ ^[[:digit:]]+$ ]]; then return 1 fi suffix=${data_size#$prefix} case $suffix in b*|B*) echo $prefix;; s*|S*) echo $(($prefix*512));; k*|K*) echo $(($prefix*2**10));; m*|M*) echo $(($prefix*2**20));; g*|G*) echo $(($prefix*2**30));; t*|T*) echo $(($prefix*2**40));; p*|P*) echo $(($prefix*2**50));; e*|E*) echo $(($prefix*2**60));; *) return 1;; esac } data_size_in_bytes() { local data_size=$1 local bytes vg_size free_space percent # -L compatible syntax if [[ $DATA_SIZE != *%* ]]; then bytes=`convert_size_in_bytes $data_size` [ $? -ne 0 ] && return 1 # If integer overflow took place, value is too large to handle. if [ $bytes -lt 0 ];then Error "DATA_SIZE=$data_size is too large to handle." return 1 fi echo $bytes return 0 fi if [[ $DATA_SIZE == *%FREE ]];then free_space=$(vgs --noheadings --nosuffix --units b -o vg_free $VG) percent=${DATA_SIZE%\%FREE} echo $((percent*free_space/100)) return 0 fi if [[ $DATA_SIZE == *%VG ]];then vg_size=$(vgs --noheadings --nosuffix --units b -o vg_size $VG) percent=${DATA_SIZE%\%VG} echo $((percent*vg_size/100)) fi return 0 } check_min_data_size_condition() { local min_data_size_bytes data_size_bytes free_space [ -z $MIN_DATA_SIZE ] && return 0 if ! check_numeric_size_syntax $MIN_DATA_SIZE; then Fatal "MIN_DATA_SIZE value $MIN_DATA_SIZE is invalid." fi if ! min_data_size_bytes=$(convert_size_in_bytes $MIN_DATA_SIZE);then Fatal "Failed to convert MIN_DATA_SIZE to bytes" fi # If integer overflow took place, value is too large to handle. if [ $min_data_size_bytes -lt 0 ];then Fatal "MIN_DATA_SIZE=$MIN_DATA_SIZE is too large to handle." fi free_space=$(vgs --noheadings --nosuffix --units b -o vg_free $VG) if [ $free_space -lt $min_data_size_bytes ];then Fatal "There is not enough free space in volume group $VG to create data volume of size MIN_DATA_SIZE=${MIN_DATA_SIZE}." fi if ! data_size_bytes=$(data_size_in_bytes $DATA_SIZE);then Fatal "Failed to convert desired data size to bytes" fi if [ $data_size_bytes -lt $min_data_size_bytes ]; then # Increasing DATA_SIZE to meet minimum data size requirements. Info "DATA_SIZE=${DATA_SIZE} is smaller than MIN_DATA_SIZE=${MIN_DATA_SIZE}. Will create data volume of size specified by MIN_DATA_SIZE." DATA_SIZE=$MIN_DATA_SIZE fi } create_lvm_thin_pool () { if [ -z "$_DEVS_RESOLVED" ] && [ -z "$_VG_EXISTS" ]; then Fatal "Specified volume group $VG does not exist, and no devices were specified" fi if [ ! -n "$DATA_SIZE" ]; then Fatal "DATA_SIZE not specified." fi if ! check_data_size_syntax $DATA_SIZE; then Fatal "DATA_SIZE value $DATA_SIZE is invalid." fi check_min_data_size_condition if [ -n "$POOL_META_SIZE" ]; then _META_SIZE_ARG="$POOL_META_SIZE" else # Calculate size of metadata lv. Reserve 0.1% of the free space in the VG # for docker metadata. _VG_SIZE=$(vgs --noheadings --nosuffix --units s -o vg_size $VG) _META_SIZE=$(( $_VG_SIZE / 1000 + 1 )) if [ -z "$_META_SIZE" ];then Fatal "Failed to calculate metadata volume size." fi _META_SIZE_ARG=${_META_SIZE}s fi if [ -n "$CHUNK_SIZE" ]; then _CHUNK_SIZE_ARG="-c $CHUNK_SIZE" fi if [[ $DATA_SIZE == *%* ]]; then _DATA_SIZE_ARG="-l $DATA_SIZE" else _DATA_SIZE_ARG="-L $DATA_SIZE" fi lvcreate -y --type thin-pool --zero n $_CHUNK_SIZE_ARG --poolmetadatasize $_META_SIZE_ARG $_DATA_SIZE_ARG -n $CONTAINER_THINPOOL $VG } get_configured_thin_pool() { local options tpool opt options=$_CURRENT_STORAGE_OPTIONS [ -z "$options" ] && return 0 # This assumes that thin pool is specified as dm.thinpooldev=foo. There # are no spaces in between. for opt in $options; do if [[ $opt =~ dm.thinpooldev* ]];then tpool=${opt#*=} echo "$tpool" return 0 fi done } check_docker_storage_metadata() { local docker_devmapper_meta_dir="$_DOCKER_METADATA_DIR/devicemapper/metadata/" [ ! -d "$docker_devmapper_meta_dir" ] && return 0 # Docker seems to be already using devicemapper storage driver. Error out. Error "Docker has been previously configured for use with devicemapper graph driver. Not creating a new thin pool as existing docker metadata will fail to work with it. Manual cleanup is required before this will succeed." Info "Docker state can be reset by stopping docker and by removing ${_DOCKER_METADATA_DIR} directory. This will destroy existing docker images and containers and all the docker metadata." exit 1 } systemd_escaped_filename () { local escaped_path filename path=$1 escaped_path=$(echo ${path}|sed 's|-|\\x2d|g') filename=$(echo ${escaped_path}.mount|sed 's|/|-|g' | cut -b 2-) echo $filename } # Compatibility mode code run_docker_compatibility_code() { # Verify storage options set correctly in input files check_storage_options # Query and save current storage options if ! _CURRENT_STORAGE_OPTIONS=$(get_current_storage_options); then return 1 fi determine_rootfs_pvs_vg if [ $_RESET -eq 1 ]; then reset_storage_compat exit 0 fi partition_disks_create_vg grow_root_pvs # NB: We are growing root here first, because when root and docker share a # disk, we'll default to giving some portion of remaining space to docker. # Do this operation only if root is on a logical volume. [ -n "$_ROOT_VG" ] && grow_root_lv_fs if is_old_data_meta_mode; then Fatal "Old mode of passing data and metadata logical volumes to docker is not supported. Exiting." fi setup_storage_compat } # # In the past we created a systemd mount target file, we no longer # use it, but if one pre-existed we still need to handle it. # remove_systemd_mount_target () { local mp=$1 local filename=$(systemd_escaped_filename $mp) if [ -f /etc/systemd/system/$filename ]; then if [ -x /usr/bin/systemctl ];then systemctl disable $filename >/dev/null 2>&1 systemctl stop $filename >/dev/null 2>&1 systemctl daemon-reload fi rm -f /etc/systemd/system/$filename >/dev/null 2>&1 fi } # This is used in compatibility mode. reset_extra_volume_compat () { local mp filename local lv_name=$1 local mount_dir=$2 local vg=$3 if extra_volume_exists $lv_name $vg; then mp=$(extra_lv_mountpoint $vg $lv_name $mount_dir) if [ -n "$mp" ];then if ! umount $mp >/dev/null 2>&1; then Fatal "Failed to unmount $mp" fi fi lvchange -an $vg/${lv_name} lvremove $vg/${lv_name} else return 0 fi # If the user has manually unmounted mount directory, mountpoint (mp) # will be empty. Extract ${mp} from $(mount_dir) in that case. if [ -z "$mp" ];then mp=${mount_dir} fi remove_systemd_mount_target $mp } reset_lvm_thin_pool () { local thinpool_name=$1 local vg=$2 if lvm_pool_exists $thinpool_name $vg; then lvchange -an $vg/${thinpool_name} lvremove $vg/${thinpool_name} fi } # Used in compatibility mode. Determine if already configured thin pool # is managed by container-storage-setup or not. Returns 0 if tpool is # managed otherwise 1. is_managed_tpool_compat() { local tpool=$1 local thinpool_name=${CONTAINER_THINPOOL} local escaped_pool_lv_name=`echo $thinpool_name | sed 's/-/--/g'` # css generated thin pool device name starts with /dev/mapper/ and # ends with $thinpool_name [[ "$tpool" == /dev/mapper/*${escaped_pool_lv_name} ]] && return 0 return 1 } # This is used in comatibility mode. bringup_existing_thin_pool_compat() { local tpool=$1 # css generated thin pool device name starts with /dev/mapper/ and # ends with $thinpool_name if ! is_managed_tpool_compat "$tpool";then Fatal "Thin pool ${tpool} does not seem to be managed by container-storage-setup. Exiting." fi if ! wait_for_dev "$tpool"; then Fatal "Already configured thin pool $tpool is not available. If thin pool exists and is taking longer to activate, set DEVICE_WAIT_TIMEOUT to a higher value and retry. If thin pool does not exist any more, remove ${_STORAGE_OUT_FILE} and retry" fi } # This is used in comatibility mode. Returns 0 if thin pool is already # configured and wait could find the device. Returns 1 if thin pool is # not configured and probably needs to be created. Terminates script # on fatal errors. check_existing_thinpool_compat() { local tpool # Check if a thin pool is already configured in /etc/sysconfig/docker-storage # If yes, wait for that thin pool to come up. tpool=`get_configured_thin_pool` [ -z "$tpool" ] && return 1 Info "Found an already configured thin pool $tpool in ${_STORAGE_OUT_FILE}" bringup_existing_thin_pool_compat "$tpool" return } # This is used in comatibility mode. setup_lvm_thin_pool_compat () { local thinpool_name=${CONTAINER_THINPOOL} if check_existing_thinpool_compat; then process_auto_pool_extenion ${VG} ${thinpool_name} # We found existing thin pool and waited for it and processed auto # pool extension changes. There should not be any need to process # further return fi # At this point of time, a volume group should exist for lvm thin pool # operations to succeed. Make that check and fail if that's not the case. if ! vg_exists "$VG";then Fatal "No valid volume group found. Exiting." else _VG_EXISTS=1 fi if ! lvm_pool_exists $thinpool_name $VG; then [ -n "$_DOCKER_COMPAT_MODE" ] && check_docker_storage_metadata create_lvm_thin_pool [ -n "$_STORAGE_OUT_FILE" ] && write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE" else # At this point /etc/sysconfig/docker-storage file should exist. If user # deleted this file accidently without deleting thin pool, recreate it. if [ -n "$_STORAGE_OUT_FILE" -a ! -f "${_STORAGE_OUT_FILE}" ];then Info "${_STORAGE_OUT_FILE} file is missing. Recreating it." write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE" fi fi process_auto_pool_extenion ${VG} ${thinpool_name} } lvm_pool_exists() { local lv_data local lvname lv lvsize local thinpool_name=$1 local vg=$2 if [ -z "$thinpool_name" ]; then Fatal "Thin pool name must be specified." fi lv_data=$( lvs --noheadings -o lv_name,lv_attr --separator , $vg | sed -e 's/^ *//') SAVEDIFS=$IFS for lv in $lv_data; do IFS=, read lvname lvattr <<< "$lv" # pool logical volume has "t" as first character in its attributes if [ "$lvname" == "$thinpool_name" ] && [[ $lvattr == t* ]]; then IFS=$SAVEDIFS return 0 fi done IFS=$SAVEDIFS return 1 } # If a ${_STORAGE_OUT_FILE} file is present and if it contains # dm.datadev or dm.metadatadev entries, that means we have used old mode # in the past. is_old_data_meta_mode() { if [ ! -f "${_STORAGE_OUT_FILE}" ];then return 1 fi if ! grep -e "^${_STORAGE_OPTIONS}=.*dm\.datadev" -e "^${_STORAGE_OPTIONS}=.*dm\.metadatadev" ${_STORAGE_OUT_FILE} > /dev/null 2>&1;then return 1 fi return 0 } grow_root_pvs() { # If root is not in a volume group, then there are no root pvs and nothing # to do. [ -z "$_ROOT_PVS" ] && return 0 # Grow root pvs only if user asked for it through config file. [ "$GROWPART" != "true" ] && return if [ ! -x "/usr/bin/growpart" ];then Error "GROWPART=true is specified and /usr/bin/growpart executable is not available. Install /usr/bin/growpart and try again." return 1 fi # Note that growpart is only variable here because we may someday support # using separate partitions on the same disk. Today we fail early in that # case. Also note that the way we are doing this, it should support LVM # RAID for the root device. In the mirrored or striped case, we are growing # partitions on all disks, so as long as they match, growing the LV should # also work. for pv in $_ROOT_PVS; do # Split device & partition. Ick. growpart $( echo $pv | sed -r 's/([^0-9]*)([0-9]+)/\1 \2/' ) || true pvresize $pv done } grow_root_lv_fs() { if [ -n "$ROOT_SIZE" ]; then # Allow user to pass in an argument that could be provided # to -L (like 10G or +5G) or an argument that could be passed # to -l (like 80%FREE). Switch based on if '%' is in string. local root_size_arg if [[ $ROOT_SIZE =~ % ]]; then root_size_arg="-l $ROOT_SIZE" else root_size_arg="-L $ROOT_SIZE" fi # TODO: Error checking if specified size is <= current size lvextend -r $root_size_arg $_ROOT_DEV || true fi } # Determines if a device is already added in a volume group as pv. Returns # 0 on success. is_dev_part_of_vg() { local dev=$1 local vg=$2 if ! pv_name=$(pvs --noheadings -o pv_name -S pv_name=$dev,vg_name=$vg); then Fatal "Error running command pvs. Exiting." fi [ -z "$pv_name" ] && return 1 pv_name=`echo $pv_name | tr -d '[ ]'` [ "$pv_name" == "$dev" ] && return 0 return 1 } is_block_dev_partition() { local bdev=$1 devparent if ! disktype=$(lsblk -n --nodeps --output type ${bdev}); then Fatal "Failed to run lsblk on device $bdev" fi if [ "$disktype" == "part" ];then return 0 fi if [ "$disktype" == "mpath" ];then return 1 fi # For loop device partitions, lsblk reports type as "loop" and not "part". # So check if device has a parent in the tree and if it does, there are high # chances it is partition (except the case of lvm volumes) if ! devparent=$(lsblk -npls -o NAME ${bdev}|tail -n +2); then Fatal "Failed to run lsblk on device $bdev" fi if [ -n "$devparent" ];then return 0 fi return 1 } check_wipe_block_dev_sig() { local bdev=$1 local sig if ! sig=$(wipefs -p $bdev); then Fatal "Failed to check signatures on device $bdev" fi [ "$sig" == "" ] && return 0 if [ "$WIPE_SIGNATURES" == "true" ];then Info "Wipe Signatures is set to true. Any signatures on $bdev will be wiped." if ! wipefs -a $bdev; then Fatal "Failed to wipe signatures on device $bdev" fi return 0 fi while IFS=, read offset uuid label type; do [ "$offset" == "# offset" ] && continue Fatal "Found $type signature on device ${bdev} at offset ${offset}. Wipe signatures using wipefs or use WIPE_SIGNATURES=true and retry." done <<< "$sig" } # This is used in compatibility mode canonicalize_block_devs_compat() { local devs=$1 dev local devs_abs dev_abs local dest_dev for dev in ${devs}; do # If the device name is a symlink, follow it and use the target if [ -h "$dev" ];then if ! dest_dev=$(readlink -e $dev);then Fatal "Failed to resolve symbolic link $dev" fi dev=$dest_dev fi # Looks like we allowed just device name (sda) as valid input. In # such cases /dev/$dev should be a valid block device. dev_abs=$dev [ ! -b "$dev" ] && dev_abs="/dev/$dev" [ ! -b "$dev_abs" ] && Fatal "$dev_abs is not a valid block device." if is_block_dev_partition ${dev_abs}; then Fatal "Partition specification unsupported at this time." fi devs_abs="$devs_abs $dev_abs" done # Return list of devices to caller. echo "$devs_abs" } # This is used in config creation mode canonicalize_block_devs_generic() { local devs=$1 dev local devs_resolved resolved_device for dev in ${devs}; do if ! resolved_device=$(realpath -e $dev);then Fatal "Failed to resolve path for device ${dev}" fi [ ! -b "$resolved_device" ] && Fatal "$resolved_device is not a valid block device." if is_block_dev_partition ${resolved_device}; then Fatal "Partition specification unsupported at this time." fi if [ -n "$devs_resolved" ]; then devs_resolved="$devs_resolved $resolved_device" else devs_resolved="$resolved_device" fi done # Return list of devices to caller. echo "$devs_resolved" } # Make sure passed in devices are valid block devies. Also make sure they # are not partitions. Names which are of the form "sdb", convert them to # their absolute path for processing in rest of the script. canonicalize_block_devs() { local input_dev_list="$1" local devs_list if [ "$_DOCKER_COMPAT_MODE" == "1" ];then devs_list=$(canonicalize_block_devs_compat "$input_dev_list") || return 1 else devs_list=$(canonicalize_block_devs_generic "$input_dev_list") || return 1 fi echo $devs_list } # Scans all the disks listed in DEVS= and returns the disks which are not # already part of volume group and are new and require further processing. scan_disks() { local disk_list="$1" local vg=$2 local wipe_signatures=$3 local new_disks="" for dev in $disk_list; do local part=$(dev_query_first_child $dev) if [ -n "$part" ] && is_dev_part_of_vg ${part} $vg; then Info "Device ${dev} is already partitioned and is part of volume group $VG" continue fi # If signatures are being overridden, then simply return the disk as new # disk. Even if it is partitioned, partition signatures will be wiped. if [ "$wipe_signatures" == "true" ];then new_disks="$new_disks $dev" continue fi # If device does not have partitions, it is a new disk requiring processing. if [[ -z "$part" ]]; then new_disks="$dev $new_disks" continue fi Fatal "Device $dev is already partitioned and cannot be added to volume group $vg" done echo $new_disks } determine_partition_type() { local dev="$1" size_bytes part_type if ! size_bytes=$(blockdev --getsize64 "$dev"); then Fatal "Failed to determine size of disk $dev" fi if [ $size_bytes -gt $_MAX_MBR_SIZE_BYTES ];then part_type="gpt" else part_type="dos" fi echo $part_type } create_partition_sfdisk(){ local dev="$1" part_type="$2" size part_label # Use a single partition of a whole device # TODO: # * Consider gpt, or unpartitioned volumes # * Error handling when partition(s) already exist # * Deal with loop/nbd device names. See growpart code if [ "$part_type" == "gpt" ];then # Linux LVM GUID for GPT. Taken from Wiki. part_label="E6D6D379-F507-44C2-A23C-238F2A3DF928" # Create as big a partition as possible. size="" else part_label="8e" size=$(( $( awk "\$4 ~ /"$( basename $dev )"/ { print \$3 }" /proc/partitions ) * 2 - 2048 )) fi cat <<EOF | sfdisk $dev unit: sectors label: $part_type 2048,${size},$part_label EOF } create_partition_parted(){ local dev="$1" local part_type="$2" if [ "$part_type" == "gpt" ];then parted $dev --script mklabel gpt mkpart "container-partition" 0% 100% set 1 lvm on else parted $dev --script mklabel msdos mkpart primary 0% 100% set 1 lvm on fi } create_partition() { local dev="$1" part part_type part_type=`determine_partition_type "$dev"` if [ -x "/usr/sbin/parted" ]; then create_partition_parted $dev "$part_type" else create_partition_sfdisk $dev "$part_type" fi # Sometimes on slow storage it takes a while for partition device to # become available. Wait for device node to show up. if ! udevadm settle;then Fatal "udevadm settle after partition creation failed. Exiting." fi part=$(dev_query_first_child $dev) if ! wait_for_dev ${part}; then Fatal "Partition device ${part} is not available" fi } dev_query_first_child() { lsblk -npl -o NAME "$1" | tail -n +2 | head -1 } create_disk_partitions() { local devs="$1" part for dev in $devs; do # wipefs /dev/disk does not wipe any lvm signatures which might be # present on /dev/diskpart1. This signature will become visible to # lvm udev rules and will kickstart volume creation as soon as partion # is created and race with further partition commands like wipefs, # pvcreate etc. So zero out first few MB of disk in an attempt to # wipe any lvm signatures on first partition. # # By now we have ownership of disk and we have checked there are no # signatures on disk or signatures have been wiped. Dont care about # any signatures now on in the middle of disk. Info "Writing zeros to first 4MB of device $dev" if ! dd if=/dev/zero of=$dev bs=1M count=4; then Fatal "Failed to zero first 4MB of device $bdev" fi create_partition $dev part=$(dev_query_first_child $dev) # It now seems unnecessary to do wipefs on partition given we already # zeroed out first 4MB. Only time it will be required if partition # starts beyong 4MB. Keep it for now. if ! wipefs -f -a ${part}; then Fatal "Failed to wipe signatures on device ${part}" fi pvcreate ${part} _PVS="$_PVS ${part}" done } remove_partition() { local dev="$1" if [ -x "/usr/sbin/parted" ]; then parted "$dev" rm 1 >/dev/null else sfdisk --delete "$dev" 1 >/dev/null fi } # Remove disk pvs and partitions. This is called in reset storage path. # If partition or pv does not exist, it will still return success. Error # will be returned only if pv or partition exists and removal fails. remove_disk_pvs_parts() { local devs="$1" part for dev in $devs; do part=$(dev_query_first_child $dev) [ -z "$part" ] && continue if ! remove_pv_if_exists $part; then Error "Failed to remove physical volume label on device $part" return 1 fi if ! remove_partition $dev; then Error "Failed to remove partition on device $dev" return 1 fi done } create_extend_volume_group() { if [ -z "$_VG_EXISTS" ]; then vgcreate $VG $_PVS _VG_CREATED=1 _VG_EXISTS=1 else # TODO: # * Error handling when PV is already part of a VG vgextend $VG $_PVS fi } # Auto extension logic. Create a profile for pool and attach that profile # the pool volume. enable_auto_pool_extension() { local volume_group=$1 local pool_volume=$2 local profileName="${volume_group}--${pool_volume}-extend" local profileFile="${profileName}.profile" local profileDir local tmpFile=`mktemp -p /run -t tmp.XXXXX` profileDir=$(lvm dumpconfig --type full | grep "profile_dir" | cut -d "=" -f2 | sed 's/"//g') [ -n "$profileDir" ] || return 1 if [ ! -n "$POOL_AUTOEXTEND_THRESHOLD" ];then Error "POOL_AUTOEXTEND_THRESHOLD not specified" return 1 fi if [ ! -n "$POOL_AUTOEXTEND_PERCENT" ];then Error "POOL_AUTOEXTEND_PERCENT not specified" return 1 fi cat <<EOF > $tmpFile activation { thin_pool_autoextend_threshold=${POOL_AUTOEXTEND_THRESHOLD} thin_pool_autoextend_percent=${POOL_AUTOEXTEND_PERCENT} } EOF mv -Z $tmpFile ${profileDir}/${profileFile} lvchange --metadataprofile ${profileName} ${volume_group}/${pool_volume} } disable_auto_pool_extension() { local volume_group=$1 local pool_volume=$2 local profileName="${volume_group}--${pool_volume}-extend" local profileFile="${profileName}.profile" local profileDir profileDir=$(lvm dumpconfig --type full | grep "profile_dir" | cut -d "=" -f2 | sed 's/"//g') [ -n "$profileDir" ] || return 1 lvchange --detachprofile ${volume_group}/${pool_volume} rm -f ${profileDir}/${profileFile} } process_auto_pool_extenion() { local vg=$1 thinpool_name=$2 # Enable or disable automatic pool extension if [ "$AUTO_EXTEND_POOL" == "yes" ];then enable_auto_pool_extension ${vg} ${thinpool_name} else disable_auto_pool_extension ${vg} ${thinpool_name} fi } # Gets the current ${_STORAGE_OPTIONS}= string. get_current_storage_options() { local options if [ ! -f "${_STORAGE_OUT_FILE}" ];then return 0 fi if options=$(grep -e "^${_STORAGE_OPTIONS}=" ${_STORAGE_OUT_FILE} | sed "s/${_STORAGE_OPTIONS}=//" | sed 's/^ *//' | sed 's/^"//' | sed 's/"$//');then echo $options return 0 fi return 1 } is_valid_storage_driver() { local driver=$1 d # Empty driver is valid. That means user does not want us to setup any # storage. [ -z "$driver" ] && return 0 for d in $_STORAGE_DRIVERS;do [ "$driver" == "$d" ] && return 0 done return 1 } # Gets the existing storage driver configured in /etc/sysconfig/docker-storage get_existing_storage_driver() { local options driver options=$_CURRENT_STORAGE_OPTIONS [ -z "$options" ] && return 0 # Check if -storage-driver <driver> is there. if ! driver=$(echo $options | sed -n 's/.*\(--storage-driver [ ]*[a-z0-9]*\).*/\1/p' | sed 's/--storage-driver *//');then return 1 fi # If pattern does not match then driver == options. if [ -n "$driver" ] && [ ! "$driver" == "$options" ];then echo $driver return 0 fi # Check if -s <driver> is there. if ! driver=$(echo $options | sed -n 's/.*\(-s [ ]*[a-z][0-9]*\).*/\1/p' | sed 's/-s *//');then return 1 fi # If pattern does not match then driver == options. if [ -n "$driver" ] && [ ! "$driver" == "$options" ];then echo $driver return 0 fi # We shipped some versions where we did not specify -s devicemapper. # If dm.thinpooldev= is present driver is devicemapper. if echo $options | grep -q -e "--storage-opt dm.thinpooldev=";then echo "devicemapper" return 0 fi #Failed to determine existing storage driver. return 1 } extra_volume_exists() { local lv_name=$1 local vg=$2 lvs $vg/$lv_name > /dev/null 2>&1 && return 0 return 1 } # This returns the mountpoint of $1 extra_lv_mountpoint() { local mounts local vg=$1 local lv_name=$2 local mount_dir=$3 mounts=$(findmnt -n -o TARGET --source /dev/$vg/$lv_name | grep "^$mount_dir") echo $mounts } mount_extra_volume() { local vg=$1 local lv_name=$2 local mount_dir=$3 remove_systemd_mount_target $mount_dir mounts=$(extra_lv_mountpoint $vg $lv_name $mount_dir) if [ -z "$mounts" ]; then mount -o pquota /dev/$vg/$lv_name $mount_dir fi } # Create a logical volume of size specified by first argument. Name of the # volume is specified using second argument. create_lv() { local data_size=$1 local data_lv_name=$2 # TODO: Error handling when data_size > available space. if [[ $data_size == *%* ]]; then lvcreate -y -l $data_size -n $data_lv_name $VG || return 1 else lvcreate -y -L $data_size -n $data_lv_name $VG || return 1 fi return 0 } setup_extra_volume() { local lv_name=$1 local mount_dir=$2 local lv_size=$3 if ! create_lv $lv_size $lv_name; then Fatal "Failed to create volume $lv_name of size ${lv_size}." fi if ! mkfs -t xfs /dev/$VG/$lv_name > /dev/null; then Fatal "Failed to create filesystem on /dev/$VG/${lv_name}." fi if ! mount_extra_volume $VG $lv_name $mount_dir; then Fatal "Failed to mount volume ${lv_name} on ${mount_dir}" fi # setup right selinux label first time fs is created. Mount operation # changes the label of directory to reflect the label on root inode # of mounted fs. if ! restore_selinux_context $mount_dir; then return 1 fi } # This is used only in compatibility mode. We are still using systemd # mount unit only for compatibility mode. Reason being that upon service # restart, we run into races and device might not yet be up when we try # to mount it. And we don't know if this is first time start and we need # to create volume or this is restart and we need to wait for device. So # continue to use systemd mount unit for compatibility mode. setup_systemd_mount_unit_compat() { local filename local vg=$1 local lv_name=$2 local mount_dir=$3 local unit_file_path # filename must match the path ${mount_dir}. # e.g if ${mount_dir} is /var/lib/containers # then filename will be var-lib-containers.mount filename=$(systemd_escaped_filename ${mount_dir}) unit_file_path="/etc/systemd/system/$filename" # If unit file already exists, nothing to do. [ -f "$unit_file_path" ] && return 0 cat <<EOF > "${unit_file_path}.tmp" # WARNING: This file was auto generated by container-storage-setup. Do not # edit it. In the future, this file might be moved to a different location. [Unit] Description=Mount $lv_name on $mount_dir directory. Before=docker-storage-setup.service [Mount] What=/dev/$vg/$lv_name Where=${mount_dir} Type=xfs Options=pquota [Install] WantedBy=docker-storage-setup.service EOF mv "${unit_file_path}.tmp" "$unit_file_path" systemctl daemon-reload systemctl enable $filename >/dev/null 2>&1 systemctl start $filename } setup_extra_lv_fs_compat() { [ -z "$_RESOLVED_MOUNT_DIR_PATH" ] && return 0 if ! setup_extra_dir $_RESOLVED_MOUNT_DIR_PATH; then return 1 fi # If we are restarting, then extra volume should exist. This unit # file is dependent on extra volume mount unit file. That means this # code should run after mount unit has activated successfully. That # means after extra volume has come up. # We had got rid of this logic and reintroducing it back. That means # there can be configurations out there which have extra volume but # don't have unit file. So in such case, drop a unit file now. This # is still racy though. There is no guarantee that volume will be # up by the time this code runs when unit file is not present already. if extra_volume_exists $CONTAINER_ROOT_LV_NAME $VG; then if ! setup_systemd_mount_unit_compat "$VG" "$CONTAINER_ROOT_LV_NAME" "$_RESOLVED_MOUNT_DIR_PATH"; then Fatal "Failed to setup systemd mount unit for extra volume $CONTAINER_ROOT_LV_NAME." fi return 0 fi if [ -z "$CONTAINER_ROOT_LV_SIZE" ]; then Fatal "Specify a valid value for CONTAINER_ROOT_LV_SIZE." fi if ! check_data_size_syntax $CONTAINER_ROOT_LV_SIZE; then Fatal "CONTAINER_ROOT_LV_SIZE value $CONTAINER_ROOT_LV_SIZE is invalid." fi # Container runtime extra volume does not exist. Create one. if ! setup_extra_volume $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH $CONTAINER_ROOT_LV_SIZE; then Fatal "Failed to setup extra volume $CONTAINER_ROOT_LV_NAME." fi if ! setup_systemd_mount_unit_compat "$VG" "$CONTAINER_ROOT_LV_NAME" "$_RESOLVED_MOUNT_DIR_PATH"; then Fatal "Failed to setup systemd mount unit for extra volume $CONTAINER_ROOT_LV_NAME." fi } setup_extra_lv_fs() { [ -z "$_RESOLVED_MOUNT_DIR_PATH" ] && return 0 if ! setup_extra_dir $_RESOLVED_MOUNT_DIR_PATH; then return 1 fi if extra_volume_exists $CONTAINER_ROOT_LV_NAME $VG; then if ! mount_extra_volume $VG $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH; then Fatal "Failed to mount volume $CONTAINER_ROOT_LV_NAME on $_RESOLVED_MOUNT_DIR_PATH" fi return 0 fi if [ -z "$CONTAINER_ROOT_LV_SIZE" ]; then Fatal "Specify a valid value for CONTAINER_ROOT_LV_SIZE." fi if ! check_data_size_syntax $CONTAINER_ROOT_LV_SIZE; then Fatal "CONTAINER_ROOT_LV_SIZE value $CONTAINER_ROOT_LV_SIZE is invalid." fi # Container runtime extra volume does not exist. Create one. if ! setup_extra_volume $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH $CONTAINER_ROOT_LV_SIZE; then Fatal "Failed to setup extra volume $CONTAINER_ROOT_LV_NAME." fi } setup_docker_root_lv_fs() { [ "$DOCKER_ROOT_VOLUME" != "yes" ] && return 0 if ! setup_docker_root_dir; then return 1 fi if extra_volume_exists $_DOCKER_ROOT_LV_NAME $VG; then if ! mount_extra_volume $VG $_DOCKER_ROOT_LV_NAME $_DOCKER_ROOT_DIR; then Fatal "Failed to mount volume $_DOCKER_ROOT_LV_NAME on $_DOCKER_ROOT_DIR" fi return 0 fi if [ -z "$DOCKER_ROOT_VOLUME_SIZE" ]; then Fatal "Specify a valid value for DOCKER_ROOT_VOLUME_SIZE." fi if ! check_data_size_syntax $DOCKER_ROOT_VOLUME_SIZE; then Fatal "DOCKER_ROOT_VOLUME_SIZE value $DOCKER_ROOT_VOLUME_SIZE is invalid." fi # Docker root volume does not exist. Create one. if ! setup_extra_volume $_DOCKER_ROOT_LV_NAME $_DOCKER_ROOT_DIR $DOCKER_ROOT_VOLUME_SIZE; then Fatal "Failed to setup logical volume $_DOCKER_ROOT_LV_NAME." fi } check_storage_options(){ if [ "$STORAGE_DRIVER" == "devicemapper" ] && [ -z "$CONTAINER_THINPOOL" ];then Fatal "CONTAINER_THINPOOL must be defined for the devicemapper storage driver." fi # Populate $_RESOLVED_MOUNT_DIR_PATH if [ -n "$CONTAINER_ROOT_LV_MOUNT_PATH" ];then if ! _RESOLVED_MOUNT_DIR_PATH=$(realpath $CONTAINER_ROOT_LV_MOUNT_PATH);then Fatal "Failed to resolve path $CONTAINER_ROOT_LV_MOUNT_PATH" fi fi if [ "$DOCKER_ROOT_VOLUME" == "yes" ] && [ -n "$CONTAINER_ROOT_LV_MOUNT_PATH" ];then Fatal "DOCKER_ROOT_VOLUME and CONTAINER_ROOT_LV_MOUNT_PATH are mutually exclusive options." fi if [ -n "$CONTAINER_ROOT_LV_NAME" ] && [ -z "$CONTAINER_ROOT_LV_MOUNT_PATH" ];then Fatal "CONTAINER_ROOT_LV_MOUNT_PATH cannot be empty, when CONTAINER_ROOT_LV_NAME is set" fi if [ -n "$CONTAINER_ROOT_LV_MOUNT_PATH" ] && [ -z "$CONTAINER_ROOT_LV_NAME" ];then Fatal "CONTAINER_ROOT_LV_NAME cannot be empty, when CONTAINER_ROOT_LV_MOUNT_PATH is set" fi # Allow using DOCKER_ROOT_VOLUME only in compatibility mode. if [ "$DOCKER_ROOT_VOLUME" == "yes" ] && [ "$_DOCKER_COMPAT_MODE" != "1" ];then Fatal "DOCKER_ROOT_VOLUME is deprecated. Use CONTAINER_ROOT_LV_MOUNT_PATH instead." fi if [ "$DOCKER_ROOT_VOLUME" == "yes" ];then Info "DOCKER_ROOT_VOLUME is deprecated, and will be removed soon. Use CONTAINER_ROOT_LV_MOUNT_PATH instead." fi if [ -n "${EXTRA_DOCKER_STORAGE_OPTIONS}" ]; then Info "EXTRA_DOCKER_STORAGE_OPTIONS is deprecated, please use EXTRA_STORAGE_OPTIONS" if [ -n "${EXTRA_STORAGE_OPTIONS}" ]; then Fatal "EXTRA_DOCKER_STORAGE_OPTIONS and EXTRA_STORAGE_OPTIONS are mutually exclusive options." fi EXTRA_STORAGE_OPTIONS=${EXTRA_DOCKER_STORAGE_OPTIONS} unset EXTRA_DOCKER_STORAGE_OPTIONS fi } # This is used in compatibility mode. setup_storage_compat() { local current_driver local containerroot if [ "$STORAGE_DRIVER" == "" ];then Info "STORAGE_DRIVER not set, no storage will be configured. You must specify STORAGE_DRIVER if you want to configure storage." exit 0 fi if ! is_valid_storage_driver $STORAGE_DRIVER;then Fatal "Invalid storage driver: ${STORAGE_DRIVER}." fi if ! current_driver=$(get_existing_storage_driver);then Fatal "Failed to determine existing storage driver." fi # If storage is configured and new driver should match old one. if [ -n "$current_driver" ] && [ "$current_driver" != "$STORAGE_DRIVER" ];then Info "Storage is already configured with ${current_driver} driver. Can't configure it with ${STORAGE_DRIVER} driver. To override, remove ${_STORAGE_OUT_FILE} and retry." check_existing_thinpool_compat || true return 0 fi if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then if ! can_mount_overlay; then Fatal "Can not setup storage driver $STORAGE_DRIVER as system does not support it. Specify a different driver." fi fi # If a user decides to setup (a) and (b)/(c): # a) lvm thin pool for devicemapper. # b) a separate volume for container runtime root. # c) a separate named ($CONTAINER_ROOT_LV_NAME) volume for $CONTAINER_ROOT_LV_MOUNT_PATH. # (a) will be setup first, followed by (b) or (c). # Set up lvm thin pool LV. if [ "$STORAGE_DRIVER" == "devicemapper" ]; then setup_lvm_thin_pool_compat else write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE" fi # If container root is on a separate volume, setup that. if ! setup_docker_root_lv_fs; then Error "Failed to setup docker root volume." return 1 fi # Set up a separate named ($CONTAINER_ROOT_LV_NAME) volume # for $CONTAINER_ROOT_LV_MOUNT_PATH. if ! setup_extra_lv_fs_compat; then Error "Failed to setup logical volume for $CONTAINER_ROOT_LV_MOUNT_PATH." return 1 fi if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then # This is little hacky. We are guessing where overlay2 will be setup # by container runtime environment. At some point of time this should # be passed in by a config variable. containerroot=${_RESOLVED_MOUNT_DIR_PATH:-/var} if ! is_xfs_ftype_enabled "$containerroot"; then Error "XFS filesystem at ${containerroot} has ftype=0, cannot use overlay backend; consider different driver or separate volume or OS reprovision" return 1 fi fi } restore_selinux_context() { local dir=$1 if ! restorecon -R $dir; then Error "restorecon -R $dir failed." return 1 fi } get_docker_root_dir(){ local flag=false path options=$(grep -e "^OPTIONS" /etc/sysconfig/docker|cut -d"'" -f 2) for opt in $options do if [ "$flag" = true ];then path=$opt flag=false continue fi case "$opt" in "-g"|"--graph") flag=true ;; -g=*|--graph=*) path=$(echo $opt|cut -d"=" -f 2) ;; *) ;; esac done if [ -z "$path" ];then return fi if ! _DOCKER_ROOT_DIR=$(realpath -m $path);then Fatal "Failed to resolve path $path" fi } setup_extra_dir() { local resolved_mount_dir_path=$1 [ -d "$resolved_mount_dir_path" ] && return 0 # Directory does not exist. Create one. mkdir -p $resolved_mount_dir_path return $? } setup_docker_root_dir() { if ! get_docker_root_dir; then return 1 fi [ -d "_$DOCKER_ROOT_DIR" ] && return 0 # Directory does not exist. Create one. mkdir -p $_DOCKER_ROOT_DIR return $? } # This deals with determining rootfs, root vg and pvs etc and sets the # global variables accordingly. determine_rootfs_pvs_vg() { # Read mounts _ROOT_DEV=$( awk '$2 ~ /^\/$/ && $1 !~ /rootfs/ { print $1 }' /proc/mounts ) if ! _ROOT_VG=$(lvs --noheadings -o vg_name $_ROOT_DEV 2>/dev/null);then Info "Volume group backing root filesystem could not be determined" _ROOT_VG= else _ROOT_VG=$(echo $_ROOT_VG | sed -e 's/^ *//' -e 's/ *$//') fi _ROOT_PVS= if [ -n "$_ROOT_VG" ];then _ROOT_PVS=$( pvs --noheadings -o pv_name,vg_name | awk "\$2 ~ /^$_ROOT_VG\$/ { print \$1 }" ) fi _VG_EXISTS= if [ -z "$VG" ]; then if [ -n "$_ROOT_VG" ]; then VG=$_ROOT_VG _VG_EXISTS=1 fi else if vg_exists "$VG";then _VG_EXISTS=1 fi fi } partition_disks_create_vg() { local dev_list # If there is no volume group specified or no root volume group, there is # nothing to do in terms of dealing with disks. if [[ -n "$DEVS" && -n "$VG" ]]; then _DEVS_RESOLVED=$(canonicalize_block_devs "${DEVS}") || return 1 # If all the disks have already been correctly partitioned, there is # nothing more to do dev_list=$(scan_disks "$_DEVS_RESOLVED" "$VG" "$WIPE_SIGNATURES") || return 1 if [[ -n "$dev_list" ]]; then for dev in $dev_list; do check_wipe_block_dev_sig $dev done create_disk_partitions "$dev_list" create_extend_volume_group fi fi } # This is used in compatibility mode. reset_storage_compat() { local tpool # Check if a thin pool is already configured in /etc/sysconfig/docker-storage tpool=`get_configured_thin_pool` if [ -n "$_RESOLVED_MOUNT_DIR_PATH" ] && [ -n "$CONTAINER_ROOT_LV_NAME" ];then reset_extra_volume_compat $CONTAINER_ROOT_LV_NAME $_RESOLVED_MOUNT_DIR_PATH $VG fi if [ "$DOCKER_ROOT_VOLUME" == "yes" ];then if ! get_docker_root_dir; then return 1 fi reset_extra_volume_compat $_DOCKER_ROOT_LV_NAME $_DOCKER_ROOT_DIR $VG fi if [ -n "$tpool" ]; then local tpool_vg tpool_lv Info "Found an already configured thin pool $tpool in ${_STORAGE_OUT_FILE}" if ! is_managed_tpool_compat "$tpool"; then Fatal "Thin pool ${tpool} does not seem to be managed by container-storage-setup. Exiting." fi tpool_vg=`get_dmdev_vg $tpool` reset_lvm_thin_pool ${CONTAINER_THINPOOL} "$tpool_vg" elif [ "$STORAGE_DRIVER" == "devicemapper" ]; then reset_lvm_thin_pool ${CONTAINER_THINPOOL} $VG fi rm -f ${_STORAGE_OUT_FILE} } usage() { cat <<-FOE Usage: $1 [OPTIONS] Usage: $1 [OPTIONS] COMMAND [arg...] Grows the root filesystem and sets up storage for container runtimes Options: --help Print help message --reset Reset your docker storage to init state. --version Print version information. Commands: create Create storage configuration activate Activate storage configuration deactivate Deactivate storage configuration remove Remove storage configuration list List storage configuration export Send storage configuration output file to stdout add-dev Add block device to storage configuration FOE } # # START of Helper functions dealing with commands and storage setup for new # design # # Functions dealing with metadata handling create_metadata() { local metafile=$1 cat > ${metafile}.tmp <<-EOF _M_METADATA_VERSION=$_METADATA_VERSION _M_STORAGE_DRIVER=$STORAGE_DRIVER _M_VG=$VG _M_VG_CREATED=$_VG_CREATED _M_DEVS_RESOLVED="$_DEVS_RESOLVED" _M_CONTAINER_THINPOOL=$CONTAINER_THINPOOL _M_CONTAINER_ROOT_LV_NAME=$CONTAINER_ROOT_LV_NAME _M_CONTAINER_ROOT_LV_MOUNT_PATH=$CONTAINER_ROOT_LV_MOUNT_PATH _M_AUTO_EXTEND_POOL=$AUTO_EXTEND_POOL _M_DEVICE_WAIT_TIMEOUT=$DEVICE_WAIT_TIMEOUT EOF mv ${metafile}.tmp ${metafile} } metadata_update_add_dev() { local metafile=$1 local new_resolved_dev=$2 local updated_resolved_devs cp $metafile ${metafile}.tmp if [ -z "$_M_DEVS_RESOLVED" ]; then updated_resolved_devs="$new_resolved_dev" else updated_resolved_devs="$_M_DEVS_RESOLVED $new_resolved_dev" fi if ! sed -i "s;^_M_DEVS_RESOLVED=.*$;_M_DEVS_RESOLVED=\"${updated_resolved_devs}\";" ${metafile}.tmp;then Error "Failed to update _M_DEVS_RESOLVED in metadata." return 1 fi mv ${metafile}.tmp ${metafile} } set_config_status() { local config_name=$1 local status=$2 local status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME" mkdir -p "$_CONFIG_DIR/$config_name" echo "$status" > ${status_file}.tmp mv ${status_file}.tmp ${status_file} } get_config_status() { local config_name=$1 local status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME" local curr_status curr_status=`cat $status_file` echo $curr_status } create_storage_config() { local config_path=$1 local infile=$2 mkdir -p $config_path cp $infile $config_path/$_INFILE_NAME touch $config_path/$_METAFILE_NAME create_metadata "$config_path/$_METAFILE_NAME" write_storage_config_file "$STORAGE_DRIVER" "$config_path/$_OUTFILE_NAME" } # activate command processing start # Wait for thin pool for certain time interval. If thinpool is found 0 is # returned otherwise 1. wait_for_thinpool() { local thinpool_name=$1 local vg=$2 local timeout=$3 if lvm_pool_exists $thinpool_name $vg; then return 0 fi if [ -z "$timeout" ] || [ "$timeout" == "0" ];then return 1 fi while [ $timeout -gt 0 ]; do Info "Waiting for lvm thin pool $vg/${thinpool_name}. Wait time remaining is $timeout seconds" if [ $timeout -le 5 ];then sleep $timeout else sleep 5 fi timeout=$((timeout-5)) if lvm_pool_exists $thinpool_name $vg; then return 0 fi done Info "Timed out waiting for lvm thin pool $vg/${thinpool_name}" return 1 } activate_devicemapper() { local thinpool_name=$1 local vg=$2 local timeout=$3 # TODO: Add logic to activate volume group. For now it assumes that # volume group will auto activate when devices are ready. # Wait for thin pool if ! wait_for_thinpool $thinpool_name $vg $timeout;then return 1 fi # Activate thin pool if ! lvchange -ay -K $vg/$thinpool_name; then Error "Thin pool $vg/$thinpool_name activation failed" return 1 fi return 0 } activate_storage_driver() { local driver=$1 if ! is_valid_storage_driver $driver; then Error "Invalid storage driver $driver" return 1 fi [ "$driver" == "" ] && return 0 [ "$driver" == "overlay" -o "$driver" == "overlay2" ] && return 0 if [ "$driver" == "devicemapper" ];then if ! activate_devicemapper $_M_CONTAINER_THINPOOL $_M_VG $_M_DEVICE_WAIT_TIMEOUT; then Error "Activation of driver $driver failed" return 1 fi fi } # Wait for logical volume wait_for_lv() { local lv_name=$1 local vg=$2 local timeout=$3 if extra_volume_exists $lv_name $vg; then return 0 fi if [ -z "$timeout" ] || [ "$timeout" == "0" ];then return 1 fi while [ $timeout -gt 0 ]; do Info "Waiting for logical volume $vg/${lv_name}. Wait time remaining is $timeout seconds" if [ $timeout -le 5 ];then sleep $timeout else sleep 5 fi timeout=$((timeout-5)) if extra_volume_exists $lv_name $vg; then return 0 fi done Info "Timed out waiting for logical volume $vg/${lv_name}" return 1 } activate_extra_lv_fs() { local lv_name=$1 local vg=$2 local timeout=$3 local mount_path=$4 if ! wait_for_lv $lv_name $vg $timeout; then Error "logical volume $vg/${lv_name} does not exist" return 1 fi if ! lvchange -ay $vg/$lv_name; then Error "Failed to activate volume $vg/$lv_name" return 1 fi if ! mount_extra_volume $vg $lv_name $mount_path; then Error "Failed to mount volume $vg/$lv_name on $mount_path" return 1 fi } # activate command processing start run_command_activate() { local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" local status_file="$_CONFIG_DIR/$_CONFIG_NAME/$_STATUSFILE_NAME" local curr_status [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist" [ ! -e "$metafile_path" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist" source "$metafile_path" if ! curr_status=$(cat $status_file); then Fatal "Failed to determine current status of storage configuration $_CONFIG_NAME" fi if [ "$curr_status" == "invalid" ];then Fatal "Storage configuration $_CONFIG_NAME is invalid. Can't activate it." fi if ! activate_storage_driver $_M_STORAGE_DRIVER; then Fatal "Activation of storage config $_CONFIG_NAME failed" fi # Populate $_RESOLVED_MOUNT_DIR_PATH if [ -n "$_M_CONTAINER_ROOT_LV_MOUNT_PATH" ];then if ! _RESOLVED_MOUNT_DIR_PATH=$(realpath $_M_CONTAINER_ROOT_LV_MOUNT_PATH);then Fatal "Failed to resolve path $_M_CONTAINER_ROOT_LV_MOUNT_PATH" fi if ! activate_extra_lv_fs $_M_CONTAINER_ROOT_LV_NAME $_M_VG $_M_DEVICE_WAIT_TIMEOUT $_RESOLVED_MOUNT_DIR_PATH; then Fatal "Activation of storage config $_CONFIG_NAME failed" fi fi set_config_status "$_CONFIG_NAME" "active" echo "Activated storage config $_CONFIG_NAME" } activate_help() { cat <<-FOE Usage: $1 activate [OPTIONS] CONFIG_NAME Activate storage configuration specified by CONFIG_NAME Options: -h, --help Print help message FOE } process_command_activate() { local command="$1" local command_opts=`echo "$command" | sed 's/activate //'` parsed_opts=`getopt -o h -l help -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) activate_help $(basename $0); exit 0;; --) shift; break;; esac done case $# in 1) _CONFIG_NAME=$1 ;; *) activate_help $(basename $0); exit 0;; esac } # activate command processing end # # deactivate command processing start # deactivate_devicemapper() { local thinpool_name=$1 local vg=$2 # Deactivate thin pool if ! lvchange -an $vg/$thinpool_name; then Error "Thin pool $vg/$thinpool_name deactivation failed" return 1 fi return 0 } deactivate_storage_driver() { local driver=$1 if ! is_valid_storage_driver $driver; then Error "Invalid storage driver $driver" return 1 fi [ "$driver" == "" ] && return 0 [ "$driver" == "overlay" -o "$driver" == "overlay2" ] && return 0 if [ "$driver" == "devicemapper" ];then if ! deactivate_devicemapper $_M_CONTAINER_THINPOOL $_M_VG; then Error "Deactivation of driver $driver failed" return 1 fi fi } deactivate_extra_lv_fs() { local lv_name=$1 local vg=$2 local mount_path=$3 if mountpoint -q $mount_path; then if ! umount $mount_path; then Error "Failed to unmount $mount_path" return 1 fi fi #TODO: Most likely we will have to try deactivation in a loop to make # sure any udev rules have run and now lv is not busy. if ! lvchange -an $vg/$lv_name; then Error "Failed to deactivate $vg/$lv_name" return 1 fi } run_command_deactivate() { local resolved_path local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist" [ ! -e "$metafile_path" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist" source "$metafile_path" if ! deactivate_storage_driver $_M_STORAGE_DRIVER; then Fatal "Deactivation of storage config $_CONFIG_NAME failed" fi if [ -n "$_M_CONTAINER_ROOT_LV_MOUNT_PATH" ];then if ! resolved_path=$(realpath $_M_CONTAINER_ROOT_LV_MOUNT_PATH);then Fatal "Failed to resolve path $_M_CONTAINER_ROOT_LV_MOUNT_PATH" fi if ! deactivate_extra_lv_fs $_M_CONTAINER_ROOT_LV_NAME $_M_VG $resolved_path; then Fatal "Deactivation of storage config $_CONFIG_NAME failed" fi fi set_config_status "$_CONFIG_NAME" "inactive" echo "Deactivated storage config $_CONFIG_NAME" } deactivate_help() { cat <<-FOE Usage: $1 deactivate [OPTIONS] CONFIG_NAME De-activate storage configuration specified by CONFIG_NAME Options: -h, --help Print help message FOE } process_command_deactivate() { local command="$1" local command_opts=`echo "$command" | sed 's/deactivate //'` parsed_opts=`getopt -o h -l help -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) deactivate_help $(basename $0); exit 0;; --) shift; break;; esac done case $# in 1) _CONFIG_NAME=$1 ;; *) deactivate_help $(basename $0); exit 0;; esac } # # deactivate command processing end # # # Remove command processing start # reset_extra_volume() { local mp filename local lv_name=$1 local mount_dir=$2 local vg=$3 if ! extra_volume_exists $lv_name $vg; then return 0 fi mp=$(extra_lv_mountpoint $vg $lv_name $mount_dir) if [ -n "$mp" ];then if ! umount $mp >/dev/null 2>&1; then Fatal "Failed to unmount $mp" fi fi lvchange -an $vg/${lv_name} lvremove $vg/${lv_name} } # Remove command processing reset_storage() { local resolved_path dev # Populate $_RESOLVED_MOUNT_DIR_PATH if [ -n "$_M_CONTAINER_ROOT_LV_MOUNT_PATH" ];then if ! resolved_path=$(realpath $_M_CONTAINER_ROOT_LV_MOUNT_PATH);then Error "Failed to resolve path $_M_CONTAINER_ROOT_LV_MOUNT_PATH" return 1 fi if ! reset_extra_volume $_M_CONTAINER_ROOT_LV_NAME $resolved_path $_M_VG;then Error "Failed to remove volume $_M_CONTAINER_ROOT_LV_NAME" return 1 fi fi if [ "$_M_STORAGE_DRIVER" == "devicemapper" ]; then if ! reset_lvm_thin_pool $_M_CONTAINER_THINPOOL $_M_VG; then Error "Failed to remove thinpool $_M_VG/$_M_CONTAINER_THINPOOL" return 1 fi fi # If we created a volume group, remove volume group. if [ "$_M_VG_CREATED" == "1" ];then if ! remove_vg_if_exists "$_M_VG"; then Error "Failed to remove volume group $_M_VG" return 1 fi # Cleanup any disks we added to volume group. if ! remove_disk_pvs_parts "$_M_DEVS_RESOLVED";then return 1 fi fi # Get rid of config data rm -rf "$_CONFIG_DIR/$_CONFIG_NAME/" } run_command_remove() { local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" local curr_status [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist" # Source stored metadata file. [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist" source "$metafile_path" # If storage is active, deactivate it first curr_status=$(get_config_status "$_CONFIG_NAME") if [ "$curr_status" == "active" ];then if ! run_command_deactivate; then Fatal "Failed to remove storage config $_CONFIG_NAME" fi fi set_config_status "$_CONFIG_NAME" "invalid" if ! reset_storage; then Fatal "Failed to remove storage config $_CONFIG_NAME" fi echo "Removed storage configuration $_CONFIG_NAME" } remove_help() { cat <<-FOE Usage: $1 remove [OPTIONS] CONFIG_NAME Remove storage configuration specified by CONFIG_NAME Options: -h, --help Print help message FOE } process_command_remove() { local command="$1" local command_opts=`echo "$command" | sed 's/remove //'` parsed_opts=`getopt -o h -l help -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) remove_help $(basename $0); exit 0;; --) shift; break;; esac done case $# in 1) _CONFIG_NAME=$1 ;; *) remove_help $(basename $0); exit 0;; esac } # # Remove command processing end # # # list command processing start # list_all_configs() { local all_configs=$(ls "$_CONFIG_DIR" 2>/dev/null) local config_name storage_driver local status_file curr_status metadata_file [ -z "$all_configs" ] && return 0 printf "%-24s %-16s %-16s\n" "NAME" "DRIVER" "STATUS" for config_name in $all_configs; do status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME" metadata_file="$_CONFIG_DIR/$config_name/$_METAFILE_NAME" curr_status=`cat $status_file` storage_driver=`grep _M_STORAGE_DRIVER $metadata_file | cut -d "=" -f2` printf "%-24s %-16s %-16s\n" "$config_name" "$storage_driver" "$curr_status" done } #TODO: What should be listed in what format list_overlay_params() { echo "VG=$_M_VG" echo "DEVS=$_M_DEVS_RESOLVED" echo "CONTAINER_ROOT_LV_NAME=$_M_CONTAINER_ROOT_LV_NAME" echo "CONTAINER_ROOT_LV_MOUNT_PATH=$_M_CONTAINER_ROOT_LV_MOUNT_PATH" } list_devicemapper_params() { echo "VG=$_M_VG" echo "DEVS=\"$_M_DEVS_RESOLVED\"" echo "CONTAINER_THINPOOL=$_M_CONTAINER_THINPOOL" echo "CONTAINER_ROOT_LV_NAME=$_M_CONTAINER_ROOT_LV_NAME" echo "CONTAINER_ROOT_LV_MOUNT_PATH=$_M_CONTAINER_ROOT_LV_MOUNT_PATH" echo "AUTO_EXTEND_POOL=$_M_AUTO_EXTEND_POOL" echo "DEVICE_WAIT_TIMEOUT=$_M_DEVICE_WAIT_TIMEOUT" } list_config() { local config_name=$1 local status_file curr_status status_file="$_CONFIG_DIR/$config_name/$_STATUSFILE_NAME" curr_status=`cat $status_file` echo "Name: $config_name" echo "Status: $curr_status" echo "STORAGE_DRIVER=$_M_STORAGE_DRIVER" if [ "$_M_STORAGE_DRIVER" == "" ]; then return 0 elif [ "$_M_STORAGE_DRIVER" == "overlay" ] || [ "$_M_STORAGE_DRIVER" == "overlay2" ];then list_overlay_params else list_devicemapper_params fi return 0 } run_command_list() { if [ -z "$_CONFIG_NAME" ]; then list_all_configs return fi local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist" # Source stored metadata file. [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist" source "$metafile_path" list_config "$_CONFIG_NAME" } list_help() { cat <<-FOE Usage: $1 list [OPTIONS] [CONFIG_NAME] List storage configuration Options: -h, --help Print help message FOE } process_command_list() { local command="$1" local command_opts=${command#"list"} parsed_opts=`getopt -o h -l help -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) list_help $(basename $0); exit 0;; --) shift; break;; esac done case $# in 0) ;; 1) _CONFIG_NAME=$1 ;; *) list_help $(basename $0); exit 0;; esac } # # list command processing end # # # export command processing start # run_command_export() { local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" local outfile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_OUTFILE_NAME" [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist" [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist" [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME output file does not exist" cat $outfile_path } export_help() { cat <<-FOE Usage: $1 export [OPTIONS] CONFIG_NAME Export storage configuration output file on stdout Options: -h, --help Print help message FOE } process_command_export() { local command="$1" local command_opts=${command#"export "} parsed_opts=`getopt -o h -l help -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) export_help $(basename $0); exit 0;; --) shift; break;; esac done case $# in 1) _CONFIG_NAME=$1 ;; *) export_help $(basename $0); exit 0;; esac } # # export command processing end # # # add-dev command processing start # run_command_add_dev() { local metafile_path="$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" [ ! -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME does not exist" [ ! -e "$_CONFIG_DIR/$_CONFIG_NAME/$_METAFILE_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME metadata does not exist" source $metafile_path [ -z "$_M_VG" ] && Fatal "No volume group is associated with configuration. Can not add disks." VG=$_M_VG if ! vg_exists "$VG";then Error "Volume group $VG does not exist." return 1 fi _VG_EXISTS=1 if ! partition_disks_create_vg; then Error "Failed to add device $DEVS to config $_CONFIG_NAME" return 1 fi if ! metadata_update_add_dev $metafile_path "$DEVS"; then Error "Failed to add device $DEVS to config $_CONFIG_NAME" return 1 fi echo "Added device $DEVS to storage configuration $_CONFIG_NAME" } add_dev_help() { cat <<-FOE Usage: $1 add-dev [OPTIONS] CONFIG_NAME DEVICE Add block device to configuration CONFIG_NAME Options: -h, --help Print help message FOE } process_command_add_dev() { local command="$1" local command_opts=${command#"add-dev "} parsed_opts=`getopt -o h -l help -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) add_dev_help $(basename $0); exit 0;; --) shift; break;; esac done case $# in 2) _CONFIG_NAME=$1 DEVS="$2" ;; *) add_dev_help $(basename $0); exit 0;; esac } # # add-dev command processing end # # # Start of create command processing # setup_lvm_thin_pool () { local thinpool_name=${CONTAINER_THINPOOL} # At this point of time, a volume group should exist for lvm thin pool # operations to succeed. Make that check and fail if that's not the case. if ! vg_exists "$VG";then Fatal "No valid volume group found. Exiting." fi _VG_EXISTS=1 if lvm_pool_exists $thinpool_name; then Fatal "Thin pool named $thinpool_name already exists. Specify a different thin pool name." fi create_lvm_thin_pool # Mark thin pool for skip auto activation during reboot. start command # will activate thin pool. lvchange -ky $VG/$thinpool_name [ -n "$_STORAGE_OUT_FILE" ] && write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE" process_auto_pool_extenion ${VG} ${thinpool_name} } setup_storage() { local containerroot if ! is_valid_storage_driver $STORAGE_DRIVER;then Fatal "Invalid storage driver: ${STORAGE_DRIVER}." fi if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then if ! can_mount_overlay; then Fatal "Can not setup storage driver $STORAGE_DRIVER as system does not support it. Specify a different driver." fi fi # If a user decides to setup (a) and (b)/(c): # a) lvm thin pool for devicemapper. # b) a separate volume for container runtime root. # c) a separate named ($CONTAINER_ROOT_LV_NAME) volume for $CONTAINER_ROOT_LV_MOUNT_PATH. # (a) will be setup first, followed by (b) or (c). # Set up lvm thin pool LV. if [ "$STORAGE_DRIVER" == "devicemapper" ]; then setup_lvm_thin_pool elif [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ];then [ -n "$_STORAGE_OUT_FILE" ] && write_storage_config_file $STORAGE_DRIVER "$_STORAGE_OUT_FILE" fi # Set up a separate named ($CONTAINER_ROOT_LV_NAME) volume # for $CONTAINER_ROOT_LV_MOUNT_PATH. if ! setup_extra_lv_fs; then Error "Failed to setup logical volume for $CONTAINER_ROOT_LV_MOUNT_PATH." return 1 fi if [ "$STORAGE_DRIVER" == "overlay" -o "$STORAGE_DRIVER" == "overlay2" ]; then # This is little hacky. We are guessing where overlay2 will be setup # by container runtime environment. At some point of time this should # be passed in by a config variable. containerroot=${_RESOLVED_MOUNT_DIR_PATH:-/var} if ! is_xfs_ftype_enabled "$containerroot"; then Error "XFS filesystem at ${containerroot} has ftype=0, cannot use overlay backend; consider different driver or separate volume or OS reprovision" return 1 fi fi } run_command_create() { # Verify storage options set correctly in input files [ -d "$_CONFIG_DIR/$_CONFIG_NAME" ] && Fatal "Storage configuration $_CONFIG_NAME already exists" check_storage_options determine_rootfs_pvs_vg partition_disks_create_vg setup_storage create_storage_config "$_CONFIG_DIR/$_CONFIG_NAME" "$_STORAGE_IN_FILE" set_config_status "$_CONFIG_NAME" "active" echo "Created storage configuration $_CONFIG_NAME" } create_help() { cat <<-FOE Usage: $1 create [OPTIONS] CONFIG_NAME INPUTFILE Create storage configuration specified by CONFIG_NAME and INPUTFILE Options: -h, --help Print help message -o, --output Output file path FOE } process_command_create() { local command="$1" local command_opts=`echo "$command" | sed 's/create //'` parsed_opts=`getopt -o ho: -l help,output: -- $command_opts` eval set -- "$parsed_opts" while true ; do case "$1" in -h | --help) create_help $(basename $0); exit 0;; -o | --output) _STORAGE_OUT_FILE=$2; shift 2;; --) shift; break;; esac done case $# in 2) _CONFIG_NAME=$1 _STORAGE_IN_FILE=$2 if [ ! -e "$_STORAGE_IN_FILE" ]; then Fatal "File $_STORAGE_IN_FILE does not exist." fi ;; *) create_help $(basename $0); exit 0;; esac } # # End of create command processing # parse_subcommands() { local subcommand_str="$1" local subcommand=`echo "$subcommand_str" | cut -d " " -f1` case $subcommand in create) process_command_create "$subcommand_str" _COMMAND="create" ;; activate) process_command_activate "$subcommand_str" _COMMAND="activate" ;; deactivate) process_command_deactivate "$subcommand_str" _COMMAND="deactivate" ;; remove) process_command_remove "$subcommand_str" _COMMAND="remove" ;; list) process_command_list "$subcommand_str" _COMMAND="list" ;; export) process_command_export "$subcommand_str" _COMMAND="export" ;; add-dev) process_command_add_dev "$subcommand_str" _COMMAND="add-dev" ;; *) Error "Unknown command $subcommand" usage exit 1 ;; esac } process_input_str() { local input="$1" local output # Look for commands and if one is found substitute with -- command so # that commands options are not parsed as css options by getopt for i in $_COMMAND_LIST; do if grep -w $i <<< "$input" > /dev/null 2>&1; then echo ${input/$i/-- $i} return fi done echo "$input" } # # END of helper functions dealing with commands and storage setup for new design # # # Start helper functions for locking # prepare_locking() { mkdir -p $_LOCKDIR eval "exec $_LOCKFD>"${_LOCKDIR}/$_LOCKFILE"" # Supress lvm warnings about leaked file descriptor. export LVM_SUPPRESS_FD_WARNINGS=1 } acquire_lock() { local timeout=60 while [ $timeout -gt 0 ];do flock -n $_LOCKFD && return 0 timeout=$((timeout-1)) Info "Waiting to acquire lock ${_LOCKDIR}/$_LOCKFILE" sleep 1 done Error "Timed out while waiting to acquire lock ${_LOCKDIR}/$_LOCKFILE" return 1 } # # End helper functions for locking # # Source library. If there is a library present in same dir as d-s-s, source # that otherwise fall back to standard library. This is useful when modifyin # libcss.sh in git tree and testing d-s-s. _SRCDIR=`dirname $0` if [ -e $_SRCDIR/libcss.sh ]; then source $_SRCDIR/libcss.sh elif [ -e /usr/share/container-storage-setup/libcss.sh ]; then source /usr/share/container-storage-setup/libcss.sh fi if [ -e $_SRCDIR/container-storage-setup.conf ]; then source $_SRCDIR/container-storage-setup.conf elif [ -e /usr/share/container-storage-setup/container-storage-setup ]; then source /usr/share/container-storage-setup/container-storage-setup fi # Main Script _INPUT_STR="$@" _INPUT_STR_MODIFIED=`process_input_str "$_INPUT_STR"` _OPTS=`getopt -o hv -l reset -l help -l version -- $_INPUT_STR_MODIFIED` eval set -- "$_OPTS" _RESET=0 while true ; do case "$1" in --reset) _RESET=1; shift;; -h | --help) usage $(basename $0); exit 0;; -v | --version) echo $_CSS_VERSION; exit 0;; --) shift; break;; esac done # Check subcommands case $# in 0) CONTAINER_THINPOOL=docker-pool _DOCKER_COMPAT_MODE=1 _STORAGE_IN_FILE="/etc/sysconfig/docker-storage-setup" _STORAGE_OUT_FILE="/etc/sysconfig/docker-storage" ;; *) _SUBCOMMAND_STR="$@" parse_subcommands "$_SUBCOMMAND_STR" ;; esac if [ -n "$_DOCKER_COMPAT_MODE" ]; then _STORAGE_OPTIONS="DOCKER_STORAGE_OPTIONS" fi # If user has overridden any settings in $_STORAGE_IN_FILE # take that into account. if [ -e "${_STORAGE_IN_FILE}" ]; then source ${_STORAGE_IN_FILE} fi # Take lock only in new mode and not compatibility mode [ -z "$_DOCKER_COMPAT_MODE" ] && { prepare_locking; acquire_lock; } case $_COMMAND in create) run_command_create ;; activate) run_command_activate ;; deactivate) run_command_deactivate ;; remove) run_command_remove ;; list) run_command_list ;; export) run_command_export ;; add-dev) run_command_add_dev ;; *) run_docker_compatibility_code ;; esac