[pkg-eucalyptus-commits] [SCM] managing cloud instances for Eucalyptus branch, master, updated. 3.0.0-alpha3-257-g1da8e3a

Matt Spaulding mspaulding06 at gmail.com
Sun Jun 16 02:31:23 UTC 2013


The following commit has been merged in the master branch:
commit c1cf8aa8034c3f88d61607d888185be15f08fa62
Author: Matt Spaulding <mspaulding06 at gmail.com>
Date:   Sun May 12 23:09:43 2013 -0700

    euca-bundle-vol is working
    
    * Cleaned up comments
    * Added debug logging
    * Fix a lot of stuff

diff --git a/bin/euca-bundle-vol b/bin/euca-bundle-vol
index 08a7fa4..4e9e3c2 100755
--- a/bin/euca-bundle-vol
+++ b/bin/euca-bundle-vol
@@ -37,6 +37,4 @@
 import euca2ools.commands.bundle.bundlevol
 
 if __name__ == '__main__':
-    cmd = euca2ools.commands.bundle.bundlevol.BundleVol()
-    cmd.main_cli()
-
+    euca2ools.commands.bundle.bundlevol.BundleVol.run()
diff --git a/euca2ools/commands/bundle/__init__.py b/euca2ools/commands/bundle/__init__.py
index a48428f..39fa574 100644
--- a/euca2ools/commands/bundle/__init__.py
+++ b/euca2ools/commands/bundle/__init__.py
@@ -28,14 +28,20 @@
 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
 
+import argparse
 import os.path
-from euca2ools.commands.argtypes import manifest_block_device_mappings
+from euca2ools.commands import Euca2ools
+from euca2ools.commands.argtypes import (delimited_list,
+                                         manifest_block_device_mappings)
+from requestbuilder import Arg
+from requestbuilder.command import BaseCommand
 from requestbuilder.exceptions import ArgumentError
 from requestbuilder.mixins import FileTransferProgressBarMixin
 from requestbuilder.util import set_userregion
 
 
 class BundleCommand(BaseCommand, FileTransferProgressBarMixin):
+    SUITE = Euca2ools
     ARGS = [Arg('-r', '--arch', choices=('i386', 'x86_64', 'armhf'),
                 required=True,
                 help="the image's processor architecture (required)"),
@@ -55,7 +61,7 @@ class BundleCommand(BaseCommand, FileTransferProgressBarMixin):
                 of the kernel image to associate with the bundle'''),
             Arg('--ramdisk', metavar='IMAGE', help='''[machine image only] ID
                 of the ramdisk image to associate with the bundle'''),
-            Arg('--block-device-mappings',
+            Arg('-B', '--block-device-mappings',
                 metavar='VIRTUAL1=DEVICE1,VIRTUAL2=DEVICE2,...',
                 type=manifest_block_device_mappings,
                 help='''[machine image only] default block device mapping
@@ -64,9 +70,8 @@ class BundleCommand(BaseCommand, FileTransferProgressBarMixin):
                 place the bundle's files (default:  dir named by TMPDIR, TEMP,
                 or TMP environment variables, or otherwise /var/tmp)'''),
             Arg('--productcodes', metavar='CODE1,CODE2,...',
-                type=delimited_list(','),
-                help='comma-separated list of product codes'),
-            Arg('--batch', action='store_true', help=argparse.SUPPRESS)]
+                type=delimited_list(','), default=[],
+                help='comma-separated list of product codes')]
 
     def configure(self):
         BaseCommand.configure(self)
diff --git a/euca2ools/commands/bundle/bundleimage.py b/euca2ools/commands/bundle/bundleimage.py
index ed9621a..1d2e02c 100644
--- a/euca2ools/commands/bundle/bundleimage.py
+++ b/euca2ools/commands/bundle/bundleimage.py
@@ -32,7 +32,8 @@ import argparse
 import binascii
 import euca2ools
 from euca2ools.commands import Euca2ools
-from euca2ools.commands.argtypes import delimited_list, filesize
+from euca2ools.commands.argtypes import (delimited_list, filesize,
+                                         manifest_block_device_mappings)
 from euca2ools.commands.bundle import add_bundle_creds
 from euca2ools.commands.bundle.bundle import Bundle
 from euca2ools.utils import mkdtemp_for_large_files
diff --git a/euca2ools/commands/bundle/bundlevol.py b/euca2ools/commands/bundle/bundlevol.py
index 339e7b8..4a83fa6 100644
--- a/euca2ools/commands/bundle/bundlevol.py
+++ b/euca2ools/commands/bundle/bundlevol.py
@@ -32,71 +32,95 @@
 #         Mitch Garnaat mgarnaat at eucalyptus.com
 
 import argparse
+import copy
 import os
 import sys
-import platform
-import euca2ools.bundler
-import euca2ools.metadata
-from euca2ools.commands.bundle.helpers import get_metadata, get_metadata_dict
+from euca2ools.commands.argtypes import delimited_list, filesize
+from euca2ools.commands.bundle import BundleCommand
+from euca2ools.commands.bundle.bundleimage import BundleImage
+from euca2ools.commands.bundle.helpers import (get_metadata, get_metadata_dict,
+                                               get_metadata_list)
 from euca2ools.commands.bundle.imagecreator import ImageCreator
-from euca2ools.exceptions import AWSError
+from euca2ools.exceptions import AWSError, MetadataReadError
 from requestbuilder import Arg, MutuallyExclusiveArgList
-from requestbuilder.exceptions import ArgumentError
-from requestbuilder.command import BaseCommand
 from requestbuilder.mixins import FileTransferProgressBarMixin
-from requestbuilder.util import set_userregion
 
 
 IMAGE_MAX_SIZE_IN_MB = euca2ools.bundler.IMAGE_MAX_SIZE / 1024 // 1024
+#
+# We pass our args dict along to BundleImage so we need to remove all the
+# args that it doesn't understand.
+#
+BUNDLE_IMAGE_ARG_FILTER = ('generate_fstab', 'fstab', 'bundle_all_dirs',
+                           'filter', 'inherit', 'size', 'volume',
+                           'exclude', 'include', 'ancestor_image_ids')
 
 
 class BundleVol(BundleCommand):
     DESCRIPTION = 'Bundles an image for use with Eucalyptus or Amazon EC2.'
-    ARGS = [Arg('-s', '--size', metavar='SIZE',
+    ARGS = [Arg('-s', '--size', metavar='MB',
                 type=filesize, default=IMAGE_MAX_SIZE_IN_MB,
-                help="""Size of the image in MB (default: {0}; recommended
-                maximum: {0})""").format(IMAGE_MAX_SIZE_IN_MB)),
-            Arg('-p', '--prefix', default='image', help='''the file name prefix
-                to give the bundle's files (defaults to 'image')'''),
+                help='''Size of the image in MB (default: {0}; recommended
+                maximum: {0}).'''.format(IMAGE_MAX_SIZE_IN_MB)),
+            Arg('-p', '--prefix', metavar='PREFIX', default='image',
+                help='''the file name prefix to give the bundle's files
+                (defaults to 'image').'''),
             Arg('-a', '--all', dest="bundle_all_dirs", action='store_true',
-                help="""Bundle all directories (including
-                mounted filesystems."""),
+                help='''Bundle all directories (including mounted
+                filesystems).'''),
             MutuallyExclusiveArgList(
-                Arg('--no-inherit', dest='inherit',
-                    action='store_false', help="""Do not add instance metadata to
-                    the bundled image."""),
-                Arg('--inherit', dest='inherit', action='store_true',
-                    help=argparse.SUPPRESS)),
-            Arg('-e', '--exclude', type=delimited_list(','),
-                help='''Comma-separated list of directories to exclude.'''),
-            Arg('--volume', dest='bundle_volume', default='/',
-                help='Path to mounted volume to bundle.'),
+                Arg('--no-inherit', dest='inherit', default=False,
+                    action='store_false', help='''Do not add instance metadata to
+                    the bundled image (use the --inherit option to explicitly
+                    inherit instance metadata).'''),
+                Arg('--inherit', dest='inherit', default=True,
+                    action='store_true', help=argparse.SUPPRESS)),
+            Arg('-i', '--include', metavar='FILE1,FILE2,...',
+                type=delimited_list(','), help='''Comma-separated list of
+                absolute file paths to include.'''),
+            Arg('-e', '--exclude', metavar='DIR1,DIR2,...',
+                type=delimited_list(','), help='''Comma-separated list of
+                directories to exclude.'''),
+            Arg('--volume', metavar='PATH', default='/', help='''Path to
+                mounted volume to bundle (defaults to '/').'''),
             Arg('--no-filter', dest='filter', default=True,
-                action='store_false', help="""Do not use the default filtered
-                files list."""),
+                action='store_false', help='''Do not use the default filtered
+                files list.'''),
             MutuallyExclusiveArgList(
-                Arg('--fstab', dest='fstab',
-                    help='Path to the fstab to be bundled with image.'),
-                Arg('--generate-fstab', dest='generate_fstab',
-                    action='store_true',
+                Arg('--fstab', metavar='PATH', help='''Path to the fstab to be
+                    bundled with image.'''),
+                Arg('--generate-fstab', default=False, action='store_true',
                     help='Generate fstab to bundle in image.')),
             Arg('--batch',
                 help='Run in batch mode.  For compatibility has no effect')]
 
-    def _check_root(self):
+    def __init__(self, **kwargs):
+        #
+        # Do root check before anything else happens
+        #
         if os.geteuid() != 0:
-            print >> sys.stderr, 'Must be superuser to execute this command.'
-            sys.exit(1)
+            raise Exception("must be root user to run euca-bundle-vol.")
+        BaseCommand.__init__(self, **kwargs)
 
     def _inherit_metadata(self):
+        """Read instance metadata which we will propagate to the BundleImage
+        command. These values are used for generating a manifest once we have
+        a bundled image.
+        """
         try:
-            if not self.args.get('ramdisk_id'):
-                self.args['ramdisk_id'] = get_metadata('ramdisk-id')
-            if not self.args.get('kernel_id'):
-                self.args['kernel_id'] = get_metadata('kernel-id')
-            if not self.args.get('block_dev_mapping'):
-                self.args['block_dev_mapping'] = \
+            if not self.args.get('ramdisk'):
+                self.args['ramdisk'] = get_metadata('ramdisk-id')
+                self.log.debug("inheriting ramdisk: {0}"
+                               .format(self.args.get('ramdisk')))
+            if not self.args.get('kernel'):
+                self.args['kernel'] = get_metadata('kernel-id')
+                self.log.debug("inheriting kernel: {0}"
+                               .format(self.args.get('kernel')))
+            if not self.args.get('block_device_mappings'):
+                self.args['block_device_mappings'] = \
                     get_metadata_dict('block-device-mapping')
+                self.log.debug("inheriting block device mappings: {0}"
+                               .format(self.args.get('block_device_mappings')))
             #
             # Product codes and ancestor AMI ids are special cases since they
             # aren't supported by Eucalyptus yet.
@@ -106,7 +130,9 @@ class BundleVol(BundleCommand):
             except MetadataReadError:
                 print >> sys.stderr, 'Unable to read product codes.'
             try:
-                self.args['ancestor_ami_ids'].extend(
+                if not self.args.get('ancestor_image_ids'):
+                    self.args['ancestor_image_ids'] = []
+                self.args['ancestor_image_ids'].extend(
                     get_metadata_list('ancestor-ami-ids'))
             except MetadataReadError:
                 print >> sys.stderr, 'Unable to read ancestor ids.'
@@ -115,16 +141,34 @@ class BundleVol(BundleCommand):
             print >> sys.stderr, 'Pass the --no-inherit option if you wish to', \
                 'exclude instance metadata.'
             sys.exit(1)
+            
+    def _filter_args_for_bundle_image(self):
+        """Make a complete copy of args to pass along to BundleImage. We first
+        need to remove any arguments that BundleImage would not know about.
+        """
+        args = copy.deepcopy(self.args)
+        for arg in BUNDLE_IMAGE_ARG_FILTER:
+            try:
+                del args[arg]
+            except KeyError:
+                pass
+        return args
 
-    def main(self):
-        self._check_root()
+    def configure(self):
+        BundleCommand.configure(self)
+        self.args['user'] = self.args.get('user').replace('-', '')
 
+    def main(self):
         if self.args.get('inherit') is True:
             self._inherit_metadata()
         
-        # No need to check existence.
-        # Requirement check is in BundleCommand's configure method.
-        self.args['user'] = self.args.get('user').replace('-', '')
+        image_file = ImageCreator(log=self.log, **self.args).run()
+        image_args = self._filter_args_for_bundle_image()
+        image_args.update(image=image_file, image_type='machine')
+        self.log.info("bundling image: {0}".format(image_file))
+        return BundleImage(**image_args).main()
 
-        image_file = ImageCreator(self.args).run()
-        print "Image Created: ", image_file
+    def print_result(self, result):
+        for part_filename in result[0]:
+            print 'Wrote', part_filename
+        print 'Wrote manifest', result[1]
diff --git a/euca2ools/commands/bundle/helpers.py b/euca2ools/commands/bundle/helpers.py
index 3cb370f..4fb7fdd 100644
--- a/euca2ools/commands/bundle/helpers.py
+++ b/euca2ools/commands/bundle/helpers.py
@@ -134,10 +134,3 @@ def get_metadata_dict(*paths):
     items = get_metadata_list(*paths)
     return dict((item, get_metadata(*(list(paths) + [item]))) \
 			for item in items)
-
-def parse_block_device_mapping_arg(arg):
-    """Parses the bundle argument string for block device mappings.
-    Returns a dict of block device mappings.
-    :param arg: The argument string for block device mappings.
-    """
-    return dict(pair.split('=') for pair in mapping.split(','))
diff --git a/euca2ools/commands/bundle/imagecreator.py b/euca2ools/commands/bundle/imagecreator.py
index b42be78..4d605d6 100644
--- a/euca2ools/commands/bundle/imagecreator.py
+++ b/euca2ools/commands/bundle/imagecreator.py
@@ -29,12 +29,16 @@
 # POSSIBILITY OF SUCH DAMAGE.
 #
 
+from euca2ools.exceptions import CopyError
 from euca2ools.utils import execute, check_command, sanitize_path
 from euca2ools.utils import mkdtemp_for_large_files as mkdtemp
+from requestbuilder.exceptions import ArgumentError
 import os
 import sys
 import platform
 import shutil
+import stat
+import time
 
 
 NO_EXCLUDE_ENVAR = 'EUCA_BUNDLE_VOL_EMPTY_EXCLUDES'
@@ -43,18 +47,18 @@ ALLOWED_FS_TYPES = ('ext2', 'ext3', 'xfs', 'jfs', 'reiserfs')
 EXCLUDED_DIRS = ('/dev', '/media', '/mnt', '/proc',
                  '/sys', '/cdrom', '/tmp')
 SYSTEM_DIRS = ('proc', 'tmp', 'dev', 'mnt', 'sys')
-DEVICE_NODES = (('/dev/console', 'c', '5', '1'),
-                ('/dev/full', 'c', '1', '7'),
-                ('/dev/null', 'c', '1', '3'),
-                ('/dev/zero', 'c', '1', '5'),
-                ('/dev/tty', 'c', '5', '0'),
-                ('/dev/tty0', 'c', '4', '0'),
-                ('/dev/tty1', 'c', '4', '1'),
-                ('/dev/tty2', 'c', '4', '2'),
-                ('/dev/tty3', 'c', '4', '3'),
-                ('/dev/tty4', 'c', '4', '4'),
-                ('/dev/tty5', 'c', '4', '5'),
-                ('/dev/xvc0', 'c', '204', '191'))
+DEVICE_NODES = (('dev/console', 'c', '5', '1'),
+                ('dev/full', 'c', '1', '7'),
+                ('dev/null', 'c', '1', '3'),
+                ('dev/zero', 'c', '1', '5'),
+                ('dev/tty', 'c', '5', '0'),
+                ('dev/tty0', 'c', '4', '0'),
+                ('dev/tty1', 'c', '4', '1'),
+                ('dev/tty2', 'c', '4', '2'),
+                ('dev/tty3', 'c', '4', '3'),
+                ('dev/tty4', 'c', '4', '4'),
+                ('dev/tty5', 'c', '4', '5'),
+                ('dev/xvc0', 'c', '204', '191'))
 FSTAB_BODY_TEMPLATE = dict(
     i386="""/dev/sda1\t/\text3\tdefaults 1 1
 /dev/sdb\t/mnt\text3\tdefaults 0 0
@@ -101,21 +105,22 @@ DEFAULT_FS_EXCLUDES = [
 
 
 class VolumeSync(object):
-    def __init__(self, volume, image):
-        self.mount = mkdtemp()
+    def __init__(self, volume, image, log=None):
+        self.log = log
+        self.mpoint = mkdtemp(prefix='vol-')
         self.excludes = []
         self.filter = False
         self.fstab = None
-        self.generate_fstab = False
+        self.generate_fstab_file = False
         self.image = image
         self.includes = []
         self.volume = volume
         self.bundle_all_dirs = False
 
     def run(self):
-        #
-        # This is where ALL the magic happens!
-        #
+        """
+        This is where ALL the magic happens!
+        """
         self._sync_files()
         self._populate_system_dirs()
         self._populate_device_nodes()
@@ -132,7 +137,7 @@ class VolumeSync(object):
         if self.fstab:
             with open(fstab, 'r') as fp:
                 self._install_fstab(fp.read())
-        elif self.generate_fstab:
+        elif self.generate_fstab_file:
             self._install_generated_fstab()
 
     def exclude(self, exclude):
@@ -158,27 +163,28 @@ class VolumeSync(object):
     def install_fstab_from_file(self, fstab):
         if not os.path.exists(fstab):
             raise ArgumentError(
-                'fstab file '{0}' does not exist.'.format(fstab))
+                "fstab file '{0}' does not exist.".format(fstab))
         self.fstab = fstab
 
     def generate_fstab(self):
-        self.generate_fstab = True
+        self.generate_fstab_file = True
 
     def _update_exclusions(self):
-        #
-        # Here we update the following sync exclusions:
-        #
-        # 1. Exclude our disk image we are syncing to.
-        # 2. Exclude our mount point for our image.
-        # 3. Exclude filesystems that are not allowed.
-        # 4. Exclude special system directories.
-        # 5. Exclude file patterns for privacy.
-        # 6. Exclude problematic udev rules.
-        #
+        """
+        Here we update the following sync exclusions:
+
+        1. Exclude our disk image we are syncing to.
+        2. Exclude our mount point for our image.
+        3. Exclude filesystems that are not allowed.
+        4. Exclude special system directories.
+        5. Exclude file patterns for privacy.
+        6. Exclude problematic udev rules.
+        """
+
         if self.image.find(self.volume) == 0:
             self.exclude(os.path.dirname(self.image))
-        if self.mount(self.volume) == 0:
-            self.exclude(self.mount)
+        if self.mpoint.find(self.volume) == 0:
+            self.exclude(self.mpoint)
         if not self.bundle_all_dirs:
             self._add_mtab_exclusions()
         self.excludes.extend(EXCLUDED_DIRS)
@@ -190,6 +196,10 @@ class VolumeSync(object):
                  '/etc/udev/rules.d/z25_persistent-net.rules'])
 
     def _add_mtab_exclusions(self):
+        """Exclude locations from the volume rsync based on whether we are
+        allowed to sync the type of filesystem. If you have chosen to bundle
+        all using '--all' then this will not get called.
+        """
         with open('/etc/mtab', 'r') as mtab:
             for line in mtab.readlines():
                 (mount, type) = line.split()[1:3]
@@ -205,23 +215,29 @@ class VolumeSync(object):
                     self.excludes.append(mount)
 
     def _populate_tmpfs_mounts(self):
+        """Find all tmpfs mounts on our volume and make sure that they are
+        created on the image.
+        """
         with open('/etc/mtab', 'r') as mtab:
             for line in mtab.readlines():
                 (mount, type) = line.split()[1:3]
                 if type == 'tmpfs':
-                    fullpath = os.path.join(self.mount, mount[1:])
+                    fullpath = os.path.join(self.mpoint, mount[1:])
                     if not os.path.exists(fullpath):
                         os.makedirs(fullpath)
 
     def _populate_device_nodes(self):
+        """Populate the /dev directory in our image with common device nodes."""
         template = 'mknod {0} {1} {2} {3}'
         for node in DEVICE_NODES:
-            cmd = template.format(node[0], *node[1:])
-            execute(cmd)
+            cmd = template.format(os.path.join(self.mpoint, node[0]),
+                                  *node[1:])
+            execute(cmd, log=self.log)
 
     def _populate_system_dirs(self):
+        """Populate our image with common system directories."""
         for sysdir in SYSTEM_DIRS:
-            fullpath = os.path.join(self.mount, sysdir)
+            fullpath = os.path.join(self.mpoint, sysdir)
             if not os.path.exists(fullpath):
                 os.makedirs(fullpath)
                 if sysdir == 'tmp':
@@ -231,11 +247,11 @@ class VolumeSync(object):
         self._install_fstab(_generate_fstab_content())
 
     def _install_fstab(self, content):
-        curr_fstab = os.path.join(self.mount, 'etc', 'fstab')
+        curr_fstab = os.path.join(self.mpoint, 'etc', 'fstab')
         if os.path.exists(curr_fstab):
             shutil.copyfile(curr_fstab, curr_fstab + '.old')
             os.remove(curr_fstab)
-        with open(os.path.join(mount, 'etc', 'fstab'), 'wb') as fp:
+        with open(os.path.join(self.mpoint, 'etc', 'fstab'), 'wb') as fp:
             fp.write(content)
 
     def _sync_files(self):
@@ -246,8 +262,9 @@ class VolumeSync(object):
             cmd.extend(['--exclude', exclude])
         for include in self.includes:
             cmd.extend(['--include', include])
-        cmd.extend([os.path.join(self.volume, '*'), self.dest])
-        (out, _, retval) = execute(cmd)
+        cmd.extend([os.path.join(self.volume, '*'), self.mpoint])
+        (out, _, retval) = execute(cmd, shell=True, raise_exception=False,
+                                   log=self.log)
 
         #
         # rsync return code 23: Partial transfer due to error
@@ -256,28 +273,28 @@ class VolumeSync(object):
         if retval in (23, 24):
             print >> sys.stderr, 'Warning: rsync reports files partially copied:'
             print >> sys.stderr, out
-        else:
+        elif retval != 0:
             print >> sys.stderr, 'Error: rsync failed with return code {0}'.format(retval)
             raise CopyError
 
     def _sync_disks(self):
-        execute('sync')
+        execute('sync', log=self.log)
 
     def mount(self):
         self._sync_disks()
-        if not os.path.exists(self.mount):
-            os.makedirs(self.mount)
+        if not os.path.exists(self.mpoint):
+            os.makedirs(self.mpoint)
         check_command('mount')
-        cmd = ['mount', '-o', 'loop', self.image, self.mount]
-        execute(cmd)
+        cmd = ['mount', '-o', 'loop', self.image, self.mpoint]
+        execute(cmd, log=self.log)
 
     def unmount(self):
         self._sync_disks()
-        utils.check_command('umount')
-        cmd = ['umount', '-d', self.dest]
-        execute(cmd)
-        if os.path.exists(self.mount):
-            os.remove(self.mount)
+        check_command('umount')
+        cmd = ['umount', '-d', self.mpoint]
+        execute(cmd, log=self.log)
+        if os.path.exists(self.mpoint):
+            os.rmdir(self.mpoint)
 
     def __enter__(self):
         self.mount()
@@ -288,31 +305,59 @@ class VolumeSync(object):
 
 
 class ImageCreator(object):
-    def __init__(self, **kwargs):
-        self.volume = sanitize_path(kwargs.get('volume', '/'))
+    def __init__(self, log=None, **kwargs):
+        #
+        # Assign settings for image creation
+        #
+        self.log = log
+        self.fs = {}
+        self.volume = kwargs.get('volume')
         self.fstab = kwargs.get('fstab')
         self.generate_fstab = kwargs.get('generate_fstab', False)
         self.excludes = kwargs.get('exclude', [])
         self.includes = kwargs.get('include', [])
         self.bundle_all_dirs = kwargs.get('bundle_all_dirs', False)
-        self.size = kwargs.get('size')
         self.prefix = kwargs.get('prefix')
-        self.image = os.path.join(mkdtemp(), '{0}.img'.format(self.prefix))
-        self.fs = {}
+        self.filter = kwargs.get('filter', True)
+        self.size = kwargs.get('size')
+        self.destination = kwargs.get('destination') or mkdtemp(prefix='image-')
+        self.image = os.path.join(self.destination,
+                                  '{0}.img'.format(self.prefix))
 
-    def run(self):
         #
-        # Prepare a disk image
-        # 
+        # Validate settings
+        #
+        if not self.volume:
+            raise ArgumentError("must supply a source volume.")
+        self.volume = sanitize_path(self.volume)
+        if not self.size:
+            raise ArgumentError("must supply a size for the generated image.")
+        if not self.prefix:
+            raise ArgumentError("must supply a prefix.")
+        if not self.volume:
+            raise ArgumentError("must supply a volume.")
+        if not (os.path.exists(self.destination) or \
+                    os.path.isdir(self.destination)):
+            raise ArgumentError("'{0}' is not a directory or does not exist."
+                                .format(self.destination))
+
+    def run(self):
+        """
+        Prepare a disk image
+        """
+        sys.stderr.write("Creating image...")
         self._create_raw_diskimage()
         self._populate_filesystem_info()
         self._make_filesystem(**self.fs)
+        sys.stderr.write(" done\n")
+        sys.stderr.flush()
         #
         # Inside the VolumeSync context we will mount our image
         # as a loop device. If for any reason a failure occurs
         # the device will automatically be unmounted and cleaned up.
         #
-        with VolumeSync(self.volume, self.image) as volsync:
+        sys.stderr.write("Syncing volume contents...")
+        with VolumeSync(self.volume, self.image, log=self.log) as volsync:
             if self.fstab:
                 volsync.install_fstab_from_file(self.fstab)
             elif self.generate_fstab:
@@ -322,22 +367,25 @@ class ImageCreator(object):
             volsync.exclude(self.excludes)
             volsync.include(self.includes)
             volsync.run()
+        sys.stderr.write(" done\n")
+        sys.stderr.flush()
 
         return self.image
 
     def _create_raw_diskimage(self):
-        template = 'dd if=/dev/zero of={0} count=1 bs=1M seek {1}'
-        cmd = template.format(self.image, self.args.get('size') - 1)
-        execute(cmd)
+        """Create a sparse raw image file."""
+        template = 'dd if=/dev/zero of={0} count=1 bs=1M seek={1}'
+        cmd = template.format(self.image, self.size - 1)
+        execute(cmd, log=self.log)
 
     def _populate_filesystem_info(self):
-        #
-        # Create a temporary device node for the volume we're going
-        # to copy. We'll use it to get information about the filesystem.
-        #
+        """Create a temporary device node for the volume we're going
+        to copy. We'll use it to get information about the source volume's
+        filesystem.
+        """
         st_dev = os.stat(self.volume).st_dev
         devid = os.makedev(os.major(st_dev), os.minor(st_dev))
-        directory = mkdtemp()
+        directory = mkdtemp(prefix='devnode-')
         devnode = os.path.join(directory, 'rootdev')
         os.mknod(devnode, 0400 | stat.S_IFBLK, devid)
         template = 'blkid -s {0} -ovalue {1}'
@@ -345,7 +393,7 @@ class ImageCreator(object):
             for tag in BLKID_TAGS:
                 cmd = template.format(tag, devnode)
                 try:
-                    (out, _, _) = execute(cmd)
+                    (out, _, _) = execute(cmd, log=self.log)
                     self.fs[tag.lower()] = out.rstrip()
                 except CommandFailed:
                     pass
@@ -354,6 +402,11 @@ class ImageCreator(object):
             os.rmdir(directory)
    
     def _make_filesystem(self, type='ext3', uuid=None, label=None):
+        """Format our raw image.
+        :param type: (optional) Filesystem type, one of ext3, ext4, xfs, btrfs.
+        :param uuid: (optional) UUID of the filesystem.
+        :param label: (optional) Label of the filesystem.
+        """
         mkfs_cmd = 'mkfs.{0}'.format(type)
         tunefs = None
 
@@ -373,18 +426,25 @@ class ImageCreator(object):
 
         if label:
             mkfs.extend(['-L', label])
-        utils.check_command(mkfs)
-        execute(mkfs)
+        check_command(mkfs)
+        execute(mkfs, log=self.log)
         if tunefs:
-            utils.check_command(tunefs)
-            execute(tunefs)
+            check_command(tunefs)
+            execute(tunefs, log=self.log)
 
 
 def _generate_fstab_content(arch=platform.machine()):
+    """Generate an fstab file based on the system's architecture.
+    Returns the fstab file contents as a string.
+    :param arch: (optional) The architecture to use when creating the fstab
+    file. It will default to the architecture of the currently running system.
+    If the system is 'i386' then the legacy fstab configuration will be used,
+    and if the system is 'x86_64' then the new fstab configuration will be used.
+    """
     if arch in FSTAB_BODY_TEMPLATE:
-        return "\n".join(FSTAB_HEADER_TEMPLATE.format(
+        return "\n".join([FSTAB_HEADER_TEMPLATE.format(
                 time.strftime(FSTAB_TIME_FORMAT)),
-                         FSTAB_BODY_TEMPLATE.get(arch))
+                         FSTAB_BODY_TEMPLATE.get(arch)])
     else:
         raise UnsupportedException(
             "platform architecture {0} not supported".format(arch))
diff --git a/euca2ools/exceptions.py b/euca2ools/exceptions.py
index 499c4ed..be94282 100644
--- a/euca2ools/exceptions.py
+++ b/euca2ools/exceptions.py
@@ -41,6 +41,10 @@ class EucaError(Exception):
     def message(self):
         return self._message
 
+    @property
+    def args(self):
+        return (self._message,)
+
 class ValidationError(Exception):
 
     @property
@@ -124,12 +128,8 @@ class CommandFailed(EucaError):
         self.err = err
         if cmd:
             self._message += ': {0}'.format(cmd)
-
-    @property
-    def message(self):
         if err:
-            return "\n".join(self._message, self.err)
-        return self._message
+            self._message += '\n{0}'.format(err)
 
 class ConnectionFailed(EucaError):
 
diff --git a/euca2ools/utils.py b/euca2ools/utils.py
index 099f431..7b03ff0 100644
--- a/euca2ools/utils.py
+++ b/euca2ools/utils.py
@@ -39,7 +39,8 @@ import tempfile
 from euca2ools import exceptions, __version__
 
 
-def execute(command, raise_exception=True, exception=None, success=0):
+def execute(command, raise_exception=True, exception=None, success=0,
+            shell=False, log=None):
     """Execute a process with optional arguments.
     Returns a tuple containing stdout, stderr and return code.
     :param command: A string or list of arguments to execute.
@@ -48,17 +49,35 @@ def execute(command, raise_exception=True, exception=None, success=0):
     :param exception: (optional) Exception object to use when raising an
     exception. If this is not set a CommandFailed exception will be used.
     :param success: (optional) Set the returncode that signifies success.
+    :param shell: (optional) Tell Popen to use a shell to execute the command.
+    :param log: (optional) Logger to use when logging debug information.
+    This is generally a security risk, so only do it when necessary with
+    trusted input.
     """
-    if isinstance(command, basestring):
-        command = command.split()
-    proc = Popen(command, stdout=PIPE, stderr=PIPE)
+    #
+    # If we're using a shell we have to provide Popen a string to execute,
+    # otherwise Popen wants a list.
+    #
+    if shell is True:
+        if isinstance(command, list):
+            command = " ".join(command)
+    else:
+        if isinstance(command, basestring):
+            command = command.split()
+    if log:
+        log.debug("executing command: {0}".format(command))
+    proc = Popen(command, stdout=PIPE, stderr=PIPE, shell=shell)
     (out, err) = proc.communicate()
+    if log:
+        log.debug("command retval: {0}".format(proc.returncode))
     if proc.returncode != success:
         if raise_exception:
             if exception:
                 raise exception
             else:
-                raise exceptions.CommandFailed(" ".join(command), err)
+                if isinstance(command, list):
+                    command = " ".join(command)
+                raise exceptions.CommandFailed(command, err)
     return (out, err, proc.returncode)
 
 
@@ -72,6 +91,12 @@ def check_command(command, **kwargs):
         if isinstance(command, basestring):
             command = command.split()
         if command:
+            #
+            # We don't want to raise a CommandFailed exception here.
+            # We only care if we get an OSError exception, which means the
+            # executable doesn't exist.
+            #
+            kwargs.update(raise_exception=False)
             execute(command[0], **kwargs)
         else:
             raise ArgumentError("No executable supplied to check command.")

-- 
managing cloud instances for Eucalyptus



More information about the pkg-eucalyptus-commits mailing list