summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/tests/hyperv/db_fakes.py7
-rw-r--r--nova/tests/test_hypervapi.py30
-rw-r--r--nova/virt/hyperv/imagecache.py75
-rw-r--r--nova/virt/hyperv/migrationops.py29
-rw-r--r--nova/virt/hyperv/pathutils.py15
-rw-r--r--nova/virt/hyperv/vhdutils.py7
-rw-r--r--nova/virt/hyperv/vmops.py62
7 files changed, 170 insertions, 55 deletions
diff --git a/nova/tests/hyperv/db_fakes.py b/nova/tests/hyperv/db_fakes.py
index 7169edf8d..5152bd035 100644
--- a/nova/tests/hyperv/db_fakes.py
+++ b/nova/tests/hyperv/db_fakes.py
@@ -41,7 +41,7 @@ def get_fake_instance_data(name, project_id, user_id):
{'name': 'm1.tiny',
'memory_mb': 512,
'vcpus': 1,
- 'root_gb': 0,
+ 'root_gb': 1024,
'flavorid': 1,
'rxtx_factor': 1}
}
@@ -69,8 +69,6 @@ def get_fake_volume_info_data(target_portal, volume_id):
'target_portal': target_portal,
'target_lun': 1,
'auth_method': 'CHAP',
- 'auth_method': 'fake',
- 'auth_method': 'fake',
}
}
@@ -121,6 +119,9 @@ def stub_out_db_instance_api(stubs):
def __getitem__(self, key):
return self.get(key)
+ def __setitem__(self, key, value):
+ self.values[key] = value
+
def __str__(self):
return str(self.values)
diff --git a/nova/tests/test_hypervapi.py b/nova/tests/test_hypervapi.py
index aaceff8ec..5dc77b911 100644
--- a/nova/tests/test_hypervapi.py
+++ b/nova/tests/test_hypervapi.py
@@ -154,6 +154,8 @@ class HyperVAPITestCase(test.TestCase):
self._mox.StubOutWithMock(vhdutils.VHDUtils, 'merge_vhd')
self._mox.StubOutWithMock(vhdutils.VHDUtils, 'get_vhd_parent_path')
self._mox.StubOutWithMock(vhdutils.VHDUtils, 'get_vhd_info')
+ self._mox.StubOutWithMock(vhdutils.VHDUtils, 'resize_vhd')
+ self._mox.StubOutWithMock(vhdutils.VHDUtils, 'validate_vhd')
self._mox.StubOutWithMock(hostutils.HostUtils, 'get_cpus_info')
self._mox.StubOutWithMock(hostutils.HostUtils,
@@ -567,6 +569,8 @@ class HyperVAPITestCase(test.TestCase):
self.flags(use_cow_images=cow)
instance_data = self._get_instance_data()
+ instance = db.instance_create(self._context, instance_data)
+ instance['system_metadata'] = {}
network_info = fake_network.fake_get_instance_nw_info(self.stubs,
spectacular=True)
@@ -579,10 +583,14 @@ class HyperVAPITestCase(test.TestCase):
None)
m.AndReturn(False)
- vhdutils.VHDUtils.get_vhd_info(mox.Func(self._check_img_path))
+ m = vhdutils.VHDUtils.get_vhd_info(mox.Func(self._check_img_path))
+ m.AndReturn({'MaxInternalSize': 1024})
+
+ fake.PathUtils.copyfile(mox.IsA(str), mox.IsA(str))
+ vhdutils.VHDUtils.resize_vhd(mox.IsA(str), mox.IsA(object))
self._mox.ReplayAll()
- self._conn.pre_live_migration(self._context, instance_data,
+ self._conn.pre_live_migration(self._context, instance,
None, network_info)
self._mox.VerifyAll()
@@ -697,6 +705,7 @@ class HyperVAPITestCase(test.TestCase):
self._instance_data = self._get_instance_data()
instance = db.instance_create(self._context, self._instance_data)
+ instance['system_metadata'] = {}
image = db_fakes.get_fake_image_data(self._project_id, self._user_id)
@@ -763,12 +772,16 @@ class HyperVAPITestCase(test.TestCase):
m.AndReturn(boot_from_volume)
if not boot_from_volume:
- vhdutils.VHDUtils.get_vhd_info(mox.Func(self._check_img_path))
+ m = vhdutils.VHDUtils.get_vhd_info(mox.Func(self._check_img_path))
+ m.AndReturn({'MaxInternalSize': 1024})
if cow:
- vhdutils.VHDUtils.create_differencing_vhd(
- mox.IsA(str), mox.Func(self._check_img_path))
+ fake.PathUtils.copyfile(mox.IsA(str), mox.IsA(str))
+ vhdutils.VHDUtils.resize_vhd(mox.IsA(str), mox.IsA(object))
+ vhdutils.VHDUtils.create_differencing_vhd(mox.IsA(str),
+ mox.IsA(str))
else:
+ vhdutils.VHDUtils.resize_vhd(mox.IsA(str), mox.IsA(object))
fake.PathUtils.copyfile(mox.IsA(str), mox.IsA(str))
self._setup_create_instance_mocks(setup_vif_mocks_func,
@@ -1009,6 +1022,7 @@ class HyperVAPITestCase(test.TestCase):
def test_finish_migration(self):
self._instance_data = self._get_instance_data()
instance = db.instance_create(self._context, self._instance_data)
+ instance['system_metadata'] = {}
network_info = fake_network.fake_get_instance_nw_info(
self.stubs, spectacular=True)
@@ -1032,6 +1046,12 @@ class HyperVAPITestCase(test.TestCase):
vhdutils.VHDUtils.reconnect_parent_vhd(mox.IsA(str), mox.IsA(str))
+ m = vhdutils.VHDUtils.get_vhd_info(mox.IsA(str))
+ m.AndReturn({'MaxInternalSize': 1024})
+
+ m = fake.PathUtils.exists(mox.IsA(str))
+ m.AndReturn(True)
+
self._set_vm_name(instance['name'])
self._setup_create_instance_mocks(None, False)
diff --git a/nova/virt/hyperv/imagecache.py b/nova/virt/hyperv/imagecache.py
index 93ea32b25..5d68bab61 100644
--- a/nova/virt/hyperv/imagecache.py
+++ b/nova/virt/hyperv/imagecache.py
@@ -19,15 +19,21 @@ Image caching and management.
"""
import os
+from nova.compute import instance_types
+from nova.openstack.common import excutils
from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import vhdutils
from nova.virt.hyperv import vmutils
from nova.virt import images
+from oslo.config import cfg
LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+CONF.import_opt('use_cow_images', 'nova.virt.driver')
+
class ImageCache(object):
def __init__(self):
@@ -36,12 +42,59 @@ class ImageCache(object):
def _validate_vhd_image(self, vhd_path):
try:
- self._vhdutils.get_vhd_info(vhd_path)
+ self._vhdutils.validate_vhd(vhd_path)
except Exception as ex:
LOG.exception(ex)
raise vmutils.HyperVException(_('The image is not a valid VHD: %s')
% vhd_path)
+ def _get_root_vhd_size_gb(self, instance):
+ try:
+ # In case of resizes we need the old root disk size
+ old_instance_type = instance_types.extract_instance_type(
+ instance, prefix='old_')
+ return old_instance_type['root_gb']
+ except KeyError:
+ return instance['root_gb']
+
+ def _resize_and_cache_vhd(self, instance, vhd_path):
+ vhd_info = self._vhdutils.get_vhd_info(vhd_path)
+ vhd_size = vhd_info['MaxInternalSize']
+
+ root_vhd_size_gb = self._get_root_vhd_size_gb(instance)
+ root_vhd_size = root_vhd_size_gb * 1024 ** 3
+
+ if root_vhd_size < vhd_size:
+ raise vmutils.HyperVException(_("Cannot resize the image to a "
+ "size smaller than the VHD max. "
+ "internal size: %(vhd_size)s. "
+ "Requested disk size: "
+ "%(root_vhd_size)s") % locals())
+ if root_vhd_size > vhd_size:
+ path_parts = os.path.splitext(vhd_path)
+ resized_vhd_path = '%s_%s%s' % (path_parts[0],
+ root_vhd_size_gb,
+ path_parts[1])
+
+ @lockutils.synchronized(resized_vhd_path, 'nova-')
+ def copy_and_resize_vhd():
+ if not self._pathutils.exists(resized_vhd_path):
+ try:
+ LOG.debug(_("Copying VHD %(vhd_path)s to "
+ "%(resized_vhd_path)s") % locals())
+ self._pathutils.copyfile(vhd_path, resized_vhd_path)
+ LOG.debug(_("Resizing VHD %(resized_vhd_path)s to new "
+ "size %(root_vhd_size)s") % locals())
+ self._vhdutils.resize_vhd(resized_vhd_path,
+ root_vhd_size)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ if self._pathutils.exists(resized_vhd_path):
+ self._pathutils.remove(resized_vhd_path)
+
+ copy_and_resize_vhd()
+ return resized_vhd_path
+
def get_cached_image(self, context, instance):
image_id = instance['image_ref']
@@ -51,10 +104,22 @@ class ImageCache(object):
@lockutils.synchronized(vhd_path, 'nova-')
def fetch_image_if_not_existing():
if not self._pathutils.exists(vhd_path):
- images.fetch(context, image_id, vhd_path,
- instance['user_id'],
- instance['project_id'])
- self._validate_vhd_image(vhd_path)
+ try:
+ images.fetch(context, image_id, vhd_path,
+ instance['user_id'],
+ instance['project_id'])
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ if self._pathutils.exists(vhd_path):
+ self._pathutils.remove(vhd_path)
fetch_image_if_not_existing()
+
+ if CONF.use_cow_images:
+ # Resize the base VHD image as it's not possible to resize a
+ # differencing VHD.
+ resized_vhd_path = self._resize_and_cache_vhd(instance, vhd_path)
+ if resized_vhd_path:
+ return resized_vhd_path
+
return vhd_path
diff --git a/nova/virt/hyperv/migrationops.py b/nova/virt/hyperv/migrationops.py
index 8d5b5e90c..07bf453e5 100644
--- a/nova/virt/hyperv/migrationops.py
+++ b/nova/virt/hyperv/migrationops.py
@@ -137,16 +137,15 @@ class MigrationOps(object):
self._revert_migration_files(instance_name)
if self._volumeops.ebs_root_in_block_devices(block_device_info):
- boot_vhd_path = None
+ root_vhd_path = None
else:
- boot_vhd_path = self._pathutils.get_vhd_path(instance_name)
+ root_vhd_path = self._pathutils.get_vhd_path(instance_name)
self._vmops.create_instance(instance, network_info, block_device_info,
- boot_vhd_path)
+ root_vhd_path)
self._vmops.power_on(instance)
def _merge_base_vhd(self, diff_vhd_path, base_vhd_path):
-
base_vhd_copy_path = os.path.join(os.path.dirname(diff_vhd_path),
os.path.basename(base_vhd_path))
try:
@@ -183,10 +182,6 @@ class MigrationOps(object):
def _check_base_disk(self, context, instance, diff_vhd_path,
src_base_disk_path):
- base_disk_file_name = os.path.basename(src_base_disk_path)
- if os.path.splitext(base_disk_file_name)[0] != instance["image_ref"]:
- raise vmutils.HyperVException(_("Unexpected base VHD path"))
-
base_vhd_path = self._imagecache.get_cached_image(context, instance)
# If the location of the base host differs between source
@@ -206,28 +201,28 @@ class MigrationOps(object):
instance_name = instance['name']
if self._volumeops.ebs_root_in_block_devices(block_device_info):
- boot_vhd_path = None
+ root_vhd_path = None
else:
- boot_vhd_path = self._pathutils.get_vhd_path(instance_name)
- if not self._pathutils.exists(boot_vhd_path):
+ root_vhd_path = self._pathutils.get_vhd_path(instance_name)
+ if not self._pathutils.exists(root_vhd_path):
raise vmutils.HyperVException(_("Cannot find boot VHD "
- "file: %s") % boot_vhd_path)
+ "file: %s") % root_vhd_path)
- vhd_info = self._vhdutils.get_vhd_info(boot_vhd_path)
+ vhd_info = self._vhdutils.get_vhd_info(root_vhd_path)
src_base_disk_path = vhd_info.get("ParentPath")
if src_base_disk_path:
- self._check_base_disk(context, instance, boot_vhd_path,
+ self._check_base_disk(context, instance, root_vhd_path,
src_base_disk_path)
if resize_instance:
curr_size = vhd_info['MaxInternalSize']
- new_size = instance['root_gb'] * 1024 * 1024 * 1024
+ new_size = instance['root_gb'] * 1024 ** 3
if new_size < curr_size:
raise vmutils.HyperVException(_("Cannot resize a VHD to a "
"smaller size"))
elif new_size > curr_size:
- self._resize_vhd(boot_vhd_path, new_size)
+ self._resize_vhd(root_vhd_path, new_size)
self._vmops.create_instance(instance, network_info, block_device_info,
- boot_vhd_path)
+ root_vhd_path)
self._vmops.power_on(instance)
diff --git a/nova/virt/hyperv/pathutils.py b/nova/virt/hyperv/pathutils.py
index 1297cd1ed..7ee4d06ea 100644
--- a/nova/virt/hyperv/pathutils.py
+++ b/nova/virt/hyperv/pathutils.py
@@ -18,9 +18,9 @@
import os
import shutil
-from oslo.config import cfg
-
+from eventlet.green import subprocess
from nova.openstack.common import log as logging
+from oslo.config import cfg
LOG = logging.getLogger(__name__)
@@ -58,10 +58,17 @@ class PathUtils(object):
os.rename(src, dest)
def copyfile(self, src, dest):
- shutil.copyfile(src, dest)
+ self.copy(src, dest)
def copy(self, src, dest):
- shutil.copy(src, dest)
+ # With large files this is 2x-3x faster than shutil.copy(src, dest),
+ # especially when copying to a UNC target.
+ # shutil.copyfileobj(...) with a proper buffer is better than
+ # shutil.copy(...) but still 20% slower than a shell copy.
+ # It can be replaced with Win32 API calls to avoid the process
+ # spawning overhead.
+ if subprocess.call(['cmd.exe', '/C', 'copy', '/Y', src, dest]):
+ raise IOError(_('The file copy from %(src)s to %(dest)s failed'))
def rmtree(self, path):
shutil.rmtree(path)
diff --git a/nova/virt/hyperv/vhdutils.py b/nova/virt/hyperv/vhdutils.py
index 1e529807d..c21799051 100644
--- a/nova/virt/hyperv/vhdutils.py
+++ b/nova/virt/hyperv/vhdutils.py
@@ -31,6 +31,13 @@ class VHDUtils(object):
if sys.platform == 'win32':
self._conn = wmi.WMI(moniker='//./root/virtualization')
+ def validate_vhd(self, vhd_path):
+ image_man_svc = self._conn.Msvm_ImageManagementService()[0]
+
+ (job_path, ret_val) = image_man_svc.ValidateVirtualHardDisk(
+ Path=vhd_path)
+ self._vmutils.check_ret_val(ret_val, job_path)
+
def create_differencing_vhd(self, path, parent_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py
index f488e993f..48edae7fd 100644
--- a/nova/virt/hyperv/vmops.py
+++ b/nova/virt/hyperv/vmops.py
@@ -111,19 +111,39 @@ class VMOps(object):
'num_cpu': info['NumberOfProcessors'],
'cpu_time': info['UpTime']}
- def _create_boot_vhd(self, context, instance):
+ def _create_root_vhd(self, context, instance):
base_vhd_path = self._imagecache.get_cached_image(context, instance)
- boot_vhd_path = self._pathutils.get_vhd_path(instance['name'])
-
- if CONF.use_cow_images:
- LOG.debug(_("Creating differencing VHD. Parent: "
- "%(base_vhd_path)s, Target: %(boot_vhd_path)s")
- % locals())
- self._vhdutils.create_differencing_vhd(boot_vhd_path,
- base_vhd_path)
- else:
- self._pathutils.copyfile(base_vhd_path, boot_vhd_path)
- return boot_vhd_path
+ root_vhd_path = self._pathutils.get_vhd_path(instance['name'])
+
+ try:
+ if CONF.use_cow_images:
+ LOG.debug(_("Creating differencing VHD. Parent: "
+ "%(base_vhd_path)s, Target: %(root_vhd_path)s")
+ % locals())
+ self._vhdutils.create_differencing_vhd(root_vhd_path,
+ base_vhd_path)
+ else:
+ LOG.debug(_("Copying VHD image %(base_vhd_path)s to target: "
+ "%(root_vhd_path)s") % locals())
+ self._pathutils.copyfile(base_vhd_path, root_vhd_path)
+
+ base_vhd_info = self._vhdutils.get_vhd_info(base_vhd_path)
+ base_vhd_size = base_vhd_info['MaxInternalSize']
+ root_vhd_size = instance['root_gb'] * 1024 ** 3
+
+ if root_vhd_size < base_vhd_size:
+ raise vmutils.HyperVException(_("Cannot resize a VHD to a "
+ "smaller size"))
+ elif root_vhd_size > base_vhd_size:
+ LOG.debug(_("Resizing VHD %(root_vhd_path)s to new "
+ "size %(root_vhd_size)s") % locals())
+ self._vhdutils.resize_vhd(root_vhd_path, root_vhd_size)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ if self._pathutils.exists(root_vhd_path):
+ self._pathutils.remove(root_vhd_path)
+
+ return root_vhd_path
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info, block_device_info=None):
@@ -135,13 +155,13 @@ class VMOps(object):
raise exception.InstanceExists(name=instance_name)
if self._volumeops.ebs_root_in_block_devices(block_device_info):
- boot_vhd_path = None
+ root_vhd_path = None
else:
- boot_vhd_path = self._create_boot_vhd(context, instance)
+ root_vhd_path = self._create_root_vhd(context, instance)
try:
self.create_instance(instance, network_info, block_device_info,
- boot_vhd_path)
+ root_vhd_path)
if configdrive.required_by(instance):
self._create_config_drive(instance, injected_files,
@@ -154,7 +174,7 @@ class VMOps(object):
raise vmutils.HyperVException(_('Spawn instance failed'))
def create_instance(self, instance, network_info,
- block_device_info, boot_vhd_path):
+ block_device_info, root_vhd_path):
instance_name = instance['name']
self._vmutils.create_vm(instance_name,
@@ -162,9 +182,9 @@ class VMOps(object):
instance['vcpus'],
CONF.limit_cpu_features)
- if boot_vhd_path:
+ if root_vhd_path:
self._vmutils.attach_ide_drive(instance_name,
- boot_vhd_path,
+ root_vhd_path,
0,
0,
constants.IDE_DISK)
@@ -173,7 +193,7 @@ class VMOps(object):
self._volumeops.attach_volumes(block_device_info,
instance_name,
- boot_vhd_path is None)
+ root_vhd_path is None)
for vif in network_info:
LOG.debug(_('Creating nic for instance: %s'), instance_name)
@@ -238,8 +258,8 @@ class VMOps(object):
def _delete_disk_files(self, instance_name):
self._pathutils.get_instance_dir(instance_name,
- create_dir=False,
- remove_dir=True)
+ create_dir=False,
+ remove_dir=True)
def destroy(self, instance, network_info=None, block_device_info=None,
destroy_disks=True):