[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