#!/usr/bin/python # Copyright (C) 2013-2015 Codethink Limited # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY 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 . '''A Morph deployment write extension for deploying to an nfsboot server *** DO NOT USE *** - This was written before 'proper' deployment mechanisms were in place It is unlikely to work at all and will not work correctly Use the pxeboot write extension instead *** An nfsboot server is defined as a baserock system that has tftp and nfs servers running, the tftp server is exporting the contents of /srv/nfsboot/tftp/ and the user has sufficient permissions to create nfs roots in /srv/nfsboot/nfs/ ''' import glob import os import subprocess import writeexts class NFSBootWriteExtension(writeexts.WriteExtension): '''Create an NFS root and kernel on TFTP during Morph's deployment. The location command line argument is the hostname of the nfsboot server. The user is expected to provide the location argument using the following syntax: HOST where: * HOST is the host of the nfsboot server The extension will connect to root@HOST via ssh to copy the kernel and rootfs, and configure the nfs server. It requires root because it uses systemd, and reads/writes to /etc. ''' _nfsboot_root = '/srv/nfsboot' def process_args(self, args): if len(args) != 2: raise writeexts.ExtensionError( 'Wrong number of command line args') temp_root, location = args version_label = os.getenv('VERSION_LABEL', 'factory') hostname = os.environ['HOSTNAME'] versioned_root = os.path.join(self._nfsboot_root, hostname, 'systems', version_label) self.copy_rootfs(temp_root, location, versioned_root, hostname) self.copy_kernel(temp_root, location, versioned_root, version_label, hostname) self.configure_nfs(location, hostname) def create_local_state(self, location, hostname): statedir = os.path.join(self._nfsboot_root, hostname, 'state') subdirs = [os.path.join(statedir, 'home'), os.path.join(statedir, 'opt'), os.path.join(statedir, 'srv')] writeexts.ssh_runcmd('root@%s' % location, ['mkdir', '-p'] + subdirs) def copy_kernel(self, temp_root, location, versioned_root, version, hostname): bootdir = os.path.join(temp_root, 'boot') image_names = ['vmlinuz', 'zImage', 'uImage'] for name in image_names: try_path = os.path.join(bootdir, name) if os.path.exists(try_path): kernel_src = try_path break else: raise writeexts.ExtensionError( 'Could not find a kernel in the system: none of ' '%s found' % ', '.join(image_names)) kernel_dest = os.path.join(versioned_root, 'orig', 'kernel') rsync_dest = 'root@%s:%s' % (location, kernel_dest) self.status(msg='Copying kernel') subprocess.check_call( ['rsync', '-s', kernel_src, rsync_dest]) # Link the kernel to the right place self.status(msg='Creating links to kernel in tftp directory') tftp_dir = os.path.join(self._nfsboot_root , 'tftp') versioned_kernel_name = "%s-%s" % (hostname, version) kernel_name = hostname try: writeexts.ssh_runcmd('root@%s' % location, ['ln', '-f', kernel_dest, os.path.join(tftp_dir, versioned_kernel_name)]) writeexts.ssh_runcmd('root@%s' % location, ['ln', '-sf', versioned_kernel_name, os.path.join(tftp_dir, kernel_name)]) except writeexts.ExtensionError: raise writeexts.ExtensionError('Could not create symlinks to the ' 'kernel at %s in %s on %s' % (kernel_dest, tftp_dir, location)) def copy_rootfs(self, temp_root, location, versioned_root, hostname): rootfs_src = temp_root + '/' orig_path = os.path.join(versioned_root, 'orig') run_path = os.path.join(versioned_root, 'run') self.status(msg='Creating destination directories') try: writeexts.ssh_runcmd('root@%s' % location, ['mkdir', '-p', orig_path, run_path]) except writeexts.ExtensionError: raise writexts.ExtensionError( 'Could not create dirs %s and %s on %s' % (orig_path, run_path, location)) self.status(msg='Creating \'orig\' rootfs') subprocess.check_call( ['rsync', '-asXSPH', '--delete', rootfs_src, 'root@%s:%s' % (location, orig_path)]) self.status(msg='Creating \'run\' rootfs') try: writeexts.ssh_runcmd('root@%s' % location, ['rm', '-rf', run_path]) writeexts.ssh_runcmd('root@%s' % location, ['cp', '-al', orig_path, run_path]) writeexts.ssh_runcmd('root@%s' % location, ['rm', '-rf', os.path.join(run_path, 'etc')]) writeexts.ssh_runcmd('root@%s' % location, ['cp', '-a', os.path.join(orig_path, 'etc'), os.path.join(run_path, 'etc')]) except writeexts.ExtensionError: raise writeexts.ExtensionError('Could not create \'run\' rootfs' ' from \'orig\'') self.status(msg='Linking \'default\' to latest system') try: writeexts.ssh_runcmd('root@%s' % location, ['ln', '-sfn', versioned_root, os.path.join(self._nfsboot_root, hostname, 'systems', 'default')]) except writeexts.ExtensionError: raise writeexts.ExtensionError("Could not link 'default' to %s" % versioned_root) def configure_nfs(self, location, hostname): exported_path = os.path.join(self._nfsboot_root, hostname) exports_path = '/etc/exports' # If that path is not already exported: try: writeexts.ssh_runcmd( 'root@%s' % location, ['grep', '-q', exported_path, exports_path]) except writeexts.ExtensionError: ip_mask = '*' options = 'rw,no_subtree_check,no_root_squash,async' exports_string = '%s %s(%s)\n' % (exported_path, ip_mask, options) exports_append_sh = '''\ set -eu target="$1" temp=$(mktemp) cat "$target" > "$temp" cat >> "$temp" mv "$temp" "$target" ''' writeexts.ssh_runcmd( 'root@%s' % location, ['sh', '-c', exports_append_sh, '--', exports_path], feed_stdin=exports_string) writeexts.ssh_runcmd( 'root@%s' % location, ['systemctl', 'restart', 'nfs-server.service']) NFSBootWriteExtension().run()