From af2f2d99b9595b9c76c66fc64a426d3bcc2d8191 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Thu, 28 Apr 2016 16:15:03 +0000 Subject: [PATCH] Upgrade OpenStack support from AMI compatibility to qcow2 native Issues fixed: Output of kernel, init and inithooks correctly displayed in console. SystemD working again. Root disk is grown to match flavor size; done without reboot. Kernel updates possible. Old AMI version retained as openstack-ami. --- bin/generate-buildenv | 2 +- bin/openstack-bundle | 82 +++++++++-- bin/openstack-bundle-ami | 74 ++++++++++ bt-openstack | 8 +- bt-openstack-ami | 93 +++++++++++++ docs/setup | 5 + patches/openstack-ami/conf | 36 +++++ patches/openstack-ami/overlay/etc/fstab | 6 + patches/openstack/conf | 29 ++-- patches/openstack/overlay/etc/fstab | 3 +- .../everyboot.d/25ec2-userdata-idchange | 36 +++++ .../inithooks/firstboot.d/26ec2-resizerootfs | 129 ++++++++++++++++++ 12 files changed, 460 insertions(+), 43 deletions(-) create mode 100755 bin/openstack-bundle-ami create mode 100755 bt-openstack-ami create mode 100755 patches/openstack-ami/conf create mode 100644 patches/openstack-ami/overlay/etc/fstab create mode 100755 patches/openstack/overlay/usr/lib/inithooks/everyboot.d/25ec2-userdata-idchange create mode 100755 patches/openstack/overlay/usr/lib/inithooks/firstboot.d/26ec2-resizerootfs diff --git a/bin/generate-buildenv b/bin/generate-buildenv index 24addda..62c9aa9 100755 --- a/bin/generate-buildenv +++ b/bin/generate-buildenv @@ -45,7 +45,7 @@ _git_state() { } _common() { - git_state $BT + _git_state $BT echo "turnkey_version $(cat /etc/turnkey_version)" echo "ami-id $(which ec2metadata && ec2metadata --ami-id || echo none)" echo "awscli $(python -c 'import awscli; print awscli.__version__')" diff --git a/bin/openstack-bundle b/bin/openstack-bundle index 27141cc..0d4426b 100755 --- a/bin/openstack-bundle +++ b/bin/openstack-bundle @@ -33,6 +33,8 @@ rootfs=$1 name=$(echo $rootfs | sed 's/.rootfs//') appname=$(echo $name |sed 's/turnkey-\(.*\)-[0-9].*/\1/') + + case "$appname" in canvas) loopsize_padding=524288 ;; ejabberd) loopsize_padding=524288 ;; @@ -46,29 +48,79 @@ loopsize=$[$rootsize + $loopsize_padding] info "creating sparse loopback" dd if=/dev/null of=$rootfs.img bs=1 seek=${loopsize}K -mkfs.ext4 -F -j $rootfs.img -mkdir $rootfs.img.mount -mount -o loop $rootfs.img $rootfs.img.mount +info "creating partition" +#inspired by package openstack-debian-images +PARTED=/sbin/parted +AMI_NAME=$rootfs.img +${PARTED} -s ${AMI_NAME} mktable msdos +${PARTED} -s -a optimal ${AMI_NAME} mkpart primary ext3 1Mi 100% +${PARTED} -s ${AMI_NAME} set 1 boot on +install-mbr ${AMI_NAME} +RESULT_KPARTX=`kpartx -asv ${AMI_NAME} 2>&1` + +if echo "${RESULT_KPARTX}" | grep "^add map" ; then + LOOP_DEVICE=`echo ${RESULT_KPARTX} | cut -d" " -f3` + info "kpartx mounted using: ${LOOP_DEVICE}" +else + fatal "It seems kpartx didn't mount the image correctly: exiting." +fi + +cleanup(){ + error=$? + [ ! -d "${MOUNT_DIR}" ] && return + echo + echo "error $error, umounting $MOUNT_DIR" + chroot ${MOUNT_DIR} umount /proc || true + chroot ${MOUNT_DIR} umount /sys || true + umount ${MOUNT_DIR} + rmdir ${MOUNT_DIR} + kpartx -d ${AMI_NAME} + exit $error +} +trap "cleanup" EXIT TERM INT + +mkfs.ext4 -F -j /dev/mapper/${LOOP_DEVICE} +# No fsck because of X days without checks +tune2fs -i 0 /dev/mapper/${LOOP_DEVICE} + +MOUNT_DIR=`mktemp -d -t build-debimg.XXXXXX` +mount -o loop /dev/mapper/${LOOP_DEVICE} ${MOUNT_DIR} info "syncing rootfs to loopback" -rsync -a -t -r -S -I -H $rootfs/ $rootfs.img.mount +rsync -a -t -r -S -I -H $rootfs/ ${MOUNT_DIR} -info "umount loopback" -umount -d $rootfs.img.mount -rmdir $rootfs.img.mount +info "install extlinux" +mkdir -p ${MOUNT_DIR}/boot/extlinux +echo "default linux +timeout 1 +label linux +kernel /vmlinuz +append initrd=/initrd.img root=/dev/vda1 biosdevname=0 net.ifnames=0 console=tty0 console=ttyS0,115200 ro" > ${MOUNT_DIR}/boot/extlinux/extlinux.conf +extlinux --install ${MOUNT_DIR}/boot/extlinux -info "setting up image directory" -mkdir $name -mv $rootfs.img $name/$name.img -cp $rootfs/boot/vmlinuz-* $name/$name-kernel -cp $rootfs/boot/initrd.img-* $name/$name-initrd +info "umount loopback" +umount -d ${MOUNT_DIR} +rmdir ${MOUNT_DIR} + +fsck.ext3 -f /dev/mapper/${LOOP_DEVICE} || true + +#the next command failed once, so +sync +kpartx -d ${AMI_NAME} + +info "creating qcow2 image" +QCOW2_NAME=$name-openstack.qcow2 +QEMU_VERSION=`qemu-img --help | head -n 1 | cut -d" " -f3 | cut -d"," -f1` +if dpkg --compare-versions ${QEMU_VERSION} gt 1.0 ; then + OTHER_QEMU_IMG_OPTIONS=" -o compat=0.10" +else + OTHER_QEMU_IMG_OPTIONS="" +fi -info "creating $name-openstack.tar.gz" -tar --sparse -zcvf $name-openstack.tar.gz $name +qemu-img convert -c -f raw ${AMI_NAME}${OTHER_QEMU_IMG_OPTIONS} -O qcow2 ${QCOW2_NAME} if [ -z "$BT_DEBUG" ]; then info "removing directory" rm -rf $name fi - diff --git a/bin/openstack-bundle-ami b/bin/openstack-bundle-ami new file mode 100755 index 0000000..27141cc --- /dev/null +++ b/bin/openstack-bundle-ami @@ -0,0 +1,74 @@ +#!/bin/bash -e +# Copyright (c) 2011-2015 TurnKey GNU/Linux - http://www.turnkeylinux.org +# +# This file is part of buildtasks. +# +# Buildtasks is free software; you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. + + +fatal() { echo "FATAL [$(basename $0)]: $@" 1>&2; exit 1; } +info() { echo "INFO [$(basename $0)]: $@"; } + +usage() { +cat< $O/$name-openstack.tar.gz.buildenv +$BT/bin/generate-buildenv openstack $BT_ISOS/$isofile.sig > $O/$name-openstack.qcow2.buildenv # publish if specified if [ "$publish" == "yes" ]; then export PUBLISH_DEST=${BT_PUBLISH_IMGS}/openstack/ - $BT/bin/publish-files $O/$name-openstack.tar.gz + $BT/bin/publish-files $O/$name-openstack.qcow2 export PUBLISH_DEST=${BT_PUBLISH_META}/ - $BT/bin/publish-files $O/$name-openstack.{tar.gz.sig,tar.gz.buildenv} + $BT/bin/publish-files $O/$name-openstack.{qcow2.sig,qcow2.buildenv} fi if [ -z "$BT_DEBUG" ] && ! (mount | grep -q $(basename $rootfs)); then diff --git a/bt-openstack-ami b/bt-openstack-ami new file mode 100755 index 0000000..57d925f --- /dev/null +++ b/bt-openstack-ami @@ -0,0 +1,93 @@ +#!/bin/bash -e +# Copyright (c) 2011-2015 TurnKey GNU/Linux - http://www.turnkeylinux.org +# +# This file is part of buildtasks. +# +# Buildtasks is free software; you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. + + +fatal() { echo "FATAL [$(basename $0)]: $@" 1>&2; exit 1; } +warning() { echo "WARNING [$(basename $0)]: $@"; } +info() { echo "INFO [$(basename $0)]: $@"; } + +usage() { +cat< $O/$name-openstack.tar.gz.buildenv + +# publish if specified +if [ "$publish" == "yes" ]; then + export PUBLISH_DEST=${BT_PUBLISH_IMGS}/openstack/ + $BT/bin/publish-files $O/$name-openstack.tar.gz + + export PUBLISH_DEST=${BT_PUBLISH_META}/ + $BT/bin/publish-files $O/$name-openstack.{tar.gz.sig,tar.gz.buildenv} +fi + +if [ -z "$BT_DEBUG" ] && ! (mount | grep -q $(basename $rootfs)); then + rm -rf $rootfs + rm -rf $cdroot +fi + diff --git a/docs/setup b/docs/setup index 45789fc..a4afb67 100644 --- a/docs/setup +++ b/docs/setup @@ -22,6 +22,11 @@ bt-ec2 :: apt-get install parted ec2metadata pip install boto +bt-openstack :: + + apt-get install parted ec2metadata mbr qemu kpartx extlinux + pip install boto + bt-vm :: # vmware ovftool diff --git a/patches/openstack-ami/conf b/patches/openstack-ami/conf new file mode 100755 index 0000000..84cffd5 --- /dev/null +++ b/patches/openstack-ami/conf @@ -0,0 +1,36 @@ +#!/bin/bash -ex + +install() { + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get -y \ + -o DPkg::Options::=--force-confdef \ + -o DPkg::Options::=--force-confold \ + install $@ +} + +# install useful packages +install ebsmount sysvinit-core systemd-shim + +# remove systemd (sysvinit used in container) +dpkg --purge systemd-sysv systemd || true + +# support hot-plugging of attached volumes +echo "acpiphp" >> /etc/modules + +# hold kernel (not used in image, pro-longs sec-updates) +ARCH=$(dpkg --print-architecture) +case "$ARCH" in + "i386") + META_KERNEL="linux-image-686"; + ;; + "amd64") + META_KERNEL="linux-image-amd64"; + ;; + *) + fatal "non-supported architecture: $ARCH"; + ;; +esac +KERNEL=$(echo /boot/vmlinuz-* | sed 's|/boot/vmlinuz-|linux-image-|') +echo "$KERNEL hold" | dpkg --set-selections +echo "$META_KERNEL hold" | dpkg --set-selections + diff --git a/patches/openstack-ami/overlay/etc/fstab b/patches/openstack-ami/overlay/etc/fstab new file mode 100644 index 0000000..cfbe00d --- /dev/null +++ b/patches/openstack-ami/overlay/etc/fstab @@ -0,0 +1,6 @@ +# /etc/fstab: static file system information. +# +proc /proc proc nodev,noexec,nosuid 0 0 +/dev/vda / ext4 defaults 0 0 +/dev/vdb /mnt auto defaults 0 0 + diff --git a/patches/openstack/conf b/patches/openstack/conf index 84cffd5..3a0979f 100755 --- a/patches/openstack/conf +++ b/patches/openstack/conf @@ -8,29 +8,16 @@ install() { install $@ } +#conflicts with cloud-utils, which is a dependency of cloud-initramfs-growroot +dpkg --purge ec2metadata + # install useful packages -install ebsmount sysvinit-core systemd-shim +install ebsmount cloud-initramfs-growroot extlinux -# remove systemd (sysvinit used in container) -dpkg --purge systemd-sysv systemd || true +#on the other hand, inithooks needs it and the other implementation is written by Alon Swartz anyway +DEBIAN_FRONTEND=noninteractive apt-get -y -o DPkg::Options::=--force-confdef \ + -o DPkg::Options::=--force-confold -o DPkg::Options::=--force-overwrite \ + install ec2metadata # support hot-plugging of attached volumes echo "acpiphp" >> /etc/modules - -# hold kernel (not used in image, pro-longs sec-updates) -ARCH=$(dpkg --print-architecture) -case "$ARCH" in - "i386") - META_KERNEL="linux-image-686"; - ;; - "amd64") - META_KERNEL="linux-image-amd64"; - ;; - *) - fatal "non-supported architecture: $ARCH"; - ;; -esac -KERNEL=$(echo /boot/vmlinuz-* | sed 's|/boot/vmlinuz-|linux-image-|') -echo "$KERNEL hold" | dpkg --set-selections -echo "$META_KERNEL hold" | dpkg --set-selections - diff --git a/patches/openstack/overlay/etc/fstab b/patches/openstack/overlay/etc/fstab index cfbe00d..88d3bde 100644 --- a/patches/openstack/overlay/etc/fstab +++ b/patches/openstack/overlay/etc/fstab @@ -1,6 +1,5 @@ # /etc/fstab: static file system information. # proc /proc proc nodev,noexec,nosuid 0 0 -/dev/vda / ext4 defaults 0 0 -/dev/vdb /mnt auto defaults 0 0 +/dev/vda1 / ext4 defaults 0 0 diff --git a/patches/openstack/overlay/usr/lib/inithooks/everyboot.d/25ec2-userdata-idchange b/patches/openstack/overlay/usr/lib/inithooks/everyboot.d/25ec2-userdata-idchange new file mode 100755 index 0000000..012a353 --- /dev/null +++ b/patches/openstack/overlay/usr/lib/inithooks/everyboot.d/25ec2-userdata-idchange @@ -0,0 +1,36 @@ +#!/bin/bash -e +# process userdata if instance id has changed (ie. snapshot launch) + +. /etc/default/inithooks + +EC2_METADATA_CACHE=/var/lib/ec2metadata + +set_instanceid_fs() { + mkdir -p $EC2_METADATA_CACHE + echo $1 > $EC2_METADATA_CACHE/instance-id +} + +get_instanceid_fs() { + if [ -e $EC2_METADATA_CACHE/instance-id ]; then + cat $EC2_METADATA_CACHE/instance-id + fi +} + +get_instanceid_md() { + /usr/bin/ec2metadata --instance-id +} + +INSTANCEID_FS=$(get_instanceid_fs) +INSTANCEID_MD=$(get_instanceid_md) + +if [ "$INSTANCEID_FS" ]; then + if [ "$INSTANCEID_FS" != "$INSTANCEID_MD" ]; then + set_instanceid_fs $INSTANCEID_MD + $INITHOOKS_PATH/firstboot.d/25ec2-userdata + fi +else + set_instanceid_fs $INSTANCEID_MD +fi + +exit 0 + diff --git a/patches/openstack/overlay/usr/lib/inithooks/firstboot.d/26ec2-resizerootfs b/patches/openstack/overlay/usr/lib/inithooks/firstboot.d/26ec2-resizerootfs new file mode 100755 index 0000000..a052efc --- /dev/null +++ b/patches/openstack/overlay/usr/lib/inithooks/firstboot.d/26ec2-resizerootfs @@ -0,0 +1,129 @@ +#!/usr/bin/python +# Author: Alon Swartz + +import os +import sys + +if '_TURNKEY_INIT' in os.environ: + sys.exit(0) + +import stat +import statvfs +import tempfile + +from executil import system, ExecError + +def get_mounts(mounts_file="/proc/mounts"): + """ + Given a mounts file (e.g., /proc/mounts), generate dicts with the + following keys: + + - device: The device file which is mounted. + - mount-point: The path at which the filesystem is mounted. + - filesystem: The filesystem type. + - total-space: The capacity of the filesystem in kbytes. + """ + for line in open(mounts_file): + + try: + device, mount_point, filesystem = line.split()[:3] + mount_point = mount_point.decode("string-escape") + except ValueError: + continue + + stats = os.statvfs(mount_point) + block_size = stats[statvfs.F_BSIZE] + total_space = (stats[statvfs.F_BLOCKS] * block_size) / 1024 + + yield { "device": device, + "mount-point": mount_point, + "filesystem": filesystem, + "total-space": int(total_space) } + +def get_partitions(partitions_file="/proc/partitions"): + """ + Given a partitions file (e.g., /proc/partitions), generate dicts with the + following keys: + + - major: Major block + - minor: Minor block + - blocks: Amount of blocks + - name: Partition name + """ + for line in open(partitions_file): + + try: + major, minor, blocks, name = line.split() + blocks = int(blocks) + except ValueError: + continue + + yield { "major": major, + "minor": minor, + "blocks": blocks, + "name": name } + +class RootFS: + SUPPORTED_FILESYSTEMS = ('ext2', 'ext3', 'ext4') + + def __init__(self, mountpoint): + self.mountpoint = mountpoint + + m = self._get_mount(self.mountpoint) + self.device = m['device'] + self.filesystem = m['filesystem'] + self.filesystem_size = float(m['total-space']) + + p = self._get_partition(self.device) + self.partition_size = float(p['blocks']) + + self.fs_gb = round(self.filesystem_size / (1024 * 1024)) + self.pt_gb = round(self.partition_size / (1024 * 1024)) + + @staticmethod + def _get_mount(mountpoint): + for m in get_mounts(): + if m['device'].startswith('/dev/') and m['mount-point'] == mountpoint: + return m + + @staticmethod + def _get_partition(device_name): + for p in get_partitions(): + if p['name'] == device_name.replace('/dev/', ''): + return p + + @property + def has_unused_space(self): + return True if self.pt_gb > self.fs_gb else False + + @property + def has_supported_filesystem(self): + return True if self.filesystem in self.SUPPORTED_FILESYSTEMS else False + + def resize_filesystem(self): + fd, devpath = tempfile.mkstemp(dir=self.mountpoint) + os.unlink(devpath) + os.close(fd) + + st_dev = os.stat(self.mountpoint).st_dev + dev = os.makedev(os.major(st_dev), os.minor(st_dev)) + os.mknod(devpath, 0400 | stat.S_IFBLK, dev) + + try: + system("resize2fs", devpath) + except ExecError: + os.unlink(devpath) + raise + + os.unlink(devpath) + + +def main(): + rootfs = RootFS(mountpoint="/") + if rootfs.has_unused_space and rootfs.has_supported_filesystem: + print "Resizing %s: %sG to %sG" % (rootfs.device, rootfs.fs_gb, rootfs.pt_gb) + rootfs.resize_filesystem() + +if __name__ == "__main__": + main() +