summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/compute/disk.py108
-rw-r--r--nova/virt/libvirt.xml.template6
-rw-r--r--nova/virt/libvirt_conn.py110
3 files changed, 139 insertions, 85 deletions
diff --git a/nova/compute/disk.py b/nova/compute/disk.py
index 814a258cd..766f27d35 100644
--- a/nova/compute/disk.py
+++ b/nova/compute/disk.py
@@ -28,6 +28,7 @@ import tempfile
from nova import exception
from nova import flags
+from nova import utils
FLAGS = flags.FLAGS
@@ -37,8 +38,7 @@ flags.DEFINE_integer('block_size', 1024 * 1024 * 256,
'block_size to use for dd')
-def partition(infile, outfile, local_bytes=0, resize=True,
- local_type='ext2', execute=None):
+def partition(infile, outfile, local_bytes=0, resize=True, local_type='ext2'):
"""
Turns a partition (infile) into a bootable drive image (outfile).
@@ -61,10 +61,10 @@ def partition(infile, outfile, local_bytes=0, resize=True,
file_size = os.path.getsize(infile)
if resize and file_size < FLAGS.minimum_root_size:
last_sector = FLAGS.minimum_root_size / sector_size - 1
- execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
+ utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
% (infile, last_sector, sector_size))
- execute('e2fsck -fp %s' % infile, check_exit_code=False)
- execute('resize2fs %s' % infile)
+ utils.execute('e2fsck -fp %s' % infile, check_exit_code=False)
+ utils.execute('resize2fs %s' % infile)
file_size = FLAGS.minimum_root_size
elif file_size % sector_size != 0:
logging.warn(_("Input partition size not evenly divisible by"
@@ -83,37 +83,37 @@ def partition(infile, outfile, local_bytes=0, resize=True,
last_sector = local_last # e
# create an empty file
- execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
+ utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
% (outfile, mbr_last, sector_size))
# make mbr partition
- execute('parted --script %s mklabel msdos' % outfile)
+ utils.execute('parted --script %s mklabel msdos' % outfile)
# append primary file
- execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append'
+ utils.execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append'
% (infile, outfile, FLAGS.block_size))
# make primary partition
- execute('parted --script %s mkpart primary %ds %ds'
+ utils.execute('parted --script %s mkpart primary %ds %ds'
% (outfile, primary_first, primary_last))
if local_bytes > 0:
# make the file bigger
- execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
+ utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
% (outfile, last_sector, sector_size))
# make and format local partition
- execute('parted --script %s mkpartfs primary %s %ds %ds'
+ utils.execute('parted --script %s mkpartfs primary %s %ds %ds'
% (outfile, local_type, local_first, local_last))
-def extend(image, size, execute):
+def extend(image, size):
file_size = os.path.getsize(image)
if file_size >= size:
return
- return execute('truncate -s size %s' % (image,))
+ return utils.execute('truncate -s size %s' % (image,))
-def inject_data(image, key=None, net=None, partition=None, execute=None):
+def inject_data(image, key=None, net=None, partition=None):
"""Injects a ssh key and optionally net data into a disk image.
it will mount the image as a fully partitioned disk and attempt to inject
@@ -122,15 +122,11 @@ def inject_data(image, key=None, net=None, partition=None, execute=None):
If partition is not specified it mounts the image as a single partition.
"""
- out, err = execute('sudo losetup --find --show %s' % image)
- if err:
- raise exception.Error(_('Could not attach image to loopback: %s')
- % err)
- device = out.strip()
+ device = _link_device(image)
try:
if not partition is None:
# create partition
- out, err = execute('sudo kpartx -a %s' % device)
+ out, err = utils.execute('sudo kpartx -a %s' % device)
if err:
raise exception.Error(_('Failed to load partition: %s') % err)
mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1],
@@ -146,12 +142,12 @@ def inject_data(image, key=None, net=None, partition=None, execute=None):
mapped_device)
# Configure ext2fs so that it doesn't auto-check every N boots
- out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
+ out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
tmpdir = tempfile.mkdtemp()
try:
# mount loopback to dir
- out, err = execute(
+ out, err = utils.execute(
'sudo mount %s %s' % (mapped_device, tmpdir))
if err:
raise exception.Error(_('Failed to mount filesystem: %s')
@@ -160,45 +156,79 @@ def inject_data(image, key=None, net=None, partition=None, execute=None):
try:
if key:
# inject key file
- _inject_key_into_fs(key, tmpdir, execute=execute)
+ _inject_key_into_fs(key, tmpdir)
if net:
- _inject_net_into_fs(net, tmpdir, execute=execute)
+ _inject_net_into_fs(net, tmpdir)
finally:
# unmount device
- execute('sudo umount %s' % mapped_device)
+ utils.execute('sudo umount %s' % mapped_device)
finally:
# remove temporary directory
- execute('rmdir %s' % tmpdir)
+ utils.execute('rmdir %s' % tmpdir)
if not partition is None:
# remove partitions
- execute('sudo kpartx -d %s' % device)
+ utils.execute('sudo kpartx -d %s' % device)
finally:
- # remove loopback
- execute('sudo losetup --detach %s' % device)
+ _unlink_device(image, device)
-def _inject_key_into_fs(key, fs, execute=None):
+def _link_device(image):
+ if FLAGS.use_cow_images:
+ device = _allocate_device()
+ utils.execute('sudo qemu-nbd --connect=%s %s' % (device, image))
+ else:
+ out, err = utils.execute('sudo losetup --find --show %s' % image)
+ if err:
+ raise exception.Error(_('Could not attach image to loopback: %s')
+ % err)
+ return out.strip()
+
+
+def _unlink_device(image, device):
+ if FLAGS.use_cow_images:
+ utils.execute('sudo qemu-nbd --disconnect %s' % image)
+ _free_device(device)
+ else:
+ utils.execute('sudo losetup --detach %s' % device)
+
+
+_DEVICES = ['/dev/nbd%s' % i for i in xrange(16)]
+
+def _allocate_device():
+ # NOTE(vish): This assumes no other processes are using nbd devices.
+ # It will race cause a race condition if multiple
+ # workers are running on a given machine.
+ if not _DEVICES:
+ raise exception.Error(_('No free nbd devices'))
+ return _DEVICES.pop()
+
+
+def _free_device(device):
+ _DEVICES.append(device)
+
+
+def _inject_key_into_fs(key, fs):
"""Add the given public ssh key to root's authorized_keys.
key is an ssh key string.
fs is the path to the base of the filesystem into which to inject the key.
"""
sshdir = os.path.join(fs, 'root', '.ssh')
- execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
- execute('sudo chown root %s' % sshdir)
- execute('sudo chmod 700 %s' % sshdir)
+ utils.execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
+ utils.execute('sudo chown root %s' % sshdir)
+ utils.execute('sudo chmod 700 %s' % sshdir)
keyfile = os.path.join(sshdir, 'authorized_keys')
- execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
+ utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
-def _inject_net_into_fs(net, fs, execute=None):
+def _inject_net_into_fs(net, fs):
"""Inject /etc/network/interfaces into the filesystem rooted at fs.
net is the contents of /etc/network/interfaces.
"""
netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
- execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter
- execute('sudo chown root:root %s' % netdir)
- execute('sudo chmod 755 %s' % netdir)
+ utils.execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter
+ utils.execute('sudo chown root:root %s' % netdir)
+ utils.execute('sudo chmod 755 %s' % netdir)
netfile = os.path.join(netdir, 'interfaces')
- execute('sudo tee %s' % netfile, net)
+ utils.execute('sudo tee %s' % netfile, net)
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template
index 3fb2243da..d6d9d3de6 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -58,6 +58,12 @@
<source file='${basepath}/disk'/>
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
</disk>
+ #if $getVar('local', False)
+ <disk type='file'>
+ <source file='${basepath}/local'/>
+ <target dev='${disk_prefix}b' bus='${disk_bus}'/>
+ </disk>
+ #end if
#end if
<interface type='bridge'>
<source bridge='${bridge_name}'/>
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 00edfbdc8..883913926 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -85,6 +85,9 @@ flags.DEFINE_string('libvirt_uri',
flags.DEFINE_bool('allow_project_net_traffic',
True,
'Whether to allow in project network traffic')
+flags.DEFINE_bool('use_cow_images',
+ True,
+ 'Whether to use cow images')
def get_connection(read_only):
@@ -418,19 +421,50 @@ class LibvirtConnection(object):
return self._dump_file(fpath)
+ def _get_image(self, image_id, target, user, project, size=None):
+ if not os.path.exists(target):
+ if FLAGS.use_cow_images:
+ base = os.path.join(FLAGS.instances_path, '_base')
+ if not os.path.exists(base):
+ images.fetch(image_id, base, user, project)
+ if size:
+ # TODO(vish): Attempt to resize the filesystem
+ disk.extend(base, size)
+ utils.execute('qemu-img create -f qcow2 -o '
+ 'cluster_size=2M,backing_file=%s %s'
+ % (base, target))
+ else:
+ images.fetch(image_id, target, user, project)
+ if size:
+ # TODO(vish): Attempt to resize the filesystem
+ disk.extend(target, size)
+
+ def _get_local(self, local_gb, target):
+ if not os.path.exists(target):
+ last_mb = local_gb * 1024 - 1
+ if FLAGS.use_cow_images:
+ base = os.path.join(FLAGS.instances_path, '_base')
+ if not os.path.exists(base):
+ utils.execute('dd if=/dev/zero of=%s bs=1M count=1'
+ 'seek=%s' % (base, last_mb))
+ utils.execute('qemu-img create -f qcow2 -o '
+ 'cluster_size=2M,backing_file=%s %s'
+ % (base, target))
+ else:
+ utils.execute('dd if=/dev/zero of=%s bs=1M count=1'
+ 'seek=%s' % (base, last_mb))
+
def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
# syntactic nicety
- basepath = lambda fname = '', prefix = prefix: os.path.join(
- FLAGS.instances_path,
- inst['name'],
- prefix + fname)
+ def basepath(fname='', prefix=prefix):
+ return os.path.join(FLAGS.instances_path,
+ inst['name'],
+ prefix + fname)
# ensure directories exist and are writable
utils.execute('mkdir -p %s' % basepath(prefix=''))
utils.execute('chmod 0777 %s' % basepath(prefix=''))
- # TODO(termie): these are blocking calls, it would be great
- # if they weren't.
logging.info(_('instance %s: Creating image'), inst['name'])
f = open(basepath('libvirt.xml'), 'w')
f.write(libvirt_xml)
@@ -447,23 +481,26 @@ class LibvirtConnection(object):
disk_images = {'image_id': inst['image_id'],
'kernel_id': inst['kernel_id'],
'ramdisk_id': inst['ramdisk_id']}
- if not os.path.exists(basepath('disk')):
- images.fetch(inst.image_id, basepath('disk-raw'), user,
- project)
-
- if inst['kernel_id']:
- if not os.path.exists(basepath('kernel')):
- images.fetch(inst['kernel_id'], basepath('kernel'),
- user, project)
- if inst['ramdisk_id']:
- if not os.path.exists(basepath('ramdisk')):
- images.fetch(inst['ramdisk_id'], basepath('ramdisk'),
- user, project)
-
- def execute(cmd, process_input=None, check_exit_code=True):
- return utils.execute(cmd=cmd,
- process_input=process_input,
- check_exit_code=check_exit_code)
+
+ if disk_images['kernel_id']:
+ self._get_image(disk_images['kernel_id'], basepath('kernel'),
+ user, project)
+ if disk_images['ramdisk_id']:
+ self._get_image(disk_images['ramdisk_id'], basepath('ramdisk'),
+ user, project)
+
+
+ size = FLAGS.minimum_root_size
+ if not FLAGS.use_cow_images:
+ if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
+ size = None
+
+ self._get_image(disk_images['image_id'], basepath('disk'),
+ user, project, size)
+
+ type_data = instance_types.INSTANCE_TYPES[inst['instance_type']]
+ self._get_local(type_data['local_gb'], basepath('local'),
+ user, project, size)
# For now, we assume that if we're not using a kernel, we're using a
# partitioned disk image where the target partition is the first
@@ -493,34 +530,14 @@ class LibvirtConnection(object):
logging.info(_('instance %s: injecting net into image %s'),
inst['name'], inst.image_id)
try:
- disk.inject_data(basepath('disk-raw'), key, net,
- partition=target_partition,
- execute=execute)
+ disk.inject_data(basepath('disk'), key, net,
+ partition=target_partition)
except Exception as e:
# This could be a windows image, or a vmdk format disk
logging.warn(_('instance %s: ignoring error injecting data'
' into image %s (%s)'),
inst['name'], inst.image_id, e)
- if inst['kernel_id']:
- if os.path.exists(basepath('disk')):
- utils.execute('rm -f %s' % basepath('disk'))
-
- local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type]
- ['local_gb']
- * 1024 * 1024 * 1024)
-
- resize = True
- if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
- resize = False
-
- if inst['kernel_id']:
- disk.partition(basepath('disk-raw'), basepath('disk'),
- local_bytes, resize, execute=execute)
- else:
- os.rename(basepath('disk-raw'), basepath('disk'))
- disk.extend(basepath('disk'), local_bytes, execute=execute)
-
if FLAGS.libvirt_type == 'uml':
utils.execute('sudo chown root %s' % basepath('disk'))
@@ -558,7 +575,8 @@ class LibvirtConnection(object):
'ip_address': ip_address,
'dhcp_server': dhcp_server,
'extra_params': extra_params,
- 'rescue': rescue}
+ 'rescue': rescue,
+ 'local': instance_type['local_gb']}
if not rescue:
if instance['kernel_id']:
xml_info['kernel'] = xml_info['basepath'] + "/kernel"