Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

blueprint lvm-disk-images

Add ability to use LVM volumes for VM disks.

Implements LVM disks support for libvirt driver.

VM disks will be stored on LVM volumes in volume group
 specified by `libvirt_images_volume_group` option.
 Another option `libvirt_local_images_type` specify which storage
 type will be used. Supported values are `raw`, `lvm`, `qcow2`,
 `default`. If `libvirt_local_images_type` = `default`, usual
 logic with `use_cow_images` flag is used.
Boolean option `libvirt_sparse_logical_volumes` controls which type
 of logical volumes will be created (sparsed with virtualsize or
 usual logical volumes with full space allocation). Default value
 for this option is `False`.
Commit introduce three classes: `Raw`, `Qcow2` and `Lvm`. They contain
 image creation logic, that was stored in
 `LibvirtConnection._cache_image` and `libvirt_info` methods,
 that produce right `LibvirtGuestConfigDisk` configurations for
 libvirt. `Backend` class choose which image type to use.

Change-Id: I0d01cb7d2fd67de2565b8d45d34f7846ad4112c2
  • Loading branch information...
commit e0540dfed1c1276106105aea8d5765356961ef3d 1 parent 6555c5a
@frenzykryger frenzykryger authored
View
1  Authors
@@ -23,6 +23,7 @@ Asbjørn Sannes <asbjorn.sannes@interhost.no>
Ben McGraw <ben@pistoncloud.com>
Ben Swartzlander <bswartz@netapp.com>
Bilal Akhtar <bilalakhtar@ubuntu.com>
+Boris Filippov <bfilippov@griddynamics.com>
Brad Hall <brad@nicira.com>
Brad McConnell <bmcconne@rackspace.com>
Brendan Maguire <B_Maguire@Dell.com>
View
9 nova/rootwrap/compute.py
@@ -188,4 +188,13 @@
# nova/virt/libvirt/connection.py:
filters.ReadFileFilter("/etc/iscsi/initiatorname.iscsi"),
+ # nova/virt/libvirt/connection.py:
+ filters.CommandFilter("/sbin/lvremove", "root"),
+
+ # nova/virt/libvirt/utils.py:
+ filters.CommandFilter("/sbin/lvcreate", "root"),
+
+ # nova/virt/libvirt/utils.py:
+ filters.CommandFilter("/sbin/vgs", "root")
+
]
View
48 nova/tests/fake_imagebackend.py
@@ -0,0 +1,48 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Grid Dynamics
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+from nova.virt.libvirt import config
+from nova.virt.libvirt import imagebackend
+
+
+class Backend(object):
+ def __init__(self, use_cow):
+ pass
+
+ def image(self, instance, name, suffix='', image_type=''):
+ class FakeImage(imagebackend.Image):
+ def __init__(self, instance, name, suffix=''):
+ self.path = os.path.join(instance, name + suffix)
+
+ def create_image(self, prepare_template, base,
+ size, *args, **kwargs):
+ pass
+
+ def cache(self, fn, fname, size=None, *args, **kwargs):
+ pass
+
+ def libvirt_info(self, device_type):
+ info = config.LibvirtConfigGuestDisk()
+ info.source_type = 'file'
+ info.source_device = device_type
+ info.driver_format = 'raw'
+ info.source_path = self.path
+ return info
+
+ return FakeImage(instance, name, suffix)
View
16 nova/tests/fake_libvirt_utils.py
@@ -50,6 +50,22 @@ def mkfs(fs, path):
pass
+def resize2fs(path):
+ pass
+
+
+def create_lvm_image(vg, lv, size, sparse=False):
+ pass
+
+
+def volume_group_free_space(vg):
+ pass
+
+
+def remove_logical_volumes(*paths):
+ pass
+
+
def ensure_tree(path):
pass
View
392 nova/tests/test_imagebackend.py
@@ -0,0 +1,392 @@
+#!/usr/bin/python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Grid Dynamics
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+from nova import flags
+from nova import test
+from nova.tests import fake_libvirt_utils
+from nova.virt.libvirt import imagebackend
+
+FLAGS = flags.FLAGS
+
+
+class _ImageTestCase(test.TestCase):
+ INSTANCES_PATH = '/fake'
+
+ def mock_create_image(self, image):
+ def create_image(fn, base, size, *args, **kwargs):
+ fn(target=base, *args, **kwargs)
+ image.create_image = create_image
+
+ def setUp(self):
+ super(_ImageTestCase, self).setUp()
+ self.flags(instances_path=self.INSTANCES_PATH)
+ self.INSTANCE = 'instance'
+ self.NAME = 'fake'
+ self.SUFFIX = 'vm'
+ self.TEMPLATE = 'template'
+
+ self.PATH = os.path.join(FLAGS.instances_path, self.INSTANCE,
+ self.NAME + self.SUFFIX)
+ self.TEMPLATE_DIR = os.path.join(FLAGS.instances_path,
+ '_base')
+ self.TEMPLATE_PATH = os.path.join(self.TEMPLATE_DIR, 'template')
+
+ imagebackend.libvirt_utils = fake_libvirt_utils
+
+ def test_cache(self):
+ self.mox.StubOutWithMock(os.path, 'exists')
+ os.path.exists(self.PATH).AndReturn(False)
+ os.path.exists(self.TEMPLATE_DIR).AndReturn(False)
+ os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
+ fn = self.mox.CreateMockAnything()
+ fn(target=self.TEMPLATE_PATH)
+ self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'ensure_tree')
+ imagebackend.libvirt_utils.ensure_tree(self.TEMPLATE_DIR)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ self.mock_create_image(image)
+ image.cache(fn, self.TEMPLATE)
+
+ self.mox.VerifyAll()
+
+ def test_cache_image_exists(self):
+ self.mox.StubOutWithMock(os.path, 'exists')
+ os.path.exists(self.PATH).AndReturn(True)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.cache(None, self.TEMPLATE)
+
+ self.mox.VerifyAll()
+
+ def test_cache_base_dir_exists(self):
+ self.mox.StubOutWithMock(os.path, 'exists')
+ os.path.exists(self.PATH).AndReturn(False)
+ os.path.exists(self.TEMPLATE_DIR).AndReturn(True)
+ os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
+ fn = self.mox.CreateMockAnything()
+ fn(target=self.TEMPLATE_PATH)
+ self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'ensure_tree')
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ self.mock_create_image(image)
+ image.cache(fn, self.TEMPLATE)
+
+ self.mox.VerifyAll()
+
+ def test_cache_template_exists(self):
+ self.mox.StubOutWithMock(os.path, 'exists')
+ os.path.exists(self.PATH).AndReturn(False)
+ os.path.exists(self.TEMPLATE_DIR).AndReturn(True)
+ os.path.exists(self.TEMPLATE_PATH).AndReturn(True)
+ fn = self.mox.CreateMockAnything()
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ self.mock_create_image(image)
+ image.cache(fn, self.TEMPLATE)
+
+ self.mox.VerifyAll()
+
+
+class RawTestCase(_ImageTestCase):
+
+ SIZE = 1024
+
+ def setUp(self):
+ self.image_class = imagebackend.Raw
+ super(RawTestCase, self).setUp()
+
+ def prepare_mocks(self):
+ fn = self.mox.CreateMockAnything()
+ self.mox.StubOutWithMock(imagebackend.utils.synchronized, '__call__')
+ self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'copy_image')
+ self.mox.StubOutWithMock(imagebackend.disk, 'extend')
+ return fn
+
+ def test_create_image(self):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH, image_id=None)
+ imagebackend.libvirt_utils.copy_image(self.TEMPLATE_PATH, self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, None, image_id=None)
+
+ self.mox.VerifyAll()
+
+ def test_create_image_generated(self):
+ fn = self.prepare_mocks()
+ fn(target=self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, None)
+
+ self.mox.VerifyAll()
+
+ def test_create_image_extend(self):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH, image_id=None)
+ imagebackend.libvirt_utils.copy_image(self.TEMPLATE_PATH, self.PATH)
+ imagebackend.disk.extend(self.PATH, self.SIZE)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, self.SIZE, image_id=None)
+
+ self.mox.VerifyAll()
+
+
+class Qcow2TestCase(_ImageTestCase):
+ SIZE = 1024 * 1024 * 1024
+
+ def setUp(self):
+ self.image_class = imagebackend.Qcow2
+ super(Qcow2TestCase, self).setUp()
+ self.QCOW2_BASE = (self.TEMPLATE_PATH +
+ '_%d' % (self.SIZE / (1024 * 1024 * 1024)))
+
+ def prepare_mocks(self):
+ fn = self.mox.CreateMockAnything()
+ self.mox.StubOutWithMock(imagebackend.utils.synchronized, '__call__')
+ self.mox.StubOutWithMock(imagebackend.libvirt_utils,
+ 'create_cow_image')
+ self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'copy_image')
+ self.mox.StubOutWithMock(imagebackend.disk, 'extend')
+ return fn
+
+ def test_create_image(self):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH)
+ imagebackend.libvirt_utils.create_cow_image(self.TEMPLATE_PATH,
+ self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, None)
+
+ self.mox.VerifyAll()
+
+ def test_create_image_with_size(self):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH)
+ self.mox.StubOutWithMock(os.path, 'exists')
+ os.path.exists(self.QCOW2_BASE).AndReturn(False)
+ imagebackend.libvirt_utils.copy_image(self.TEMPLATE_PATH,
+ self.QCOW2_BASE)
+ imagebackend.disk.extend(self.QCOW2_BASE, self.SIZE)
+ imagebackend.libvirt_utils.create_cow_image(self.QCOW2_BASE,
+ self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, self.SIZE)
+
+ self.mox.VerifyAll()
+
+ def test_create_image_with_size_template_exists(self):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH)
+ self.mox.StubOutWithMock(os.path, 'exists')
+ os.path.exists(self.QCOW2_BASE).AndReturn(True)
+ imagebackend.libvirt_utils.create_cow_image(self.QCOW2_BASE,
+ self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, self.SIZE)
+
+ self.mox.VerifyAll()
+
+
+class LvmTestCase(_ImageTestCase):
+ VG = 'FakeVG'
+ TEMPLATE_SIZE = 512
+ SIZE = 1024
+
+ def setUp(self):
+ self.image_class = imagebackend.Lvm
+ super(LvmTestCase, self).setUp()
+ self.flags(libvirt_images_volume_group=self.VG)
+ self.LV = '%s_%s' % (self.INSTANCE, self.NAME + self.SUFFIX)
+ self.PATH = os.path.join('/dev', self.VG, self.LV)
+
+ self.disk = imagebackend.disk
+ self.utils = imagebackend.utils
+ self.libvirt_utils = imagebackend.libvirt_utils
+
+ def prepare_mocks(self):
+ fn = self.mox.CreateMockAnything()
+ self.mox.StubOutWithMock(self.disk, 'get_image_virtual_size')
+ self.mox.StubOutWithMock(self.disk, 'resize2fs')
+ self.mox.StubOutWithMock(self.libvirt_utils, 'create_lvm_image')
+ self.mox.StubOutWithMock(self.utils, 'execute')
+ return fn
+
+ def _create_image(self, sparse):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH)
+ self.disk.get_image_virtual_size(self.TEMPLATE_PATH
+ ).AndReturn(self.TEMPLATE_SIZE)
+ self.libvirt_utils.create_lvm_image(self.VG,
+ self.LV,
+ self.TEMPLATE_SIZE,
+ sparse=sparse)
+ cmd = ('dd', 'if=%s' % self.TEMPLATE_PATH,
+ 'of=%s' % self.PATH, 'bs=4M')
+ self.utils.execute(*cmd, run_as_root=True)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, None)
+
+ self.mox.VerifyAll()
+
+ def _create_image_generated(self, sparse):
+ fn = self.prepare_mocks()
+ self.libvirt_utils.create_lvm_image(self.VG, self.LV,
+ self.SIZE, sparse=sparse)
+ fn(target=self.PATH, ephemeral_size=None)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH,
+ self.SIZE, ephemeral_size=None)
+
+ self.mox.VerifyAll()
+
+ def _create_image_resize(self, sparse):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH)
+ self.disk.get_image_virtual_size(self.TEMPLATE_PATH
+ ).AndReturn(self.TEMPLATE_SIZE)
+ self.libvirt_utils.create_lvm_image(self.VG, self.LV,
+ self.SIZE, sparse=sparse)
+ cmd = ('dd', 'if=%s' % self.TEMPLATE_PATH,
+ 'of=%s' % self.PATH, 'bs=4M')
+ self.utils.execute(*cmd, run_as_root=True)
+ self.disk.resize2fs(self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+ image.create_image(fn, self.TEMPLATE_PATH, self.SIZE)
+
+ self.mox.VerifyAll()
+
+ def test_create_image(self):
+ self._create_image(False)
+
+ def test_create_image_sparsed(self):
+ self.flags(libvirt_sparse_logical_volumes=True)
+ self._create_image(True)
+
+ def test_create_image_generated(self):
+ self._create_image_generated(False)
+
+ def test_create_image_generated_sparsed(self):
+ self.flags(libvirt_sparse_logical_volumes=True)
+ self._create_image_generated(True)
+
+ def test_create_image_resize(self):
+ self._create_image_resize(False)
+
+ def test_create_image_resize_sparsed(self):
+ self.flags(libvirt_sparse_logical_volumes=True)
+ self._create_image_resize(True)
+
+ def test_create_image_negative(self):
+ fn = self.prepare_mocks()
+ fn(target=self.TEMPLATE_PATH)
+ self.disk.get_image_virtual_size(self.TEMPLATE_PATH
+ ).AndReturn(self.TEMPLATE_SIZE)
+ self.libvirt_utils.create_lvm_image(self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=False
+ ).AndRaise(RuntimeError())
+ self.mox.StubOutWithMock(self.libvirt_utils, 'remove_logical_volumes')
+ self.libvirt_utils.remove_logical_volumes(self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+
+ self.assertRaises(RuntimeError, image.create_image, fn,
+ self.TEMPLATE_PATH, self.SIZE)
+ self.mox.VerifyAll()
+
+ def test_create_image_generated_negative(self):
+ fn = self.prepare_mocks()
+ fn(target=self.PATH,
+ ephemeral_size=None).AndRaise(RuntimeError())
+ self.libvirt_utils.create_lvm_image(self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=False)
+ self.mox.StubOutWithMock(self.libvirt_utils, 'remove_logical_volumes')
+ self.libvirt_utils.remove_logical_volumes(self.PATH)
+ self.mox.ReplayAll()
+
+ image = self.image_class(self.INSTANCE, self.NAME, self.SUFFIX)
+
+ self.assertRaises(RuntimeError, image.create_image, fn,
+ self.TEMPLATE_PATH, self.SIZE,
+ ephemeral_size=None)
+ self.mox.VerifyAll()
+
+
+class BackendTestCase(test.TestCase):
+ INSTANCE = 'fake-instance'
+ NAME = 'fake-name'
+ SUFFIX = 'suffix'
+
+ def get_image(self, use_cow, image_type):
+ return imagebackend.Backend(use_cow).image(self.INSTANCE,
+ self.NAME,
+ self.SUFFIX,
+ image_type)
+
+ def _test_image(self, image_type, image_not_cow, image_cow):
+ image1 = self.get_image(False, image_type)
+ image2 = self.get_image(True, image_type)
+
+ def assertIsInstance(instance, class_object):
+ failure = ('Expected %s,' +
+ ' but got %s.') % (class_object.__name__,
+ instance.__class__.__name__)
+ self.assertTrue(isinstance(instance, class_object), failure)
+
+ assertIsInstance(image1, image_not_cow)
+ assertIsInstance(image2, image_cow)
+
+ def test_image_raw(self):
+ self._test_image('raw', imagebackend.Raw, imagebackend.Raw)
+
+ def test_image_qcow2(self):
+ self._test_image('qcow2', imagebackend.Qcow2, imagebackend.Qcow2)
+
+ def test_image_lvm(self):
+ self.flags(libvirt_images_volume_group='FakeVG')
+ self._test_image('lvm', imagebackend.Lvm, imagebackend.Lvm)
+
+ def test_image_default(self):
+ self._test_image('default', imagebackend.Raw, imagebackend.Qcow2)
View
27 nova/tests/test_libvirt.py
@@ -47,6 +47,7 @@
from nova.virt.libvirt import config
from nova.virt.libvirt import connection
from nova.virt.libvirt import firewall
+from nova.virt.libvirt import imagebackend
from nova.virt.libvirt import utils as libvirt_utils
from nova.virt.libvirt import volume
from nova.volume import driver as volume_driver
@@ -342,24 +343,24 @@ def fake_extend(image, size):
self.stubs.Set(os.path, 'exists', fake_exists)
self.stubs.Set(utils, 'execute', fake_execute)
- self.stubs.Set(connection.disk, 'extend', fake_extend)
- connection.libvirt_utils = fake_libvirt_utils
+ self.stubs.Set(imagebackend.disk, 'extend', fake_extend)
+ imagebackend.libvirt_utils = fake_libvirt_utils
def tearDown(self):
- connection.libvirt_utils = libvirt_utils
+ imagebackend.libvirt_utils = libvirt_utils
super(CacheConcurrencyTestCase, self).tearDown()
def test_same_fname_concurrency(self):
"""Ensures that the same fname cache runs at a sequentially"""
- conn = connection.LibvirtDriver
+ backend = imagebackend.Backend(False)
wait1 = eventlet.event.Event()
done1 = eventlet.event.Event()
- eventlet.spawn(conn._cache_image, _concurrency,
- 'target', 'fname', False, None, wait1, done1)
+ eventlet.spawn(backend.image('instance', 'name').cache,
+ _concurrency, 'fname', None, wait=wait1, done=done1)
wait2 = eventlet.event.Event()
done2 = eventlet.event.Event()
- eventlet.spawn(conn._cache_image, _concurrency,
- 'target', 'fname', False, None, wait2, done2)
+ eventlet.spawn(backend.image('instance', 'name').cache,
+ _concurrency, 'fname', None, wait=wait2, done=done2)
wait2.send()
eventlet.sleep(0)
try:
@@ -372,15 +373,15 @@ def test_same_fname_concurrency(self):
def test_different_fname_concurrency(self):
"""Ensures that two different fname caches are concurrent"""
- conn = connection.LibvirtDriver
+ backend = imagebackend.Backend(False)
wait1 = eventlet.event.Event()
done1 = eventlet.event.Event()
- eventlet.spawn(conn._cache_image, _concurrency,
- 'target', 'fname2', False, None, wait1, done1)
+ eventlet.spawn(backend.image('instance', 'name').cache,
+ _concurrency, 'fname2', None, wait=wait1, done=done1)
wait2 = eventlet.event.Event()
done2 = eventlet.event.Event()
- eventlet.spawn(conn._cache_image, _concurrency,
- 'target', 'fname1', False, None, wait2, done2)
+ eventlet.spawn(backend.image('instance', 'name').cache,
+ _concurrency, 'fname1', None, wait=wait2, done=done2)
wait2.send()
eventlet.sleep(0)
try:
View
2  nova/tests/test_virt_drivers.py
@@ -63,6 +63,7 @@ def _setup_fakelibvirt(self):
else:
self.saved_libvirt = None
+ import fake_imagebackend
import fake_libvirt_utils
import fakelibvirt
@@ -70,6 +71,7 @@ def _setup_fakelibvirt(self):
import nova.virt.libvirt.connection
import nova.virt.libvirt.firewall
+ nova.virt.libvirt.connection.imagebackend = fake_imagebackend
nova.virt.libvirt.connection.libvirt = fakelibvirt
nova.virt.libvirt.connection.libvirt_utils = fake_libvirt_utils
nova.virt.libvirt.firewall.libvirt = fakelibvirt
View
8 nova/virt/disk/api.py
@@ -108,6 +108,11 @@ def get_image_virtual_size(image):
return int(m.group(2))
+def resize2fs(image, check_exit_code=False):
+ utils.execute('e2fsck', '-fp', image, check_exit_code=check_exit_code)
+ utils.execute('resize2fs', image, check_exit_code=check_exit_code)
+
+
def extend(image, size):
"""Increase image to size"""
# NOTE(MotoKen): check image virtual size before resize
@@ -116,8 +121,7 @@ def extend(image, size):
return
utils.execute('qemu-img', 'resize', image, size)
# NOTE(vish): attempts to resize filesystem
- utils.execute('e2fsck', '-fp', image, check_exit_code=False)
- utils.execute('resize2fs', image, check_exit_code=False)
+ resize2fs(image)
def bind(src, target, instance_name):
View
269 nova/virt/libvirt/connection.py
@@ -71,6 +71,7 @@
from nova.virt import driver
from nova.virt.libvirt import config
from nova.virt.libvirt import firewall
+from nova.virt.libvirt import imagebackend
from nova.virt.libvirt import imagecache
from nova.virt.libvirt import utils as libvirt_utils
@@ -271,6 +272,7 @@ def __init__(self, read_only=False):
self._disk_cachemode = None
self.image_cache_manager = imagecache.ImageCacheManager()
+ self.image_backend = imagebackend.Backend(FLAGS.use_cow_images)
@property
def disk_cachemode(self):
@@ -506,6 +508,34 @@ def _cleanup(self, instance, network_info, block_device_info):
if os.path.exists(target):
shutil.rmtree(target)
+ #NOTE(bfilippov): destroy all LVM disks for this instance
+ self._cleanup_lvm(instance)
+
+ def _cleanup_lvm(self, instance):
+ """Delete all LVM disks for given instance object"""
+ disks = self._lvm_disks(instance)
+ if disks:
+ libvirt_utils.remove_logical_volumes(*disks)
+
+ def _lvm_disks(self, instance):
+ """Returns all LVM disks for given instance object"""
+ if FLAGS.libvirt_images_volume_group:
+ vg = os.path.join('/dev', FLAGS.libvirt_images_volume_group)
+ if not os.path.exists(vg):
+ return []
+ pattern = '%s_' % instance['name']
+
+ def belongs_to_instance(disk):
+ return disk.startswith(pattern)
+
+ def fullpath(name):
+ return os.path.join(vg, name)
+
+ disk_names = filter(belongs_to_instance, os.listdir(vg))
+ disks = map(fullpath, disk_names)
+ return disks
+ return []
+
def get_volume_connector(self, instance):
if not self._initiator:
self._initiator = libvirt_utils.get_iscsi_initiator()
@@ -681,7 +711,10 @@ def snapshot(self, context, instance, image_href):
# NOTE(vish): assume amis are raw
source_format = 'raw'
image_format = FLAGS.snapshot_image_format or source_format
- if FLAGS.use_cow_images:
+ use_qcow2 = ((FLAGS.libvirt_images_type == 'default' and
+ FLAGS.use_cow_images) or
+ FLAGS.libvirt_images_type == 'qcow2')
+ if use_qcow2:
source_format = 'qcow2'
# NOTE(vish): glance forces ami disk format to be ami
if base.get('disk_format') == 'ami':
@@ -957,8 +990,8 @@ def _append_to_file(self, data, fpath):
return fpath
def _inject_files(self, instance, files, partition):
- disk_path = os.path.join(FLAGS.instances_path,
- instance['name'], 'disk')
+ disk_path = self.image_backend.image(instance['name'],
+ 'disk').path
disk.inject_files(disk_path, files, partition=partition,
use_cow=FLAGS.use_cow_images)
@@ -1071,72 +1104,6 @@ def _supports_direct_io(dirpath):
return hasDirectIO
@staticmethod
- def _cache_image(fn, target, fname, cow=False, size=None, *args, **kwargs):
- """Wrapper for a method that creates an image that caches the image.
-
- This wrapper will save the image into a common store and create a
- copy for use by the hypervisor.
-
- The underlying method should specify a kwarg of target representing
- where the image will be saved.
-
- fname is used as the filename of the base image. The filename needs
- to be unique to a given image.
-
- If cow is True, it will make a CoW image instead of a copy.
-
- If size is specified, we attempt to resize up to that size.
- """
-
- # NOTE(mikal): Checksums aren't created here, even if the image cache
- # manager is enabled, as that would slow down VM startup. If both
- # cache management and checksumming are enabled, then the checksum
- # will be created on the first pass of the image cache manager.
-
- generating = 'image_id' not in kwargs
- if not os.path.exists(target):
- base_dir = os.path.join(FLAGS.instances_path, FLAGS.base_dir_name)
-
- if not os.path.exists(base_dir):
- libvirt_utils.ensure_tree(base_dir)
- base = os.path.join(base_dir, fname)
-
- @utils.synchronized(fname)
- def call_if_not_exists(base, fn, *args, **kwargs):
- if not os.path.exists(base):
- with utils.remove_path_on_error(base):
- fn(target=base, *args, **kwargs)
-
- if cow or not generating:
- call_if_not_exists(base, fn, *args, **kwargs)
- elif generating:
- # For raw it's quicker to just generate outside the cache
- call_if_not_exists(target, fn, *args, **kwargs)
-
- @utils.synchronized(base)
- def copy_and_extend(cow, generating, base, target, size):
- if cow:
- cow_base = base
- if size:
- size_gb = size / (1024 * 1024 * 1024)
- cow_base += "_%d" % size_gb
- if not os.path.exists(cow_base):
- with utils.remove_path_on_error(cow_base):
- libvirt_utils.copy_image(base, cow_base)
- disk.extend(cow_base, size)
- libvirt_utils.create_cow_image(cow_base, target)
- elif not generating:
- libvirt_utils.copy_image(base, target)
- # Resize after the copy, as it's usually much faster
- # to make sparse updates, rather than potentially
- # naively copying the whole image file.
- if size:
- disk.extend(target, size)
-
- with utils.remove_path_on_error(target):
- copy_and_extend(cow, generating, base, target, size)
-
- @staticmethod
def _create_local(target, local_size, unit='G',
fs_format=None, label=None):
"""Create a blank image of specified size"""
@@ -1181,6 +1148,13 @@ def basepath(fname='', suffix=suffix):
instance['name'],
fname + suffix)
+ def image(fname, image_type=FLAGS.libvirt_images_type):
+ return self.image_backend.image(instance['name'],
+ fname, suffix, image_type)
+
+ def raw(fname):
+ return image(fname, image_type='raw')
+
# ensure directories exist and are writable
libvirt_utils.ensure_tree(basepath(suffix=''))
@@ -1204,22 +1178,20 @@ def basepath(fname='', suffix=suffix):
if disk_images['kernel_id']:
fname = disk_images['kernel_id']
- self._cache_image(fn=libvirt_utils.fetch_image,
- context=context,
- target=basepath('kernel'),
- fname=fname,
- image_id=disk_images['kernel_id'],
- user_id=instance['user_id'],
- project_id=instance['project_id'])
+ raw('kernel').cache(fn=libvirt_utils.fetch_image,
+ context=context,
+ fname=fname,
+ image_id=disk_images['kernel_id'],
+ user_id=instance['user_id'],
+ project_id=instance['project_id'])
if disk_images['ramdisk_id']:
fname = disk_images['ramdisk_id']
- self._cache_image(fn=libvirt_utils.fetch_image,
- context=context,
- target=basepath('ramdisk'),
- fname=fname,
- image_id=disk_images['ramdisk_id'],
- user_id=instance['user_id'],
- project_id=instance['project_id'])
+ raw('ramdisk').cache(fn=libvirt_utils.fetch_image,
+ context=context,
+ fname=fname,
+ image_id=disk_images['ramdisk_id'],
+ user_id=instance['user_id'],
+ project_id=instance['project_id'])
root_fname = hashlib.sha1(str(disk_images['image_id'])).hexdigest()
size = instance['root_gb'] * 1024 * 1024 * 1024
@@ -1231,15 +1203,13 @@ def basepath(fname='', suffix=suffix):
if not self._volume_in_mapping(self.default_root_device,
block_device_info):
- self._cache_image(fn=libvirt_utils.fetch_image,
- context=context,
- target=basepath('disk'),
- fname=root_fname,
- cow=FLAGS.use_cow_images,
- image_id=disk_images['image_id'],
- user_id=instance['user_id'],
- project_id=instance['project_id'],
- size=size)
+ image('disk').cache(fn=libvirt_utils.fetch_image,
+ context=context,
+ fname=root_fname,
+ size=size,
+ image_id=disk_images['image_id'],
+ user_id=instance['user_id'],
+ project_id=instance['project_id'])
ephemeral_gb = instance['ephemeral_gb']
if ephemeral_gb and not self._volume_in_mapping(
@@ -1248,12 +1218,14 @@ def basepath(fname='', suffix=suffix):
fn = functools.partial(self._create_ephemeral,
fs_label='ephemeral0',
os_type=instance.os_type)
- self._cache_image(fn=fn,
- target=basepath('disk.local'),
- fname="ephemeral_%s_%s_%s" %
- ("0", ephemeral_gb, instance.os_type),
- cow=FLAGS.use_cow_images,
- ephemeral_size=ephemeral_gb)
+ fname = "ephemeral_%s_%s_%s" % ("0",
+ ephemeral_gb,
+ instance.os_type)
+ size = ephemeral_gb * 1024 * 1024 * 1024
+ image('disk.local').cache(fn=fn,
+ fname=fname,
+ size=size,
+ ephemeral_size=ephemeral_gb)
else:
swap_device = self.default_second_device
@@ -1261,12 +1233,14 @@ def basepath(fname='', suffix=suffix):
fn = functools.partial(self._create_ephemeral,
fs_label='ephemeral%d' % eph['num'],
os_type=instance.os_type)
- self._cache_image(fn=fn,
- target=basepath(_get_eph_disk(eph)),
- fname="ephemeral_%s_%s_%s" %
- (eph['num'], eph['size'], instance.os_type),
- cow=FLAGS.use_cow_images,
- ephemeral_size=eph['size'])
+ size = eph['size'] * 1024 * 1024 * 1024
+ fname = "ephemeral_%s_%s_%s" % (eph['num'],
+ eph['size'],
+ instance.os_type)
+ image(_get_eph_disk(eph)).cache(fn=fn,
+ fname=fname,
+ size=size,
+ ephemeral_size=eph['size'])
swap_mb = 0
@@ -1278,11 +1252,11 @@ def basepath(fname='', suffix=suffix):
swap_mb = inst_type['swap']
if swap_mb > 0:
- self._cache_image(fn=self._create_swap,
- target=basepath('disk.swap'),
- fname="swap_%s" % swap_mb,
- cow=FLAGS.use_cow_images,
- swap_mb=swap_mb)
+ size = swap_mb * 1024 * 1024
+ image('disk.swap').cache(fn=self._create_swap,
+ fname="swap_%s" % swap_mb,
+ size=size,
+ swap_mb=swap_mb)
target_partition = None
if not instance['kernel_id']:
@@ -1297,12 +1271,11 @@ def basepath(fname='', suffix=suffix):
if config_drive_id:
fname = config_drive_id
- self._cache_image(fn=libvirt_utils.fetch_image,
- target=basepath('disk.config'),
- fname=fname,
- image_id=config_drive_id,
- user_id=instance['user_id'],
- project_id=instance['project_id'],)
+ raw('disk.config').cache(fn=libvirt_utils.fetch_image,
+ fname=fname,
+ image_id=config_drive_id,
+ user_id=instance['user_id'],
+ project_id=instance['project_id'])
elif config_drive:
label = 'config'
with utils.remove_path_on_error(basepath('disk.config')):
@@ -1360,10 +1333,10 @@ def basepath(fname='', suffix=suffix):
if any((key, net, metadata, admin_password)):
if config_drive: # Should be True or None by now.
- injection_path = basepath('disk.config')
+ injection_path = raw('disk.config').path
img_id = 'config-drive'
else:
- injection_path = basepath('disk')
+ injection_path = image('disk').path
img_id = instance.image_ref
for injection in ('metadata', 'key', 'net', 'admin_password'):
@@ -1511,16 +1484,16 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
"rootfs")
guest.add_device(fs)
else:
- if FLAGS.use_cow_images:
- driver_type = 'qcow2'
- else:
- driver_type = 'raw'
-
if image_meta and image_meta.get('disk_format') == 'iso':
root_device_type = 'cdrom'
else:
root_device_type = 'disk'
+ def disk_info(name, suffix=''):
+ image = self.image_backend.image(instance['name'],
+ name, suffix)
+ return image.libvirt_info(root_device_type)
+
if FLAGS.libvirt_type == "uml":
ephemeral_disk_bus = "uml"
elif FLAGS.libvirt_type == "xen":
@@ -1529,23 +1502,13 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
ephemeral_disk_bus = "virtio"
if rescue:
- diskrescue = config.LibvirtConfigGuestDisk()
- diskrescue.source_type = "file"
- diskrescue.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- "disk.rescue")
- diskrescue.driver_format = driver_type
+ diskrescue = disk_info('disk', '.rescue')
diskrescue.driver_cache = self.disk_cachemode
diskrescue.target_dev = self.default_root_device
diskrescue.target_bus = ephemeral_disk_bus
guest.add_device(diskrescue)
- diskos = config.LibvirtConfigGuestDisk()
- diskos.source_type = "file"
- diskos.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- "disk")
- diskos.driver_format = driver_type
+ diskos = disk_info('disk')
diskos.driver_cache = self.disk_cachemode
diskos.target_dev = self.default_second_device
diskos.target_bus = ephemeral_disk_bus
@@ -1555,14 +1518,8 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
block_device_info)
if not ebs_root:
- diskos = config.LibvirtConfigGuestDisk()
- diskos.source_type = "file"
- diskos.source_device = root_device_type
- diskos.driver_format = driver_type
+ diskos = disk_info('disk')
diskos.driver_cache = self.disk_cachemode
- diskos.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- "disk")
diskos.target_dev = root_device
if root_device_type == "cdrom":
diskos.target_bus = "ide"
@@ -1580,14 +1537,8 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
ephemeral_device = self.default_second_device
if ephemeral_device is not None:
- disklocal = config.LibvirtConfigGuestDisk()
- disklocal.source_type = "file"
- disklocal.source_device = root_device_type
- disklocal.driver_format = driver_type
+ disklocal = disk_info('disk.local')
disklocal.driver_cache = self.disk_cachemode
- disklocal.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- "disk.local")
disklocal.target_dev = ephemeral_device
disklocal.target_bus = ephemeral_disk_bus
guest.add_device(disklocal)
@@ -1603,14 +1554,8 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
for eph in driver.block_device_info_get_ephemerals(
block_device_info):
- diskeph = config.LibvirtConfigGuestDisk()
- diskeph.source_type = "block"
- diskeph.source_device = root_device_type
- diskeph.driver_format = driver_type
+ diskeph = disk_info(_get_eph_disk(eph))
diskeph.driver_cache = self.disk_cachemode
- diskeph.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- _get_eph_disk(eph))
diskeph.target_dev = block_device.strip_dev(
eph['device_name'])
diskeph.target_bus = ephemeral_disk_bus
@@ -1618,13 +1563,8 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
swap = driver.block_device_info_get_swap(block_device_info)
if driver.swap_is_usable(swap):
- diskswap = config.LibvirtConfigGuestDisk()
- diskswap.disk_type = "file"
- diskswap.driver_format = driver_type
+ diskswap = disk_info('disk.swap')
diskswap.driver_cache = self.disk_cachemode
- diskswap.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- "disk.swap")
diskswap.target_dev = block_device.strip_dev(
swap['device_name'])
diskswap.target_bus = ephemeral_disk_bus
@@ -1632,13 +1572,8 @@ def get_guest_config(self, instance, network_info, image_meta, rescue=None,
elif (inst_type['swap'] > 0 and
not self._volume_in_mapping(swap_device,
block_device_info)):
- diskswap = config.LibvirtConfigGuestDisk()
- diskswap.disk_type = "file"
- diskswap.driver_format = driver_type
+ diskswap = disk_info('disk.swap')
diskswap.driver_cache = self.disk_cachemode
- diskswap.source_path = os.path.join(FLAGS.instances_path,
- instance['name'],
- "disk.swap")
diskswap.target_dev = swap_device
diskswap.target_bus = ephemeral_disk_bus
guest.add_device(diskswap)
View
255 nova/virt/libvirt/imagebackend.py
@@ -0,0 +1,255 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Grid Dynamics
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+import contextlib
+import os
+
+from nova import flags
+from nova.openstack.common import cfg
+from nova.openstack.common import excutils
+from nova import utils
+from nova.virt.disk import api as disk
+from nova.virt.libvirt import config
+from nova.virt.libvirt import utils as libvirt_utils
+
+__imagebackend_opts = [
+ cfg.StrOpt('libvirt_images_type',
+ default='default',
+ help='VM Images format. Acceptable values are: raw, qcow2, lvm,'
+ ' default. If default is specified,'
+ ' then use_cow_images flag is used instead of this one.'),
+ cfg.StrOpt('libvirt_images_volume_group',
+ default=None,
+ help='LVM Volume Group that is used for VM images, when you'
+ ' specify libvirt_images_type=lvm.'),
+ cfg.BoolOpt('libvirt_sparse_logical_volumes',
+ default=False,
+ help='Create sparse logical volumes (with virtualsize)'
+ ' if this flag is set to True.'),
+ ]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(__imagebackend_opts)
+
+
+class Image(object):
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def __init__(self, instance, name, suffix):
+ """Image initialization.
+
+ :instance: Instance name.
+ :name: Image name.
+ :suffix: Suffix for image name.
+ """
+ pass
+
+ @abc.abstractmethod
+ def create_image(self, prepare_template, base, size, *args, **kwargs):
+ """Create image from template.
+
+ Contains specific behavior for each image type.
+
+ :prepare_template: function, that creates template.
+ Should accept `target` argument.
+ :base: Template name
+ :size: Size of created image in bytes
+ """
+ pass
+
+ @abc.abstractmethod
+ def libvirt_info(self, device_type):
+ """Get `LibvirtConfigGuestDisk` filled for this image.
+
+ :device_type: Device type for this image.
+ """
+ pass
+
+ def cache(self, fn, fname, size=None, *args, **kwargs):
+ """Creates image from template.
+
+ Ensures that template and image not already exists.
+ Ensures that base directory exists.
+ Synchronizes on template fetching.
+
+ :fn: function, that creates template.
+ Should accept `target` argument.
+ :fname: Template name
+ :size: Size of created image in bytes (optional)
+ """
+ @utils.synchronized(fname)
+ def call_if_not_exists(target, *args, **kwargs):
+ if not os.path.exists(target):
+ fn(target=target, *args, **kwargs)
+
+ if not os.path.exists(self.path):
+ base_dir = os.path.join(FLAGS.instances_path, '_base')
+ if not os.path.exists(base_dir):
+ libvirt_utils.ensure_tree(base_dir)
+ base = os.path.join(base_dir, fname)
+
+ self.create_image(call_if_not_exists, base, size,
+ *args, **kwargs)
+
+
+class Raw(Image):
+ def __init__(self, instance, name, suffix):
+ if not suffix:
+ suffix = ''
+ self.path = os.path.join(FLAGS.instances_path,
+ instance, name + suffix)
+
+ def libvirt_info(self, device_type):
+ info = config.LibvirtConfigGuestDisk()
+ info.source_type = 'file'
+ info.source_device = device_type
+ info.driver_format = 'raw'
+ info.source_path = self.path
+ return info
+
+ def create_image(self, prepare_template, base, size, *args, **kwargs):
+ @utils.synchronized(base)
+ def copy_raw_image(base, target, size):
+ libvirt_utils.copy_image(base, target)
+ if size:
+ disk.extend(target, size)
+
+ generating = 'image_id' not in kwargs
+ if generating:
+ #Generating image in place
+ prepare_template(target=self.path, *args, **kwargs)
+ else:
+ prepare_template(target=base, *args, **kwargs)
+ with utils.remove_path_on_error(self.path):
+ copy_raw_image(base, self.path, size)
+
+
+class Qcow2(Raw):
+ def libvirt_info(self, device_type):
+ info = config.LibvirtConfigGuestDisk()
+ info.source_type = 'file'
+ info.source_device = device_type
+ info.driver_format = 'qcow2'
+ info.source_path = self.path
+ return info
+
+ def create_image(self, prepare_template, base, size, *args, **kwargs):
+ @utils.synchronized(base)
+ def copy_qcow2_image(base, target, size):
+ qcow2_base = base
+ if size:
+ size_gb = size / (1024 * 1024 * 1024)
+ qcow2_base += '_%d' % size_gb
+ if not os.path.exists(qcow2_base):
+ with utils.remove_path_on_error(qcow2_base):
+ libvirt_utils.copy_image(base, qcow2_base)
+ disk.extend(qcow2_base, size)
+ libvirt_utils.create_cow_image(qcow2_base, target)
+
+ prepare_template(target=base, *args, **kwargs)
+ with utils.remove_path_on_error(self.path):
+ copy_qcow2_image(base, self.path, size)
+
+
+class Lvm(Image):
+ @staticmethod
+ def escape(fname):
+ return fname.replace('_', '__')
+
+ def libvirt_info(self, device_type):
+ info = config.LibvirtConfigGuestDisk()
+ info.source_type = 'block'
+ info.source_device = device_type
+ info.driver_format = 'raw'
+ info.source_path = self.path
+ return info
+
+ def __init__(self, instance, name, suffix):
+ if not suffix:
+ suffix = ''
+ if not FLAGS.libvirt_images_volume_group:
+ raise RuntimeError(_('You should specify'
+ ' libvirt_images_volume_group'
+ ' flag to use LVM images.'))
+ self.vg = FLAGS.libvirt_images_volume_group
+ self.lv = '%s_%s' % (self.escape(instance),
+ self.escape(name + suffix))
+ self.path = os.path.join('/dev', self.vg, self.lv)
+ self.sparse = FLAGS.libvirt_sparse_logical_volumes
+
+ def create_image(self, prepare_template, base, size, *args, **kwargs):
+ @utils.synchronized(base)
+ def create_lvm_image(base, size):
+ base_size = disk.get_image_virtual_size(base)
+ resize = size > base_size
+ size = size if resize else base_size
+ libvirt_utils.create_lvm_image(self.vg, self.lv,
+ size, sparse=self.sparse)
+ cmd = ('dd', 'if=%s' % base, 'of=%s' % self.path, 'bs=4M')
+ utils.execute(*cmd, run_as_root=True)
+ if resize:
+ disk.resize2fs(self.path)
+
+ generated = 'ephemeral_size' in kwargs
+
+ #Generate images with specified size right on volume
+ if generated and size:
+ libvirt_utils.create_lvm_image(self.vg, self.lv,
+ size, sparse=self.sparse)
+ with self.remove_volume_on_error(self.path):
+ prepare_template(target=self.path, *args, **kwargs)
+ else:
+ prepare_template(target=base, *args, **kwargs)
+ with self.remove_volume_on_error(self.path):
+ create_lvm_image(base, size)
+
+ @contextlib.contextmanager
+ def remove_volume_on_error(self, path):
+ try:
+ yield
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ libvirt_utils.remove_logical_volumes(path)
+
+
+class Backend(object):
+ def __init__(self, use_cow):
+ self.BACKEND = {
+ 'raw': Raw,
+ 'qcow2': Qcow2,
+ 'lvm': Lvm,
+ 'default': Qcow2 if use_cow else Raw
+ }
+
+ def image(self, instance, name,
+ suffix=None, image_type=None):
+ """Constructs image for selected backend
+
+ :instance: Instance name.
+ :name: Image name.
+ :suffix: Suffix for image name (optional).
+ :image_type: Image type.
+ Optional, is FLAGS.libvirt_images_type by default.
+ """
+ if not image_type:
+ image_type = FLAGS.libvirt_images_type
+ image = self.BACKEND.get(image_type)
+ if not image:
+ raise RuntimeError(_('Unknown image_type=%s') % image_type)
+ return image(instance, name, suffix)
View
55 nova/virt/libvirt/utils.py
@@ -90,6 +90,61 @@ def create_cow_image(backing_file, path):
'backing_file=%s' % backing_file, path)
+def create_lvm_image(vg, lv, size, sparse=False):
+ """Create LVM image.
+
+ Creates a LVM image with given size.
+
+ :param vg: existing volume group which should hold this image
+ :param lv: name for this image (logical volume)
+ :size: size of image in bytes
+ :sparse: create sparse logical volume
+ """
+ free_space = volume_group_free_space(vg)
+
+ def check_size(size):
+ if size > free_space:
+ raise RuntimeError(_('Insufficient Space on Volume Group %(vg)s.'
+ ' Only %(free_space)db available,'
+ ' but %(size)db required'
+ ' by volume %(lv)s.') % locals())
+
+ if sparse:
+ preallocated_space = 64 * 1024 * 1024
+ check_size(preallocated_space)
+ if free_space < size:
+ LOG.warning(_('Volume group %(vg)s will not be able'
+ ' to hold sparse volume %(lv)s.'
+ ' Virtual volume size is %(size)db,'
+ ' but free space on volume group is'
+ ' only %(free_space)db.') % locals())
+
+ cmd = ('lvcreate', '-L', '%db' % preallocated_space,
+ '--virtualsize', '%db' % size, '-n', lv, vg)
+ else:
+ check_size(size)
+ cmd = ('lvcreate', '-L', '%db' % size, '-n', lv, vg)
+ execute(*cmd, run_as_root=True, attempts=3)
+
+
+def volume_group_free_space(vg):
+ """Return available space on volume group in bytes.
+
+ :param vg: volume group name
+ """
+ out, err = execute('vgs', '--noheadings', '--nosuffix',
+ '--units', 'b', '-o', 'vg_free', vg,
+ run_as_root=True)
+ return int(out.strip())
+
+
+def remove_logical_volumes(*paths):
+ """Remove one or more logical volume."""
+ if paths:
+ lvremove = ('lvremove', '-f') + paths
+ execute(*lvremove, attempts=3, run_as_root=True)
+
+
def get_disk_size(path):
"""Get the (virtual) size of a disk image

0 comments on commit e0540df

Please sign in to comment.
Something went wrong with that request. Please try again.