diff options
-rw-r--r-- | nova/compute/api.py | 11 | ||||
-rw-r--r-- | nova/conductor/api.py | 10 | ||||
-rw-r--r-- | nova/conductor/manager.py | 1 | ||||
-rw-r--r-- | nova/conductor/rpcapi.py | 8 | ||||
-rw-r--r-- | nova/db/api.py | 11 | ||||
-rw-r--r-- | nova/db/sqlalchemy/api.py | 19 | ||||
-rw-r--r-- | nova/network/l3.py | 2 | ||||
-rw-r--r-- | nova/openstack/common/db/sqlalchemy/session.py | 10 | ||||
-rw-r--r-- | nova/openstack/common/importutils.py | 10 | ||||
-rw-r--r-- | nova/openstack/common/jsonutils.py | 36 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py | 5 | ||||
-rw-r--r-- | nova/tests/conductor/test_conductor.py | 10 | ||||
-rw-r--r-- | nova/tests/fake_libvirt_utils.py | 4 | ||||
-rw-r--r-- | nova/tests/network/test_manager.py | 38 | ||||
-rw-r--r-- | nova/tests/test_libvirt.py | 25 | ||||
-rw-r--r-- | nova/tests/test_virt_drivers.py | 2 | ||||
-rwxr-xr-x | nova/virt/libvirt/driver.py | 23 | ||||
-rwxr-xr-x | nova/virt/libvirt/imagebackend.py | 65 | ||||
-rw-r--r-- | nova/virt/libvirt/snapshots.py | 89 | ||||
-rwxr-xr-x | nova/virt/libvirt/utils.py | 6 |
20 files changed, 164 insertions, 221 deletions
diff --git a/nova/compute/api.py b/nova/compute/api.py index 0119f6602..43b87d6f2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1250,8 +1250,8 @@ class API(base.Base): # search_opts in get_all def get_active_by_window(self, context, begin, end=None, project_id=None): """Get instances that were continuously active over a window.""" - return self.db.instance_get_active_by_window(context, begin, end, - project_id) + return self.db.instance_get_active_by_window_joined(context, begin, + end, project_id) #NOTE(bcwaldon): this doesn't really belong in this class def get_instance_type(self, context, instance_type_id): @@ -2953,13 +2953,14 @@ class SecurityGroupAPI(base.Base): security_groups.add(security_group) # ..then we find the instances that are members of these groups.. - instances = set() + instances = {} for security_group in security_groups: for instance in security_group['instances']: - instances.add(instance) + if instance['uuid'] not in instances: + instances[instance['uuid']] = instance # ..then we send a request to refresh the rules for each instance. - for instance in instances: + for instance in instances.values(): if instance['host']: self.security_group_rpcapi.refresh_instance_security_rules( context, instance['host'], instance) diff --git a/nova/conductor/api.py b/nova/conductor/api.py index a30f8dffa..3b193fff8 100644 --- a/nova/conductor/api.py +++ b/nova/conductor/api.py @@ -93,11 +93,6 @@ class LocalAPI(object): return self._manager.instance_get_all_hung_in_rebooting(context, timeout) - def instance_get_active_by_window(self, context, begin, end=None, - project_id=None, host=None): - return self._manager.instance_get_active_by_window( - context, begin, end, project_id, host) - def instance_get_active_by_window_joined(self, context, begin, end=None, project_id=None, host=None): return self._manager.instance_get_active_by_window_joined( @@ -384,11 +379,6 @@ class API(object): return self.conductor_rpcapi.instance_get_all_hung_in_rebooting( context, timeout) - def instance_get_active_by_window(self, context, begin, end=None, - project_id=None, host=None): - return self.conductor_rpcapi.instance_get_active_by_window( - context, begin, end, project_id, host) - def instance_get_active_by_window_joined(self, context, begin, end=None, project_id=None, host=None): return self.conductor_rpcapi.instance_get_active_by_window_joined( diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index b53ab8fcc..0d2031a4a 100644 --- a/nova/conductor/manager.py +++ b/nova/conductor/manager.py @@ -239,6 +239,7 @@ class ConductorManager(manager.SchedulerDependentManager): def instance_get_active_by_window(self, context, begin, end=None, project_id=None, host=None): + # Unused, but cannot remove until major RPC version bump result = self.db.instance_get_active_by_window(context, begin, end, project_id, host) return jsonutils.to_primitive(result) diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py index 169da4ed7..f5ce0b4cb 100644 --- a/nova/conductor/rpcapi.py +++ b/nova/conductor/rpcapi.py @@ -74,6 +74,7 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): 1.39 - Added notify_usage_exists 1.40 - Added security_groups_trigger_handler and security_groups_trigger_members_refresh + Remove instance_get_active_by_window """ BASE_RPC_API_VERSION = '1.0' @@ -244,13 +245,6 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): timeout=timeout) return self.call(context, msg, version='1.15') - def instance_get_active_by_window(self, context, begin, end=None, - project_id=None, host=None): - msg = self.make_msg('instance_get_active_by_window', - begin=begin, end=end, project_id=project_id, - host=host) - return self.call(context, msg, version='1.15') - def instance_get_active_by_window_joined(self, context, begin, end=None, project_id=None, host=None): msg = self.make_msg('instance_get_active_by_window_joined', diff --git a/nova/db/api.py b/nova/db/api.py index 8d3f0fa4d..ffd153a46 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -606,17 +606,6 @@ def instance_get_all_by_filters(context, filters, sort_key='created_at', marker=marker) -def instance_get_active_by_window(context, begin, end=None, project_id=None, - host=None): - """Get instances active during a certain time window. - - Specifying a project_id will filter for a certain project. - Specifying a host will filter for instances on a given compute host. - """ - return IMPL.instance_get_active_by_window(context, begin, end, - project_id, host) - - def instance_get_active_by_window_joined(context, begin, end=None, project_id=None, host=None): """Get instances and joins active during a certain time window. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 874c7cf7b..d0a58e44f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1656,25 +1656,6 @@ def regex_filter(query, model, filters): return query -@require_context -def instance_get_active_by_window(context, begin, end=None, - project_id=None, host=None): - """Return instances that were active during window.""" - session = get_session() - query = session.query(models.Instance) - - query = query.filter(or_(models.Instance.terminated_at == None, - models.Instance.terminated_at > begin)) - if end: - query = query.filter(models.Instance.launched_at < end) - if project_id: - query = query.filter_by(project_id=project_id) - if host: - query = query.filter_by(host=host) - - return query.all() - - @require_admin_context def instance_get_active_by_window_joined(context, begin, end=None, project_id=None, host=None): diff --git a/nova/network/l3.py b/nova/network/l3.py index 14abf41eb..30a9f1ecd 100644 --- a/nova/network/l3.py +++ b/nova/network/l3.py @@ -101,9 +101,9 @@ class LinuxNetL3(L3Driver): def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, network=None): - linux_net.bind_floating_ip(floating_ip, l3_interface_id) linux_net.ensure_floating_forward(floating_ip, fixed_ip, l3_interface_id, network) + linux_net.bind_floating_ip(floating_ip, l3_interface_id) def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id, network=None): diff --git a/nova/openstack/common/db/sqlalchemy/session.py b/nova/openstack/common/db/sqlalchemy/session.py index 2125b006d..bc889fc36 100644 --- a/nova/openstack/common/db/sqlalchemy/session.py +++ b/nova/openstack/common/db/sqlalchemy/session.py @@ -246,12 +246,6 @@ import time from eventlet import db_pool from eventlet import greenthread -try: - import MySQLdb - from MySQLdb.constants import CLIENT as mysql_client_constants -except ImportError: - MySQLdb = None - mysql_client_constants = None from sqlalchemy.exc import DisconnectionError, OperationalError, IntegrityError import sqlalchemy.interfaces import sqlalchemy.orm @@ -259,10 +253,14 @@ from sqlalchemy.pool import NullPool, StaticPool from sqlalchemy.sql.expression import literal_column from nova.openstack.common import cfg +from nova.openstack.common import importutils from nova.openstack.common import log as logging from nova.openstack.common.gettextutils import _ from nova.openstack.common import timeutils +MySQLdb = importutils.try_import('MySQLdb') +if MySQLdb is not None: + from MySQLdb.constants import CLIENT as mysql_client_constants sql_opts = [ cfg.StrOpt('sql_connection', diff --git a/nova/openstack/common/importutils.py b/nova/openstack/common/importutils.py index f45372b4d..9dec764fb 100644 --- a/nova/openstack/common/importutils.py +++ b/nova/openstack/common/importutils.py @@ -29,7 +29,7 @@ def import_class(import_str): try: __import__(mod_str) return getattr(sys.modules[mod_str], class_str) - except (ValueError, AttributeError), exc: + except (ValueError, AttributeError): raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) @@ -57,3 +57,11 @@ def import_module(import_str): """Import a module.""" __import__(import_str) return sys.modules[import_str] + + +def try_import(import_str, default=None): + """Try to import a module and if it fails return default.""" + try: + return import_module(import_str) + except ImportError: + return default diff --git a/nova/openstack/common/jsonutils.py b/nova/openstack/common/jsonutils.py index 5a90d5c5e..290435450 100644 --- a/nova/openstack/common/jsonutils.py +++ b/nova/openstack/common/jsonutils.py @@ -34,6 +34,7 @@ This module provides a few things: import datetime +import functools import inspect import itertools import json @@ -42,7 +43,8 @@ import xmlrpclib from nova.openstack.common import timeutils -def to_primitive(value, convert_instances=False, level=0): +def to_primitive(value, convert_instances=False, convert_datetime=True, + level=0): """Convert a complex object into primitives. Handy for JSON serialization. We can optionally handle instances, @@ -84,6 +86,10 @@ def to_primitive(value, convert_instances=False, level=0): # The try block may not be necessary after the class check above, # but just in case ... try: + recursive = functools.partial(to_primitive, + convert_instances=convert_instances, + convert_datetime=convert_datetime, + level=level) # It's not clear why xmlrpclib created their own DateTime type, but # for our purposes, make it a datetime type which is explicitly # handled @@ -91,36 +97,22 @@ def to_primitive(value, convert_instances=False, level=0): value = datetime.datetime(*tuple(value.timetuple())[:6]) if isinstance(value, (list, tuple)): - o = [] - for v in value: - o.append(to_primitive(v, convert_instances=convert_instances, - level=level)) - return o + return [recursive(v) for v in value] elif isinstance(value, dict): - o = {} - for k, v in value.iteritems(): - o[k] = to_primitive(v, convert_instances=convert_instances, - level=level) - return o - elif isinstance(value, datetime.datetime): + return dict((k, recursive(v)) for k, v in value.iteritems()) + elif convert_datetime and isinstance(value, datetime.datetime): return timeutils.strtime(value) elif hasattr(value, 'iteritems'): - return to_primitive(dict(value.iteritems()), - convert_instances=convert_instances, - level=level + 1) + return recursive(dict(value.iteritems()), level=level + 1) elif hasattr(value, '__iter__'): - return to_primitive(list(value), - convert_instances=convert_instances, - level=level) + return recursive(list(value)) elif convert_instances and hasattr(value, '__dict__'): # Likely an instance of something. Watch for cycles. # Ignore class member vars. - return to_primitive(value.__dict__, - convert_instances=convert_instances, - level=level + 1) + return recursive(value.__dict__, level=level + 1) else: return value - except TypeError, e: + except TypeError: # Class objects are tricky since they may define something like # __iter__ defined but it isn't callable as list(). return unicode(value) diff --git a/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py index 13a4e9d61..440c97fbd 100644 --- a/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py +++ b/nova/tests/api/openstack/compute/contrib/test_simple_tenant_usage.py @@ -65,7 +65,8 @@ def get_fake_db_instance(start, end, instance_id, tenant_id): 'terminated_at': end} -def fake_instance_get_active_by_window(self, context, begin, end, project_id): +def fake_instance_get_active_by_window_joined(self, context, begin, end, + project_id): return [get_fake_db_instance(START, STOP, x, @@ -79,7 +80,7 @@ class SimpleTenantUsageTest(test.TestCase): self.stubs.Set(api.API, "get_instance_type", fake_instance_type_get) self.stubs.Set(api.API, "get_active_by_window", - fake_instance_get_active_by_window) + fake_instance_get_active_by_window_joined) self.admin_context = context.RequestContext('fakeadmin_0', 'faketenant_0', is_admin=True) diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py index 08598b756..bd73328e8 100644 --- a/nova/tests/conductor/test_conductor.py +++ b/nova/tests/conductor/test_conductor.py @@ -335,16 +335,6 @@ class _BaseTestCase(object): self.mox.ReplayAll() self.conductor.instance_get_all_hung_in_rebooting(self.context, 123) - def test_instance_get_active_by_window(self): - self.mox.StubOutWithMock(db, 'instance_get_active_by_window_joined') - db.instance_get_active_by_window(self.context, 'fake-begin', - 'fake-end', 'fake-proj', - 'fake-host') - self.mox.ReplayAll() - self.conductor.instance_get_active_by_window(self.context, - 'fake-begin', 'fake-end', - 'fake-proj', 'fake-host') - def test_instance_get_active_by_window_joined(self): self.mox.StubOutWithMock(db, 'instance_get_active_by_window_joined') db.instance_get_active_by_window_joined(self.context, 'fake-begin', diff --git a/nova/tests/fake_libvirt_utils.py b/nova/tests/fake_libvirt_utils.py index b3d842468..285a4b7e3 100644 --- a/nova/tests/fake_libvirt_utils.py +++ b/nova/tests/fake_libvirt_utils.py @@ -144,3 +144,7 @@ def fetch_image(context, target, image_id, user_id, project_id): def get_instance_path(instance): # TODO(mikal): we should really just call the real one here return os.path.join(CONF.instances_path, instance['name']) + + +def pick_disk_driver_name(is_block_dev=False): + return "qemu" diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py index 0dcd02f78..c0a434e6c 100644 --- a/nova/tests/network/test_manager.py +++ b/nova/tests/network/test_manager.py @@ -726,7 +726,7 @@ class VlanNetworkTestCase(test.TestCase): 'floating_ip_fixed_ip_associate', fake1) self.stubs.Set(self.network.db, 'floating_ip_disassociate', fake1) - self.stubs.Set(self.network.driver, 'bind_floating_ip', fake8) + self.stubs.Set(self.network.driver, 'ensure_floating_forward', fake8) self.assertRaises(exception.NoFloatingIpInterface, self.network._associate_floating_ip, ctxt, @@ -776,6 +776,42 @@ class VlanNetworkTestCase(test.TestCase): mox.IgnoreArg()) self.assertTrue(self.local) + def test_add_floating_ip_nat_before_bind(self): + # Tried to verify order with documented mox record/verify + # functionality, but it doesn't seem to work since I can't make it + # fail. I'm using stubs and a flag for now, but if this mox feature + # can be made to work, it would be a better way to test this. + # + # self.mox.StubOutWithMock(self.network.driver, + # 'ensure_floating_forward') + # self.mox.StubOutWithMock(self.network.driver, 'bind_floating_ip') + # + # self.network.driver.ensure_floating_forward(mox.IgnoreArg(), + # mox.IgnoreArg(), + # mox.IgnoreArg(), + # mox.IgnoreArg()) + # self.network.driver.bind_floating_ip(mox.IgnoreArg(), + # mox.IgnoreArg()) + # self.mox.ReplayAll() + + nat_called = [False] + + def fake_nat(*args, **kwargs): + nat_called[0] = True + + def fake_bind(*args, **kwargs): + self.assertTrue(nat_called[0]) + + self.stubs.Set(self.network.driver, + 'ensure_floating_forward', + fake_nat) + self.stubs.Set(self.network.driver, 'bind_floating_ip', fake_bind) + + self.network.l3driver.add_floating_ip('fakefloat', + 'fakefixed', + 'fakeiface', + 'fakenet') + def test_floating_ip_init_host(self): def get_all_by_host(_context, _host): diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 4b9a4eb7c..c93bb0168 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -273,7 +273,7 @@ class LibvirtConnTestCase(test.TestCase): 'nova.virt.libvirt.driver.libvirt_utils', fake_libvirt_utils)) self.useFixture(fixtures.MonkeyPatch( - 'nova.virt.libvirt.snapshots.libvirt_utils', + 'nova.virt.libvirt.imagebackend.libvirt_utils', fake_libvirt_utils)) def fake_extend(image, size): @@ -433,6 +433,29 @@ class LibvirtConnTestCase(test.TestCase): self.assertEquals(type(cfg.devices[7]), vconfig.LibvirtConfigGuestGraphics) + def test_get_guest_config_bug_1118829(self): + self.flags(libvirt_type='uml') + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = db.instance_create(self.context, self.test_instance) + + disk_info = {'disk_bus': 'virtio', + 'cdrom_bus': 'ide', + 'mapping': {u'vda': {'bus': 'virtio', + 'type': 'disk', + 'dev': u'vda'}, + 'root': {'bus': 'virtio', + 'type': 'disk', + 'dev': 'vda'}}} + + # NOTE(jdg): For this specific test leave this blank + # This will exercise the failed code path still, + # and won't require fakes and stubs of the iscsi discovery + block_device_info = {} + cfg = conn.get_guest_config(instance_ref, [], None, disk_info, + None, block_device_info) + instance_ref = db.instance_get(self.context, instance_ref['id']) + self.assertEquals(instance_ref['root_device_name'], '/dev/vda') + def test_get_guest_config_with_root_device_name(self): self.flags(libvirt_type='uml') conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 67452bc24..a94fdc3c5 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -84,7 +84,7 @@ class _FakeDriverBackendTestCase(object): 'nova.virt.libvirt.driver.libvirt_utils', fake_libvirt_utils)) self.useFixture(fixtures.MonkeyPatch( - 'nova.virt.libvirt.snapshots.libvirt_utils', + 'nova.virt.libvirt.imagebackend.libvirt_utils', fake_libvirt_utils)) self.useFixture(fixtures.MonkeyPatch( 'nova.virt.libvirt.firewall.libvirt', diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index cf926a14c..485f661e5 100755 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -116,8 +116,8 @@ libvirt_opts = [ cfg.IntOpt('libvirt_inject_partition', default=1, help='The partition to inject to : ' - '-1 => inspect (libguestfs only), 0 => not partitioned, ' - '>0 => partition number'), + '-2 => disable, -1 => inspect (libguestfs only), ' + '0 => not partitioned, >0 => partition number'), cfg.BoolOpt('use_usb_tablet', default=True, help='Sync virtual and real mouse cursors in Windows VMs'), @@ -827,7 +827,8 @@ class LibvirtDriver(driver.ComputeDriver): # can be validated. if self.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION, MIN_QEMU_LIVESNAPSHOT_VERSION, - REQ_HYPERVISOR_LIVESNAPSHOT): + REQ_HYPERVISOR_LIVESNAPSHOT) \ + and not source_format == "lvm": live_snapshot = True else: live_snapshot = False @@ -843,15 +844,17 @@ class LibvirtDriver(driver.ComputeDriver): if state == power_state.RUNNING or state == power_state.PAUSED: virt_dom.managedSave(0) + snapshot_backend = self.image_backend.snapshot(disk_path, + snapshot_name, + image_type=source_format) + if live_snapshot: LOG.info(_("Beginning live snapshot process"), instance=instance) else: LOG.info(_("Beginning cold snapshot process"), instance=instance) - snapshot = self.image_backend.snapshot(disk_path, snapshot_name, - image_type=source_format) - snapshot.create() + snapshot_backend.snapshot_create() update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) snapshot_directory = CONF.libvirt_snapshots_directory @@ -866,10 +869,10 @@ class LibvirtDriver(driver.ComputeDriver): self._live_snapshot(virt_dom, disk_path, out_path, image_format) else: - snapshot.extract(out_path, image_format) + snapshot_backend.snapshot_extract(out_path, image_format) finally: if not live_snapshot: - snapshot.delete() + snapshot_backend.snapshot_delete() # NOTE(dkang): because previous managedSave is not called # for LXC, _create_domain must not be called. if CONF.libvirt_type != 'lxc' and not live_snapshot: @@ -1534,7 +1537,7 @@ class LibvirtDriver(driver.ComputeDriver): raise # File injection - else: + elif CONF.libvirt_inject_partition != -2: target_partition = None if not instance['kernel_id']: target_partition = CONF.libvirt_inject_partition @@ -1798,7 +1801,7 @@ class LibvirtDriver(driver.ComputeDriver): # for nova.api.ec2.cloud.CloudController.get_metadata() self.virtapi.instance_update( nova_context.get_admin_context(), instance['uuid'], - {'root_device_name': '/dev/' + disk_mapping['disk']['dev']}) + {'root_device_name': root_device_name}) guest.os_type = vm_mode.get_from_instance(instance) diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py index 0d5e8cb66..74148a866 100755 --- a/nova/virt/libvirt/imagebackend.py +++ b/nova/virt/libvirt/imagebackend.py @@ -25,8 +25,8 @@ from nova.openstack.common import fileutils from nova.openstack.common import lockutils from nova import utils from nova.virt.disk import api as disk +from nova.virt import images from nova.virt.libvirt import config as vconfig -from nova.virt.libvirt import snapshots from nova.virt.libvirt import utils as libvirt_utils __imagebackend_opts = [ @@ -129,22 +129,25 @@ class Image(object): self.create_image(call_if_not_exists, base, size, *args, **kwargs) - @abc.abstractmethod - def snapshot(self, name): - """Create snapshot object for this image + def snapshot_create(self): + raise NotImplementedError - :name: snapshot name - """ - pass + def snapshot_extract(self, target, out_format): + raise NotImplementedError + + def snapshot_delete(self): + raise NotImplementedError class Raw(Image): - def __init__(self, instance=None, name=None, path=None): + def __init__(self, instance=None, disk_name=None, path=None, + snapshot_name=None): super(Raw, self).__init__("file", "raw", is_block_dev=False) self.path = (path or os.path.join(libvirt_utils.get_instance_path(instance), - name)) + disk_name)) + self.snapshot_name = snapshot_name def create_image(self, prepare_template, base, size, *args, **kwargs): @lockutils.synchronized(base, 'nova-', external=True, @@ -164,17 +167,25 @@ class Raw(Image): with utils.remove_path_on_error(self.path): copy_raw_image(base, self.path, size) - def snapshot(self, name): - return snapshots.RawSnapshot(self.path, name) + def snapshot_create(self): + pass + + def snapshot_extract(self, target, out_format): + images.convert_image(self.path, target, out_format) + + def snapshot_delete(self): + pass class Qcow2(Image): - def __init__(self, instance=None, name=None, path=None): + def __init__(self, instance=None, disk_name=None, path=None, + snapshot_name=None): super(Qcow2, self).__init__("file", "qcow2", is_block_dev=False) self.path = (path or os.path.join(libvirt_utils.get_instance_path(instance), - name)) + disk_name)) + self.snapshot_name = snapshot_name def create_image(self, prepare_template, base, size, *args, **kwargs): @lockutils.synchronized(base, 'nova-', external=True, @@ -190,8 +201,16 @@ class Qcow2(Image): with utils.remove_path_on_error(self.path): copy_qcow2_image(base, self.path, size) - def snapshot(self, name): - return snapshots.Qcow2Snapshot(self.path, name) + def snapshot_create(self): + libvirt_utils.create_snapshot(self.path, self.snapshot_name) + + def snapshot_extract(self, target, out_format): + libvirt_utils.extract_snapshot(self.path, 'qcow2', + self.snapshot_name, target, + out_format) + + def snapshot_delete(self): + libvirt_utils.delete_snapshot(self.path, self.snapshot_name) class Lvm(Image): @@ -199,7 +218,8 @@ class Lvm(Image): def escape(filename): return filename.replace('_', '__') - def __init__(self, instance=None, name=None, path=None): + def __init__(self, instance=None, disk_name=None, path=None, + snapshot_name=None): super(Lvm, self).__init__("block", "raw", is_block_dev=True) if path: @@ -214,7 +234,7 @@ class Lvm(Image): ' flag to use LVM images.')) self.vg = CONF.libvirt_images_volume_group self.lv = '%s_%s' % (self.escape(instance['name']), - self.escape(name)) + self.escape(disk_name)) self.path = os.path.join('/dev', self.vg, self.lv) self.sparse = CONF.libvirt_sparse_logical_volumes @@ -254,9 +274,6 @@ class Lvm(Image): with excutils.save_and_reraise_exception(): libvirt_utils.remove_logical_volumes(path) - def snapshot(self, name): - return snapshots.LvmSnapshot(self.path, name) - class Backend(object): def __init__(self, use_cow): @@ -275,7 +292,7 @@ class Backend(object): raise RuntimeError(_('Unknown image_type=%s') % image_type) return image - def image(self, instance, name, image_type=None): + def image(self, instance, disk_name, image_type=None): """Constructs image for selected backend :instance: Instance name. @@ -284,9 +301,9 @@ class Backend(object): Optional, is CONF.libvirt_images_type by default. """ backend = self.backend(image_type) - return backend(instance=instance, name=name) + return backend(instance=instance, disk_name=disk_name) - def snapshot(self, path, snapshot_name, image_type=None): + def snapshot(self, disk_path, snapshot_name, image_type=None): """Returns snapshot for given image :path: path to image @@ -294,4 +311,4 @@ class Backend(object): :image_type: type of image """ backend = self.backend(image_type) - return backend(path=path).snapshot(snapshot_name) + return backend(path=disk_path, snapshot_name=snapshot_name) diff --git a/nova/virt/libvirt/snapshots.py b/nova/virt/libvirt/snapshots.py deleted file mode 100644 index c85550eae..000000000 --- a/nova/virt/libvirt/snapshots.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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 - -from nova.virt import images -from nova.virt.libvirt import utils as libvirt_utils - - -class Snapshot(object): - @abc.abstractmethod - def create(self): - """Create new snapshot.""" - pass - - @abc.abstractmethod - def extract(self, target, out_format): - """Extract snapshot content to file - - :target: path to extraction - :out_format: format of extraction (raw, qcow2, ...) - """ - pass - - @abc.abstractmethod - def delete(self): - """Delete snapshot.""" - pass - - -class RawSnapshot(object): - def __init__(self, path, name): - self.path = path - self.name = name - - def create(self): - pass - - def extract(self, target, out_format): - images.convert_image(self.path, target, out_format) - - def delete(self): - pass - - -class Qcow2Snapshot(object): - def __init__(self, path, name): - self.path = path - self.name = name - - def create(self): - libvirt_utils.create_snapshot(self.path, self.name) - - def extract(self, target, out_format): - libvirt_utils.extract_snapshot(self.path, 'qcow2', - self.name, target, - out_format) - - def delete(self): - libvirt_utils.delete_snapshot(self.path, self.name) - - -class LvmSnapshot(object): - def __init__(self, path, name): - self.path = path - self.name = name - - def create(self): - raise NotImplementedError(_("LVM snapshots not implemented")) - - def extract(self, target, out_format): - raise NotImplementedError(_("LVM snapshots not implemented")) - - def delete(self): - raise NotImplementedError(_("LVM snapshots not implemented")) diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index 1830cac33..b8e0cafec 100755 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -237,6 +237,7 @@ def clear_logical_volume(path): vol_size = logical_volume_size(path) bs = 1024 * 1024 direct_flags = ('oflag=direct',) + sync_flags = () remaining_bytes = vol_size # The loop caters for versions of dd that @@ -248,11 +249,14 @@ def clear_logical_volume(path): 'if=/dev/zero', 'of=%s' % path, 'seek=%s' % seek_blocks, 'count=%s' % zero_blocks) zero_cmd += direct_flags + zero_cmd += sync_flags if zero_blocks: utils.execute(*zero_cmd, run_as_root=True) remaining_bytes %= bs bs /= 1024 # Limit to 3 iterations - direct_flags = () # Only use O_DIRECT with initial block size + # Use O_DIRECT with initial block size and fdatasync otherwise + direct_flags = () + sync_flags = ('conv=fdatasync',) def remove_logical_volumes(*paths): |