Source code for utah.provisioning.provisioning

# 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/>.

"""
Provide common functions for provisioning and interacting with machines.
Functions here should apply to multiple machine types (VM, bare metal, etc.)
"""

import logging
import logging.handlers
import os
import pipes
import re
import shutil
import sys
import urllib
import uuid

import netifaces

import utah.timeout

from utah import template
from utah.cleanup import cleanup
from utah.config import config
from utah.process import ProcessRunner
from utah.preseed import Preseed
from utah.provisioning.debs import get_client_debs
from utah.provisioning.rsyslog import RSyslog
from utah.provisioning.exceptions import UTAHProvisioningException
from utah.retry import retry


[docs]class Machine(object): # TODO: check if we need a general restart method """Provide a generic class to provision an arbitrary machine. Raise exceptions for most methods, since subclasses should provide them. run, uploadfiles, and downloadfiles are the most important methods for interacting with a machine. Some method of satisfying activecheck is generally required to run commands on a machine (i.e., implementing _start or reimplementing activecheck.) Some method of satisfying provisioncheck is generally required to install on a machine (i.e., implementing some combination of _create, _load, and _provision, or reimplementing provisioncheck.) Installation may be separated from the Machine classes in the future. """ def __init__(self, boot=config.boot, clean=True, debug=False, image=None, initrd=config.initrd, inventory=None, kernel=config.kernel, machineid=config.machineid, machineuuid=config.machineuuid, name=config.name, new=False, preseed=config.preseed, rewrite=config.rewrite, template=config.template, xml=config.xml): """Initialize the object representing the machine. One of these groups of arguments should be included: image, kernel, initrd, preseed: Install the machine from a specified image object, with an optional kernel, initrd, and preseed. libvirt xml file can be passed in xml as well. template: Clone the machine from a template or existing machine. name: Request a specific machine. Combine with other groups to reinstall a specific machine. The subclass is responsible for provisioning with these arguments. Other arguments: clean: Enable cleanup functions. debug: Enable debug logging. inventory: Inventory object managing the machine; used for cleanup purposes. machineid: Should be passed by the request method of an Inventory class. Often used to name virtual machines. machineuuid: Unique identifier. Will be randomly generated if not supplied. new: Request a new machine (or a reinstall if a specific machine was requested.) rewrite: How much to alter supplied preseed and xml files. all: everything we need for an automated install minimal: insert latecommand into preseed insert preseed into casper (desktop only) edit VM XML to handle reboots properly casperonly: insert preseed into casper (desktop only) none: do not alter preseed or VM XML | preseed | casper | VM xml | other | ----------+---------+--------+--------+-------+ all | X | X | X | X | minimal | X | X | X | | casperonly| | X | | | none | | | | | """ # TODO: Make this work right with super at some point. # TODO: Consider a global temp file creator, maybe as part of install. self.boot = boot self.clean = clean self.debug = debug self.image = image self.inventory = inventory self.machineid = machineid self.new = new self.rewrite = rewrite self.template = template self.uuid = uuid # TODO: Move namesetup into vm self._namesetup(name) if machineuuid is None: self.uuid = str(uuid.uuid4()) else: self.uuid = machineuuid self.provisioned = False self.active = False self._loggersetup() fileargs = ['initrd', 'kernel', 'preseed', 'xml'] # TODO: make this a function that lives somewhere else: # TODO: maybe move this preparation out of this class for item in fileargs: # Ensure every file/url type argument is available locally arg = locals()[item] if arg is None: setattr(self, item, None) else: if arg.startswith('~'): path = os.path.expanduser(arg) self.logger.debug('Rewriting ~-based path: %s to: %s', arg, path) else: path = arg self.percent = 0 self.logger.info('Preparing %s: %s', item, path) # TODO: implement download utility function and use it here filename = urllib.urlretrieve(path, reporthook=self.dldisplay)[0] setattr(self, item, filename) self.logger.debug('%s is locally available as %s', path, getattr(self, item)) self.finalpreseed = self.preseed self.logger.debug('Machine init finished') @property
[docs] def rsyslog(self): """Use the default rsyslog method if no override is in place. :returns: rsyslog method for this machine :rtype: object """ if not getattr(self, '_rsyslog', None): self._rsyslog = RSyslog(self.name, config.logpath) return self._rsyslog
def _namesetup(self, name=None): """Name the machine, automatically or using a specified name. :param name: Name to use for this machine :type name: str """ if name is None: self.name = self._makename() else: self.name = name def _makename(self, machineid=None, prefix=None): """Return a name for the machine based on how it will be installed. Generally used for VMs to comply with old vm-tools naming conventions. Probably could be reduced or removed. :param machineid: Unique numerical machine identifier :type machineid: int :param prefix: Prefix for machine name (defaults to utah) :type prefix: str :returns: Generated machine name, like utah-1-precise-i386 :rtype: str """ if machineid is None: machineid = str(self.machineid) if prefix is None: prefix = str(self.prefix) if machineid is not None: prefix += ('-{}'.format(str(machineid))) self.prefix = prefix if self.image.installtype == 'desktop': name = '-'.join([prefix, self.image.series, self.image.arch]) else: name = '-'.join([prefix, self.image.series, self.image.installtype, self.image.arch]) return name def _loggersetup(self): """Initialize the logging for the machine. Subclasses can override or supplement to customize logging. """ self.logger = logging.getLogger(self.name) def _loggerunsetup(self): """Remove logging. Principally used when a machine changes name after init. """ del self.logger
[docs] def provisioncheck(self, provision_data=None): """Ensure the machine is provisioned and installed. Check if the machine is provisioned, provision it if necessary, run an install if needed, and raise an exception if it cannot be provisioned, or if it exceeds the timeout value set in config for installation. :raises UTAHProvisioningException: When the machine fails to install within the timeout value. """ self.logger.debug('Checking if machine is provisioned') if not self.provisioned: utah.timeout.timeout( config.installtimeout, self._provision, provision_data)
[docs] def activecheck(self): """Ensure the machine is active and capable of accepting commands. Check if the machine is running and able to accept commands, start it if necessary, and raise an exception if it cannot be started. """ self.logger.debug('Checking if machine is active') self.provisioncheck() if not self.active: self._start()
[docs] def pingcheck(self): """Check network connectivity using ping. :raises UTAHProvisioningException: When ping fails. .. seealso:: :func:`utah.retry.retry`, :meth:`pingpoll` """ self.logger.info('Checking network connectivity (ping)') returncode = \ ProcessRunner(['ping', '-c1', '-w5', self.name]).returncode if returncode != 0: err = 'Ping returned {0}'.format(returncode) raise UTAHProvisioningException(err, retry=True)
[docs] def pingpoll(self, timeout=None, checktimeout=config.checktimeout, logmethod=None): """Run pingcheck over and over until timeout expires. :param timeout: How long to try pinging the machine before giving up :type timeout: int or None :param checktimeout: How long to wait between pings :type checktimeout: int :param logmethod: Function to use for logging :type logmethod: function .. seealso:: :func:`utah.timeout.timeout` :func:`utah.retry.retry` :meth:`pingcheck` """ if timeout is None: timeout = config.boottimeout if logmethod is None: logmethod = self.logger.debug utah.timeout.timeout(timeout, retry, self.pingcheck, logmethod=logmethod, retry_timeout=checktimeout)
[docs] def installclient(self): """Install the required packages on the machine. Install the python-jsonschema, utah-common, and utah-client packages. :raises UTAHProvisioningException: When packages won't install """ self.logger.info('Installing client deb on machine') tmppath = os.path.normpath('/tmp') for deb in get_client_debs(): try: self.uploadfiles([deb], tmppath) except UTAHProvisioningException as err: try: for myfile in err.files: if deb in myfile: raise UTAHProvisioningException( 'File not found: {filename}; ' 'UTAH was probably updated during the run. ' 'Please try again.' .format(filename=deb), retry=True) raise err except AttributeError: raise err deb = os.path.join(tmppath, os.path.basename(deb)) cmd = template.as_buff('install-deb-command.jinja2', deb=deb) def install_client(): returncode, _stdout, stderr = self.run(cmd, root=True) if (returncode != 0 or re.search(r'script returned error exit status \d+', stderr)): raise UTAHProvisioningException('Failed to install client', retry=True) utah.timeout.timeout(config.client_install_timeout, retry, install_client)
def _provision(self, provision_data): """Ensure the machine is available and installed. Ready the machine for use, and set provisioned to True. If new=True or the machine is not installed, this should install the machine, possibly by calling _create(). If an existing machine is requested, this should make the machine available, possibly by calling _load(). Should generally not be called directly outside of the class; provisioncheck() or activecheck() should be used. """ if not self.new: try: self.logger.debug('Trying to load existing machine') self._load() self.provisioned = True return except Exception as err: self.logger.debug('Failed to load machine: %s', str(err)) self._create(provision_data) self.provisioned = True def _create(self, provision_data): # TODO: discuss separation of this vs. start when separating Install """Install the machine. Install the operating system on the machine. Takes no arguments, all needed variables should be set during __init__ Should generally not be called directly outside of the class; provisioncheck() or activecheck() should be used. :raises UTAHProvisioingException: Currently, subclasses raise appropriate subclass of this exception """ self._unimplemented(sys._getframe().f_code.co_name)
[docs] def destroy(self): """Free up the machine and associated resources. Release resources consumed by the machine, set provisioned to False, and return True on success. For a VM, this should destroy the VM files on the disk, and remove it from libvirt if it is registered there. For a physical machine, this should free it up to be used by another process in the future, and destroy sensitive data if any exists. Destroying the install on a physical machine is optional, but the machine must be left in a state where a new install can start. """ self.provisioned = False del self
def _load(self): """Prepare an existing machine for use. Should generally not be called directly outside of the class; provisioncheck() or activecheck() should be used. """ # TODO: decide if this should set self.provisioned self._unimplemented(sys._getframe().f_code.co_name) def _start(self): """Start the machine, and set active to True. Should generally not be called directly outside of the class; activecheck() should be used. """ self._unimplemented(sys._getframe().f_code.co_name)
[docs] def stop(self, _force=False): """Stop the machine, and set active to False. :param force: If False, attempt graceful shutdown :type false: bool """ self._unimplemented(sys._getframe().f_code.co_name)
[docs] def uploadfiles(self, _files, _target=os.path.normpath('/tmp/')): """Upload a list of local files to a target on the machine. :param files: File(s) to upload :type files: list or string :param target: Remote path to upload files :type target: string """ # TODO: Decide whether recursion is optional, mandatory, default # TODO: figure out if we should return some success/failure metric self._unimplemented(sys._getframe().f_code.co_name)
[docs] def downloadfiles(self, _files, _target=os.path.normpath('/tmp/')): """Download a list of files from the machine to a local target. Support for files as a string specifying a single file is recommended but not required. target should be a valid target path for cp, i.e. a filename for a single file, a directory name for multiple files. Recursive directory download is not currently supported by all implementations. """ # TODO: Decide whether recursion is optional, mandatory, default # TODO: figure out if we should return some success/failure metric self._unimplemented(sys._getframe().f_code.co_name)
[docs] def run(self, _command, _quiet=None, _root=False, _timeout=None): """Run a command on the machine. :param quiet: If True, suppress output from helper programs like ssh :type quiet: bool :param root: If True, execute command with elevated privileges :type root: bool :param timeout: Number of seconds to wait before timing out :type timeout: int or None :returns: A tuple of the form (returncode, stdout, stder) :rtype: tuple """ self._unimplemented(sys._getframe().f_code.co_name)
[docs] def dldisplay(self, blocks, size, total): """Log download information (i.e., as a urllib callback). :param blocks: Number of blocks downloaded :type blocks: int :param size: Size of blocks downloaded :type size: int :param total: Total size of download :type size: int """ # TODO: get this and the one in iso.py into the same place read = blocks * size percent = 100 * read / total if percent >= self.percent: self.logger.info('File %s%% downloaded', percent) self.percent += config.dlpercentincrement self.logger.debug('%s read, %s%% of %s total', read, percent, total)
def _depcheck(self): """Check for dependencies that are in Recommends or Suggests. No special dependencies exist for the main class, but we implement it here to allow super() to work. """ pass def _unimplemented(self, method): """Log download information (i.e., as a urllib callback). :param blocks: Number of blocks downloaded :type blocks: int :param size: Size of blocks downloaded :type size: int :param total: Total size of download :type size: int """ raise UTAHProvisioningException( '{cls} attempted to call the {method} method ' 'of the base Machine class, which is not implemented' .format(cls=self.__class__.__name__, method=method))
[docs] def cleanfile(self, path): """Register a path to be cleaned later. :param path: A link, file, or directory to be cleaned later. :type path: str .. seealso:: :meth:`utah.cleanup._Cleanup.add_path` """ # TODO: consider doing this directly instead of through Machine if self.clean: cleanup.add_path(path)
[docs] def cleanfunction(self, function, *args, **kw): """Register a function to be run on cleanup. :param function: A callable that will do some cleanup. :type function: callable :param args: Positional arguments to the function. :type args: Tuple :param kw: Keyword arguments to the function. :type args: dict .. seealso:: :meth:`utah.cleanup._Cleanup.add_function` """ # TODO: consider doing this directly instead of through Machine if self.clean: cleanup.add_function(60, function, *args, **kw)
[docs] def cleancommand(self, cmd): """Register a command to be run on cleanup. :param cmd: A command as would be passed `subprocess.Popen`. :type cmd: iterable .. seealso:: :meth:`utah.cleanup._Cleanup.add_command` """ # TODO: consider doing this directly instead of through Machine if self.clean: cleanup.add_command(cmd)
[docs]class CustomInstallMixin(object): """Provide routines for automating an install from an image.""" def _preparekernel(self, kernel=None, tmpdir=None): """Copy a kernel to our temp directory. If a kernel was manually specified, use that. If we haven't been given a kernel file, unpack one from the image. :param kernel: Local path to the kernel to use :type filename: str :param tmpdir: Path to temp directory :type kernel: str :returns: Path to kernel file in temp directory :rtype: str :raises UTAHProvisioningException: If there's a problem copying the kernel file. .. seealso:: :func:`_prepareinitrd` """ self.logger.info('Preparing kernel') if kernel is None: kernel = self.kernel if tmpdir is None: tmpdir = self.tmpdir mykernel = os.path.join(tmpdir, 'kernel') if kernel is None: self.logger.info('Unpacking kernel from image') kernelpath = self.image.kernelpath() self.image.extract(kernelpath, outfile=mykernel) else: self.logger.info('Copying local kernel: %s', kernel) try: shutil.copyfile(kernel, mykernel) except IOError as err: raise UTAHProvisioningException( 'Failed to copy local kernel: {}'.format(err)) return mykernel def _prepareinitrd(self, initrd=None, tmpdir=None): """Copy an initrd to our temp directory. If an initrd was manually specified, use that. If we haven't been given an initrd file, unpack one from the image. :param initrd: Local path to the kernel to use :type initrd: str :param tmpdir: Path to temp directory :type tmpdir: str :returns: Path to initrd file in temp directory :rtype: str :raises UTAHProvisioningException: If there's a problem copying the initrd file. .. seealso:: :func:`_preparekernel` """ self.logger.info('Preparing initrd') if initrd is None: initrd = self.initrd if tmpdir is None: tmpdir = self.tmpdir if initrd is None: self.logger.info('Unpacking initrd from image') initrdpath = './install/initrd.gz' if self.image.installtype == 'mini': self.logger.debug('Mini image detected') initrdpath = 'initrd.gz' elif self.image.installtype == 'desktop': self.logger.debug('Desktop image detected') # TODO: scan for this like desktop initrdpath = './casper/initrd.lz' myinitrd = os.path.join(tmpdir, os.path.basename(initrdpath)) self.image.extract(initrdpath, outfile=myinitrd) else: self.logger.info('Using local initrd: %s', initrd) myinitrd = os.path.join(tmpdir, os.path.basename(initrd)) try: shutil.copyfile(initrd, myinitrd) except IOError as err: raise UTAHProvisioningException( 'Failed to copy local initrd: {}'.format(err)) return myinitrd def _unpackinitrd(self, initrd=None, tmpdir=None): """Unpack the initrd file into a directory so we can modify it.""" self.logger.info('Unpacking initrd') if initrd is None: initrd = self.initrd if tmpdir is None: tmpdir = self.tmpdir try: if not os.path.isdir(os.path.join(tmpdir, 'initrd.d')): os.makedirs(os.path.join(tmpdir, 'initrd.d')) os.chdir(os.path.join(tmpdir, 'initrd.d')) except OSError as err: raise UTAHProvisioningException( 'Error using temp directory {}: {}'.format(tmpdir, err)) pipe = pipes.Template() if os.path.splitext(initrd)[1] == '.gz': self.logger.debug('Using gzip based on file extension') pipe.prepend('zcat $IN', 'f-') elif os.path.splitext(initrd)[1] == '.lz': self.logger.debug('Using lzma based on file extension') pipe.prepend('lzcat -S .lz $IN', 'f-') else: raise UTAHProvisioningException( 'initrd file does have have gz or lz extension: {}' .format(initrd)) pipe.append('cpio -ivd 2>/dev/null', '-.') exitstatus = pipe.copy(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 _setuplatecommand(self, tmpdir=None): """Setup the latecommand script we run during a preseeded install.""" # TODO: document this better self.logger.info('Copying ssh public key') if tmpdir is None: tmpdir = self.tmpdir shutil.copyfile(config.sshpublickey, os.path.join(tmpdir, 'initrd.d', 'utah-ssh-key')) self.logger.info('Creating latecommand scripts') filename = os.path.join(tmpdir, 'initrd.d', 'utah-latecommand') template.write('utah-latecommand.jinja2', filename, user=config.user, uuid=self.uuid, log_file='/target/var/log/utah-install', media_info=self.image.media_info, install_type=self.image.installtype, md5=self.image.getmd5()) filename = os.path.join(tmpdir, 'initrd.d', 'utah-setup') template.write('utah-setup.jinja2', filename, packages=config.installpackages, log_file='/var/log/utah-install') filename = os.path.join(tmpdir, 'initrd.d', 'utah-autorun.sh') template.write('utah-autorun.sh.jinja2', filename, log_file='/var/log/utah-install') def _setuppreseed(self, tmpdir=None): """Rewrite the preseed to automate installation and access.""" # TODO: document this better # TODO: if the lines we need aren't in the preseed, add them self.logger.info('Setting up preseed') if tmpdir is None: tmpdir = self.tmpdir if self.rewrite in ['all', 'minimal']: with open(self.preseed) as f: preseed = Preseed(f) self._rewrite_latecommand(preseed, tmpdir) if self.rewrite == 'all': if 'pkgsel/include' in preseed: self._rewrite_pkgsel_include(preseed) if 'netcfg/get_hostname' in preseed: self._rewrite_get_hostname(preseed) if 'passwd/username' in preseed: self._rewrite_passwd_username(preseed) if self.image.installtype == 'desktop': self._rewrite_failure_command(preseed) output_preseed_filename = os.path.join(tmpdir, 'initrd.d', 'preseed.cfg') with open(output_preseed_filename, 'w') as f: f.write(preseed.dump()) self.finalpreseed = output_preseed_filename else: self.logger.info('Not altering preseed because rewrite is %s', self.rewrite) if (self.image.installtype == 'desktop' and self.rewrite in ['all', 'minimal', 'casperonly']): self._preseedcasper(tmpdir=tmpdir) def _rewrite_latecommand(self, preseed, tmpdir): """Rewrite latecommand in preseed.""" # Create a late command question if not present already if not 'preseed/late_command' in preseed: preseed.append('d-i preseed/late_command string') question = preseed['preseed/late_command'] log_file = '/var/log/utah-install' if self.image.installtype == 'desktop': self.logger.info('Changing d-i latecommand ' 'to ubiquity success_command ' 'and prepending ubiquity lines') question.owner = 'ubiquity' question.qname = 'ubiquity/success_command' question.prepend('ubiquity ubiquity/summary note') question.prepend('ubiquity ubiquity/reboot boolean true') filename = os.path.join(tmpdir, 'initrd.d', 'latecommand-wrapper') target_log_file = '/target{}'.format(log_file) template.write('latecommand-wrapper.jinja2', filename, latecommand=question.value.text, log_file=target_log_file) question.value = ( 'sh latecommand-wrapper || ' 'logger -s -t utah "Late command failure detected" ' '2>>{}'.format(target_log_file)) def _rewrite_failure_command(self, preseed): """Add log message to failure command. When an ubiquity installation fails, ubiquity/failure_command is called. This is a nice place to write a log message, so that when troubleshotting a provisioing problem it's clearly stated that was the image instalation itself that failed. :param preseed: The preseed contents before is written to initrd.d :type preseed: :class:`Preseed` .. note:: If ubiquity/failure_command question isn't present in the preseed, a new entry will be created for it. """ # Create an ubiquity/failure_command question if not present already command = 'logger -t utah "Installation failure detected"' if not 'ubiquity/failure_command' in preseed: self.logger.debug( 'Adding ubiquity/failure_command question with log messsage') preseed.append('ubiquity ubiquity/failure_command string {}' .format(command)) else: self.logger.debug( 'Adding log message to ubiquity/failure_command question') question = preseed['ubiquity/failure_command'] question.value.prepend('{}; '.format(command)) def _rewrite_pkgsel_include(self, preseed): """Add packages required by utah client to pkgsel/include. Only works for debian-installer """ question = preseed['pkgsel/include'] packages = question.value.text.split() for pkgname in config.installpackages: if pkgname not in packages: self.logger.info('Adding {} to preseeded packages' .format(pkgname)) packages.append(pkgname) question.value = ' '.join(packages) def _rewrite_get_hostname(self, preseed): """Set hostname in the preseed.""" self.logger.info('Rewriting hostname to %s', self.name) question = preseed['netcfg/get_hostname'] question.value = self.name def _rewrite_passwd_username(self, preseed): """Set password in the preseed.""" self.logger.info('Rewriting username to %s', config.user) question = preseed['passwd/username'] question.value = config.user def _preseedcasper(self, tmpdir=None): """Insert a preseed file into casper.""" self.logger.info('Inserting preseed into casper') if tmpdir is None: tmpdir = self.tmpdir casper_dir = os.path.join(tmpdir, 'initrd.d', 'scripts', 'casper-bottom') filename = os.path.join(casper_dir, 'utah') template.write('casper-preseed-script.jinja2', filename) os.chmod(filename, 0755) orderfilename = os.path.join(casper_dir, 'ORDER') exitstatus = ProcessRunner( ['sed', '-i', '1i/scripts/casper-bottom/' 'utah\\n' '[ -e /conf/param.conf ] && . /conf/param.conf', orderfilename]).returncode if exitstatus != 0: raise UTAHProvisioningException('Failed to setup casper script') casper_file = os.path.join(tmpdir, 'initrd.d', 'etc', 'casper.conf') tmpfilename = '{}.tmp'.format(casper_file) with open(casper_file, 'r') as i: with open(tmpfilename, 'w') as o: for line in i: if 'export HOST' in line: o.write('export HOST="{}"\n'.format(self.name)) elif 'export FLAVOUR' in line: o.write('export FLAVOUR="Ubuntu"\n') else: o.write(line) o.flush() os.rename(tmpfilename, casper_file) def _setuplogging(self, tmpdir=None): """Route the installer syslog. For virtual machines, this goes to a serial console that libvirt redirects to a local file we can read. """ if tmpdir is None: tmpdir = self.tmpdir inittab = os.path.join(tmpdir, 'initrd.d', 'etc', 'inittab') if os.path.isfile(inittab): self.logger.info('Updating inittab') with open(inittab, 'a') as myfile: myfile.write("\n" "# logging to serial\n" "ttyS0::respawn:/usr/bin/tail -n 1200 " "-f /var/log/syslog \n") self.logger.info('Creating rsyslog config file') conffilename = os.path.join(tmpdir, 'initrd.d', '50-utahdefault.conf') with open(conffilename, 'w') as f: if self.rsyslog.port: ip = self._ipaddr(config.bridge) dest = '@{}:{}'.format(ip, self.rsyslog.port) else: self.logger.debug('setting up logging to go to serial console') dest = '|/dev/ttyS0' f.write(template.as_buff('50-utahdefault.conf.jinja2', dest=dest)) def _repackinitrd(self, tmpdir=None): """Pack an initrd from our directory. :returns: The path to the initrd file we packed. :rtype: str """ self.logger.info('Repacking initrd') if tmpdir is None: tmpdir = self.tmpdir pipe = pipes.Template() pipe.prepend('find .', '.-') pipe.append('cpio --quiet -o -H newc', '--') # Desktop image loads initrd.gz, # but for physical machines we should stick with lz if self.image.installtype == 'desktop': self.logger.debug('Using lzma because installtype is desktop') pipe.append('lzma -9fc ', '--') initrd = os.path.join(tmpdir, 'initrd.lz') else: self.logger.debug('Using gzip because installtype is not desktop') pipe.append('gzip -9fc ', '--') initrd = os.path.join(tmpdir, 'initrd.gz') if pipe.copy('/dev/null', initrd) != 0: raise UTAHProvisioningException('Failed to repack initrd') return initrd def _cmdlinesetup(self): """Setup the command line for an unattended install. If any options known to be needed for an automated install are not present, add them. :param boot: Manually specified kernel command line. :type boot: str """ # TODO: update libvirtvm to work like the hardware provisioners # or vice versa self.cmdline = self.boot or '' # TODO: Refactor this into lists like BambooFeederMachine if self.rewrite == 'all': self.logger.info('Adding needed command line options') options = [] parameters = [ ('netcfg/get_hostname', self.name), ('log_host', self._ipaddr(config.bridge)), ('log_port', str(self.rsyslog.port)), ] if self.image.installtype == 'desktop': options.extend([ 'automatic-ubiquity', 'noprompt', ]) parameters.extend([ ('boot', 'casper'), ('keyboard-configuration/layoutcode', 'us'), ]) else: parameters.extend([ ('DEBCONF_DEBUG', 'developer'), ('debconf/priority', 'critical'), ]) for option in tuple(options): if option not in self.cmdline: self.logger.info('Adding boot option: %s', option) self.cmdline = '{} {}'.format(self.cmdline, option) for parameter in tuple(parameters): if parameter[0] not in self.cmdline: self.logger.info('Adding boot option: %s', '='.join(parameter)) self.cmdline = ('{} {}' .format(self.cmdline, '='.join(parameter))) self.cmdline = self.cmdline.strip() else: self.logger.info('Not altering command line since rewrite is %s', self.rewrite) self.logger.info('Boot command line is: {}'.format(self.cmdline)) @staticmethod def _ipaddr(ifname): """Return the first IP address found for the given interface name. :param ifname: Name of the network interface :type ifname: str """ iface = netifaces.ifaddresses(ifname) return iface[netifaces.AF_INET][0]['addr']
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.