Source code for utah.provisioning.baremetal.cobbler

# 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 bare metal provisioning through cobbler."""


import os
import pipes
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 CobblerMachine(CustomInstallMixin, SSHMixin, PowerMixin, Machine): """Provision and manage a machine via cobbler.""" # 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, *args, **kw): # TODO: support for reusing existing machines if name is None: raise UTAHBMProvisioningException( 'Machine name reqired for cobbler machine') if machineinfo is None: raise UTAHBMProvisioningException( 'No cobbler arguments given for machine creation') else: self.machineinfo = machineinfo super(CobblerMachine, 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 cobbler installation') self._cmdlinesetup() self._depcheck() self.isodir = None # TODO: Rework cinitrd to be less of a confusing collection of kludges self.cinitrd = None if self.image.installtype in ['alternate', 'server']: self.cinitrd = os.path.join('install', 'netboot', 'ubuntu-installer', self.image.arch, 'initrd.gz') if self.image.arch == 'amd64': self.carch = 'x86_64' else: self.carch = self.image.arch self.cname = '-'.join([self.name, self.carch]) self.logger.debug('Cobbler arch is %s', self.carch) self.logger.debug('Cobbler machine init finished') def _load(self): """Verify the machine is in cobbler.""" machines = self._cobble(['system', 'find'])['output'].splitlines() if self.name not in machines: raise UTAHBMProvisioningException( 'No machine named {} exists in cobbler'.format(self.name)) def _create(self, provision_data): """Install the OS on the machine.""" self.tmpdir = tempfile.mkdtemp(prefix='/tmp/{}_'.format(self.name)) self.cleanfile(self.tmpdir) os.chmod(self.tmpdir, 0775) if self.image.installtype in ['alternate', 'desktop', 'server']: self.logger.info('Mounting image') self.isodir = os.path.join(self.tmpdir, 'iso.d') self.cleanfunction(self._removenfs) os.makedirs(self.isodir, mode=0775) self.logger.debug('Mounting ISO') result = ProcessRunner(['sudo', 'mount', '-o', 'loop', self.image.image, self.isodir]) returncode = result.returncode if returncode != 0: raise UTAHBMProvisioningException( 'Mount failed with return code: {} Output: {}' .format(returncode, result['output'])) kernel = self._preparekernel() if self.cinitrd is None: cinitrd = None else: cinitrd = os.path.join(self.isodir, self.cinitrd) initrd = self._prepareinitrd(initrd=cinitrd) self._unpackinitrd(initrd=initrd) if provision_data: initrd_dir = os.path.join(self.tmpdir, 'initrd.d') provision_data.update_initrd(initrd_dir) self._setuplatecommand() # TODO: see if this is needed for all quantal desktops if self.image.installtype == 'desktop': self.logger.info('Configuring latecommand for desktop') myfile = open(os.path.join(self.tmpdir, 'initrd.d', 'utah-latecommand'), 'a') myfile.write(""" chroot /target sh -c 'sed "/eth[0-9]/d" -i /etc/network/interfaces' """) myfile.close() self._setuppreseed() if self.rewrite == 'all': self._setuplogging(tmpdir=self.tmpdir) else: self.logger.debug('Skipping logging setup because rewrite is %s', self.rewrite) initrd = self._repackinitrd() self.logger.info('Setting up system with cobbler') self._cleanupcobbler() preseed = os.path.join(self.tmpdir, 'initrd.d', 'preseed.cfg') if self.image.installtype in ['alternate', 'server']: self.logger.info('Importing image') self._cobble(['import', '--name={}'.format(self.cname), '--path={}'.format(self.isodir), '--arch={}'.format(self.carch)]) self._cobble(['distro', 'edit', '--name={}'.format(self.cname), '--kernel={}'.format(kernel), '--initrd={}'.format(initrd)]) elif self.image.installtype in ['mini', 'desktop']: self.logger.info('Creating distro') self._cobble(['distro', 'add', '--name={}'.format(self.cname), '--kernel={}'.format(kernel), '--initrd={}'.format(initrd)]) self.logger.info('Creating profile') self._cobble(['profile', 'add', '--name={}'.format(self.cname), '--distro={}'.format(self.cname)]) if self.image.installtype == 'desktop': self.logger.info('Setting up NFS for desktop install') self.logger.debug('Adding export to NFS config file') pipe = pipes.Template() pipe.append('sudo tee -a {} >/dev/null' .format(str(config.nfsconfigfile)), '-.') try: pipe.open('/dev/null', 'w').write( '{} {}\n'.format(self.isodir, config.nfsoptions)) except IOError as err: raise UTAHBMProvisioningException( 'Failed to setup NFS: {}'.format(err)) self.logger.debug('Reloading NFS config') ProcessRunner(config.nfscommand + ['reload']) self.logger.debug('Adding NFS boot options') ip = self._ipaddr(config.nfsiface) self.cmdline += (' root=/dev/nfs netboot=nfs nfsroot={}:{}' .format(ip, self.isodir)) self.cmdline = self.cmdline.strip() self.cleanfunction(self._cleanupcobbler) self.logger.info('Adding kernel boot options to distro') self._cobble(['distro', 'edit', '--name={}'.format(self.cname), '--kopts={}'.format(self.cmdline)]) self.logger.info('Adding kickstart to profile') self._cobble(['profile', 'edit', '--name={}'.format(self.cname), '--kickstart={}'.format(preseed)]) self.logger.info('Adding system') self._cobble(['system', 'add', '--name={}'.format(self.name), '--profile={}'.format(self.cname), '--netboot-enabled=Y'] + ['--{}={}'.format(arg, self.machineinfo[arg]) for arg in self.machineinfo]) self.restart() self.rsyslog.wait_for_install(self._disable_netboot) # TODO: wait_for_booted isn't working on d-i installs and # autopilot jobs like: # http://10.97.0.1:8080/view/autopilot/job/ # ps-unity-autopilot-release-testing/140/label=autopilot-nvidia/ # For now, it seems to be a problem on all installs, so we're going to # handle it in rsyslog.py self.rsyslog.wait_for_booted(self.uuid) retry(self.sshcheck, logmethod=self.logger.info, retry_timeout=config.checktimeout) if self.image.installtype == 'desktop': self._removenfs() self.active = True self.logger.info('System installed') def _disable_netboot(self, match): self.logger.info('install seems to have booted, disabling netboot') self._cobble(['system', 'edit', '--name={}'.format(self.name), '--netboot-enabled=N']) def _cobble(self, cmd, failok=False): """Run a command through cobbler. This allows us to make any needed interface changes in one place. :param failok: Don't raise an exception if the command fails :type failok: bool :returns: ProcessRunner with attrbiutes for output and return code :rtype: obj """ # TODO: If we keep using this, give it some syntactic sugar # All those string formats for arguments are unfortunate try: tail_process = subprocess.Popen( ['tail', '-n0', '-f', '/var/log/cobbler/cobbler.log'], stdout=subprocess.PIPE) command_results = ProcessRunner(['sudo', 'cobbler'] + cmd) retcode = command_results.returncode if retcode != 0: error_msg = 'Cobbler command failed: {}'.format(' '.join(cmd)) if failok: self.logger.debug(error_msg) return command_results else: self.logger.error(error_msg) raise UTAHBMProvisioningException(error_msg) else: return command_results except (OSError, subprocess.CalledProcessError) as err: self.logger.error('Failed to get cobbler logs: {}'.format(err)) finally: # tail process will wait forever because of the -f/--follow option, # so it has to be terminated in order to read from stdout. tail_process.terminate() self.logger.debug('Cobbler log follows:\n' '{}'.format(tail_process.stdout.read())) def _start(self): """Start the machine. Try using cobbler. If that fails, use the powercommand generated by PowerMachine. """ self.logger.debug('Starting system') retcode = self._cobble(['system', 'poweron', '--name={}'.format(self.name)], failok=True).returncode if retcode != 0: self.logger.info('Cobbler power command failed; falling back to ' 'manual command') # TODO: check return code here before setting active ProcessRunner(self.powercommand() + ['on']) self.active = True
[docs] def stop(self): """Stop the machine. Try using cobbler. If that fails, use the powercommand generated by PowerMachine. """ self.logger.debug('Stopping system') retcode = self._cobble(['system', 'poweroff', '--name={}'.format(self.name)], failok=True).returncode if retcode != 0: self.logger.info('Cobbler power command failed; falling back to ' 'manual command') # TODO: check return code here before setting active ProcessRunner(self.powercommand() + ['off']) self.active = False
def _removenfs(self): """Remove our NFS configuration and reload NFS.""" if self.isodir is not None: self.logger.info('Removing NFS share') ProcessRunner(['sudo', 'sed', '/{}/d'.format(self.isodir.replace('/', '\/')), '-i', config.nfsconfigfile]) self.logger.debug('Reloading NFS config') ProcessRunner(config.nfscommand + ['reload']) self.logger.info('Unmounting ISO') ProcessRunner(['sudo', 'umount', self.isodir]) self.logger.debug('Removing directory') os.rmdir(self.isodir) self.isodir = None def _depcheck(self): """Check for NFS if installtype requires it.""" super(CobblerMachine, self)._depcheck() if self.image.installtype in ['alternate', 'desktop', 'server']: cmd = config.nfscommand + ['status'] if ProcessRunner(cmd).returncode != 0: raise UTAHBMProvisioningException( 'NFS needed for {} install'.format(self.image.installtype)) if not os.path.isfile(config.nfsconfigfile): raise UTAHBMProvisioningException( 'NFS config file: {nfsconfigfile} not available' .format(nfsconfigfile=config.nfsconfigfile)) def _cleanupcobbler(self): """Remove our system, profile, and distro from Cobbler.""" self.logger.debug('Removing old system') self._cobble(['system', 'remove', '--name={}'.format(self.name)], failok=True) self.logger.info('Removing old profile') self._cobble(['profile', 'remove', '--name={}'.format(self.cname)], failok=True) self.logger.info('Removing old distro') self._cobble(['distro', 'remove', '--name={}'.format(self.cname)], failok=True)
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.