Source code for utah.provisioning.baremetal.bamboofeeder

# Ubuntu Testing Automation Harness
# Copyright 2012 Canonical Ltd.

# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.

# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Support provisioning of bamboo-feeder-based systems."""


import os
import pipes
import shutil
import subprocess
import tempfile

from utah.config import config
from utah.process import ProcessRunner
from utah.provisioning.baremetal.exceptions import UTAHBMProvisioningException
from utah.provisioning.baremetal.power import PowerMixin
from utah.provisioning.provisioning import (
    CustomInstallMixin,
    Machine,
)
from utah.provisioning.ssh import SSHMixin
from utah.retry import retry


[docs]class BambooFeederMachine(CustomInstallMixin, SSHMixin, PowerMixin, Machine): """Provision and manage an ARM board in a bamboo-feeder setup.""" # TODO: raise more exceptions if ProcessRunner fails # maybe get some easy way to do that like failok def __init__(self, machineinfo=config.machineinfo, name=config.name, preboot=config.preboot, *args, **kw): # TODO: respect rewrite setting if name is None: raise UTAHBMProvisioningException( 'Machine name reqired for bamboo-feeder machine') try: self.macaddress = machineinfo['mac-address'] except AttributeError: raise UTAHBMProvisioningException('No MAC address specified') super(BambooFeederMachine, self).__init__(*args, machineinfo=machineinfo, name=name, **kw) if self.inventory is not None: self.cleanfunction(self.inventory.release, machine=self) if self.image is None: raise UTAHBMProvisioningException( 'Image file required for bamboo-feeder installation') self._depcheck() self.ip = self._ipaddr(config.wwwiface) self.logger.debug('Configuring for %s with IP %s', config.wwwiface, self.ip) self._cmdlinesetup() imageurl = 'http://{}/utah/{}.img'.format(self.ip, self.name) preenvurl = 'http://{}/utah/{}.preEnv'.format(self.ip, self.name) self.preboot = (preboot or 'console=ttyO2,115200n8 imageurl={imageurl} ' 'bootcfg={preenvurl}' .format(imageurl=imageurl, preenvurl=preenvurl)) self.logger.debug('Preboot setup:') self.logger.debug(self.preboot) self.logger.debug('BambooFeederMachine init finished') def _prepareimage(self): """Make a copy of the image and share it via www. :returns: Path to www-available image :rtype: str """ self.logger.info('Making copy of install image') self.wwwimage = os.path.join(config.wwwdir, '{}.img'.format(self.name)) self.logger.debug('Copying %s to %s', self.image, self.wwwimage) self.cleanfile(self.wwwimage) shutil.copyfile(self.image, self.wwwimage) return self.wwwimage def _mountimage(self): """Mount an ARM boot image.""" self.logger.info('Mounting install image') self.imagedir = os.path.join(self.tmpdir, 'image.d') if not os.path.isdir(self.imagedir): os.makedirs(self.imagedir) try: output = subprocess.check_output(['file', self.wwwimage]).strip() self.sector = output.split(';')[1].split(',')[3].split()[1] except (OSError, subprocess.CalledProcessError) as err: raise UTAHBMProvisioningException('Failed to get image info: {}' .format(err)) self.logger.debug('Image start sector is %s', str(self.sector)) self.cleanfunction(self._umountimage) self.logger.debug('Mounting %s at %s', self.wwwimage, self.imagedir) ProcessRunner(['sudo', 'mount', self.wwwimage, self.imagedir, '-o', 'loop', '-o', 'offset={}'.format(str(self.sector * 512)), '-o', 'uid={}'.format(config.user)]) def _setupconsole(self): """Setup the install to use a serial console.""" self.logger.info('Setting up the install to use the serial console') preenvfile = os.path.join(self.imagedir, 'preEnv.txt') serialpreenvfile = os.path.join(self.imagedir, 'preEnv.txt-serial') self.logger.debug('Copying %s to %s', serialpreenvfile, preenvfile) shutil.copyfile(serialpreenvfile, preenvfile) def _unpackinitrd(self): """Unpack the uInitrd file into a directory.""" self.logger.info('Unpacking uInitrd') if not os.path.isdir(os.path.join(self.tmpdir, 'initrd.d')): os.makedirs(os.path.join(self.tmpdir, 'initrd.d')) os.chdir(os.path.join(self.tmpdir, 'initrd.d')) filesize = os.path.getsize(self.initrd) try: for line in subprocess.check_output( ['mkimage', '-l', self.initrd]).splitlines(): if 'Data Size:' in line: datasize = int(line.split()[2]) break except (OSError, subprocess.CalledProcessError) as err: raise UTAHBMProvisioningException('Failed to get initrd info: {}' .format(err)) headersize = filesize - datasize self.logger.debug('uInitrd header size is %s', str(headersize)) pipe = pipes.Template() pipe.prepend('dd if=$IN bs=1 skip={} 2>/dev/null' .format(str(headersize)), 'f-') pipe.append('gunzip', '--') pipe.append('cpio -ivd 2>/dev/null', '-.') exitstatus = pipe.copy(self.initrd, '/dev/null') if exitstatus != 0: # Currently this comes up as 512 when things seem fine # TODO: refactor this and check for codes at all stages self.logger.debug( 'Unpacking initrd exited with status {}'.format(exitstatus)) def _repackinitrd(self): """Repack the uInitrd file from the initrd.d directory.""" self.logger.info('Repacking uInitrd') os.chdir(os.path.join(self.tmpdir, 'initrd.d')) pipe = pipes.Template() pipe.prepend('find .', '.-') pipe.append('cpio --quiet -o -H newc', '--') pipe.append('gzip -9fc', '--') initrd = os.path.join(self.tmpdir, 'initrd.gz') self.logger.debug('Repacking compressed initrd') exitstatus = pipe.copy('/dev/null', initrd) if exitstatus != 0: raise UTAHBMProvisioningException( 'Unpacking initrd exited with status: {}'.format(exitstatus)) self.logger.debug('Creating uInitrd with mkimage') ProcessRunner(['mkimage', '-A', 'arm', '-O', 'linux', '-T', 'ramdisk', '-C', 'gzip', '-a', '0', '-e', '0', '-n', 'initramfs', '-d', initrd, self.initrd]) def _umountimage(self): """Unmount the image after we're done with it.""" self.logger.info('Unmounting image') ProcessRunner(['sudo', 'umount', self.imagedir]) def _cmdlinesetup(self, boot=None): """Add options to the command line for an automatic install.""" if boot is None: boot = self.boot super(BambooFeederMachine, self)._cmdlinesetup(boot=boot) # TODO: minimize these for option in ('auto', 'ro', 'text'): if option not in self.cmdline: self.logger.info('Adding boot option: %s', option) self.cmdline += ' {}'.format(option) for parameter in ( ('cdrom-detect/try_usb', 'true'), ('console', 'ttyO2,115200n8'), ('country', 'US'), ('hostname', self.name), ('language', 'en'), ('locale', 'en_US'), ('loghost', self.ip), ('log_port', '10514'), ('netcfg/choose_interface', 'auto'), ('url', 'http://{}/utah/{}.cfg'.format(self.ip, self.name)), ): if parameter[0] not in self.cmdline: self.logger.info('Adding boot option: %s', '='.join(parameter)) self.cmdline += ' {}'.format('='.join(parameter)) self.cmdline.strip() def _configurepxe(self): """Setup PXE configuration to boot remote image.""" # TODO: Maybe move this into pxe.py # TODO: look into cutting out the middleman/ # booting straight into the installer? (maybe nfs?) self.logger.info('Configuring PXE') pxeconfig = """default utah-bamboofeeder prompt 0 timeout 3 label utah/bamboofeeder kernel panda/uImage append {preboot} initrd panda/uInitrd """.format(preboot=self.preboot) tmppxefile = os.path.join(self.tmpdir, 'pxe') open(tmppxefile, 'w').write(pxeconfig) self.logger.debug('PXE info written to %s', tmppxefile) pxefile = os.path.join( config.pxedir, '01-{}'.format(self.macaddress.replace(':', '-'))) self.cleancommand(('sudo', 'rm', '-f', pxefile)) self.logger.debug('Copying %s to %s', tmppxefile, pxefile) ProcessRunner(['sudo', 'cp', tmppxefile, pxefile]) preenvfile = os.path.join(config.wwwdir, '{}.preEnv'.format(self.name)) # TODO: sort this out with self.boot # figure out which one should be what and customizable self.preenv = 'bootargs={}'.format(self.cmdline) self.logger.debug('Preenv setup:') self.logger.debug(self.preenv) self.cleanfile(preenvfile) self.logger.debug('Writing preenv setup to %s', preenvfile) open(preenvfile, 'w').write(self.preenv) def _create(self, provision_data): """Install the OS on the system.""" # TODO: more checks and exceptions for failures self.logger.info('Preparing system install') self.tmpdir = tempfile.mkdtemp(prefix='/tmp/{}_'.format(self.name)) self.cleanfile(self.tmpdir) self.logger.debug('Using %s as temp dir', self.tmpdir) os.chdir(self.tmpdir) self._prepareimage() self._mountimage() self._setupconsole() self.initrd = os.path.join(self.imagedir, 'uInitrd') self.logger.debug('uInitrd is %s', self.initrd) self._unpackinitrd() if provision_data: initrd_dir = os.path.join(self.tmpdir, 'initrd.d') provision_data.update_initrd(initrd_dir) self._setuplatecommand() self._setuppreseed() self.logger.debug('Copying preseed to download location') preseedfile = os.path.join(config.wwwdir, '{}.cfg'.format(self.name)) self.cleanfile(preseedfile) shutil.copyfile(os.path.join(self.tmpdir, 'initrd.d', 'preseed.cfg'), preseedfile) self._repackinitrd() self._configurepxe() self._umountimage() self.restart() self.logger.info('System installing') self.rsyslog.wait_for_install() self.rsyslog.wait_for_booted(self.uuid) retry(self.sshcheck, logmethod=self.logger.info, retry_timeout=config.checktimeout) self.active = True self.logger.info('System installed') self.cleanfunction(self.run, ( 'dd', 'bs=512k', 'count=10', 'if=/dev/zero', 'of=/dev/mmcblk0'), root=True) def _depcheck(self): """Check for dependencies that are in Recommends or Suggests.""" super(BambooFeederMachine, self)._depcheck() cmd = ['which', 'mkimage'] if ProcessRunner(cmd).returncode != 0: raise UTAHBMProvisioningException('u-boot-tools not installed')
Read the Docs v: latest
Versions
latest
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.