summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/tests/hyperv/fake.py52
-rw-r--r--nova/tests/test_hypervapi.py290
-rwxr-xr-xnova/virt/hyperv/driver.py31
-rw-r--r--nova/virt/hyperv/hostops.py15
-rw-r--r--nova/virt/hyperv/hostutils.py7
-rw-r--r--nova/virt/hyperv/imagecache.py60
-rw-r--r--nova/virt/hyperv/livemigrationops.py19
-rw-r--r--nova/virt/hyperv/livemigrationutils.py2
-rw-r--r--nova/virt/hyperv/migrationops.py233
-rw-r--r--nova/virt/hyperv/pathutils.py113
-rw-r--r--nova/virt/hyperv/snapshotops.py20
-rw-r--r--nova/virt/hyperv/vhdutils.py29
-rw-r--r--nova/virt/hyperv/vmops.py188
-rw-r--r--nova/virt/hyperv/vmutils.py39
-rw-r--r--nova/virt/hyperv/volumeops.py74
15 files changed, 870 insertions, 302 deletions
diff --git a/nova/tests/hyperv/fake.py b/nova/tests/hyperv/fake.py
index 9890a5462..e0e5a6bbe 100644
--- a/nova/tests/hyperv/fake.py
+++ b/nova/tests/hyperv/fake.py
@@ -23,24 +23,50 @@ class PathUtils(object):
def open(self, path, mode):
return io.BytesIO(b'fake content')
- def get_instances_path(self):
- return 'C:\\FakePath\\'
+ def exists(self, path):
+ return False
+
+ def makedirs(self, path):
+ pass
+
+ def remove(self, path):
+ pass
+
+ def rename(self, src, dest):
+ pass
+
+ def copyfile(self, src, dest):
+ pass
+
+ def copy(self, src, dest):
+ pass
+
+ def rmtree(self, path):
+ pass
+
+ def get_instances_dir(self, remote_server=None):
+ return 'C:\\FakeInstancesPath\\'
+
+ def get_instance_migr_revert_dir(self, instance_name, create_dir=False,
+ remove_dir=False):
+ return os.path.join(self.get_instances_dir(), instance_name, '_revert')
- def get_instance_path(self, instance_name):
- return os.path.join(self.get_instances_path(), instance_name)
+ def get_instance_dir(self, instance_name, remote_server=None,
+ create_dir=True, remove_dir=False):
+ return os.path.join(self.get_instances_dir(remote_server),
+ instance_name)
def get_vhd_path(self, instance_name):
- instance_path = self.get_instance_path(instance_name)
- return os.path.join(instance_path, instance_name + ".vhd")
+ instance_path = self.get_instance_dir(instance_name)
+ return os.path.join(instance_path, 'root.vhd')
- def get_base_vhd_path(self, image_name):
- base_dir = os.path.join(self.get_instances_path(), '_base')
- return os.path.join(base_dir, image_name + ".vhd")
+ def get_base_vhd_dir(self):
+ return os.path.join(self.get_instances_dir(), '_base')
- def make_export_path(self, instance_name):
- export_folder = os.path.join(self.get_instances_path(), "export",
- instance_name)
- return export_folder
+ def get_export_dir(self, instance_name):
+ export_dir = os.path.join(self.get_instances_dir(), 'export',
+ instance_name)
+ return export_dir
def vhd_exists(self, path):
return False
diff --git a/nova/tests/test_hypervapi.py b/nova/tests/test_hypervapi.py
index 0c2f90a4d..025d3a454 100644
--- a/nova/tests/test_hypervapi.py
+++ b/nova/tests/test_hypervapi.py
@@ -80,6 +80,7 @@ class HyperVAPITestCase(test.TestCase):
self._instance_ide_disks = []
self._instance_ide_dvds = []
self._instance_volume_disks = []
+ self._test_vm_name = None
self._setup_stubs()
@@ -116,6 +117,14 @@ class HyperVAPITestCase(test.TestCase):
self.stubs.Set(pathutils, 'PathUtils', fake.PathUtils)
self._mox.StubOutWithMock(fake.PathUtils, 'open')
+ self._mox.StubOutWithMock(fake.PathUtils, 'copyfile')
+ self._mox.StubOutWithMock(fake.PathUtils, 'rmtree')
+ self._mox.StubOutWithMock(fake.PathUtils, 'copy')
+ self._mox.StubOutWithMock(fake.PathUtils, 'remove')
+ self._mox.StubOutWithMock(fake.PathUtils, 'rename')
+ self._mox.StubOutWithMock(fake.PathUtils, 'makedirs')
+ self._mox.StubOutWithMock(fake.PathUtils,
+ 'get_instance_migr_revert_dir')
self._mox.StubOutWithMock(vmutils.VMUtils, 'vm_exists')
self._mox.StubOutWithMock(vmutils.VMUtils, 'create_vm')
@@ -137,11 +146,13 @@ class HyperVAPITestCase(test.TestCase):
self._mox.StubOutWithMock(vmutils.VMUtils,
'get_mounted_disk_by_drive_number')
self._mox.StubOutWithMock(vmutils.VMUtils, 'detach_vm_disk')
+ self._mox.StubOutWithMock(vmutils.VMUtils, 'get_vm_storage_paths')
self._mox.StubOutWithMock(vhdutils.VHDUtils, 'create_differencing_vhd')
self._mox.StubOutWithMock(vhdutils.VHDUtils, 'reconnect_parent_vhd')
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(hostutils.HostUtils, 'get_cpus_info')
self._mox.StubOutWithMock(hostutils.HostUtils,
@@ -149,6 +160,7 @@ class HyperVAPITestCase(test.TestCase):
self._mox.StubOutWithMock(hostutils.HostUtils, 'get_memory_info')
self._mox.StubOutWithMock(hostutils.HostUtils, 'get_volume_info')
self._mox.StubOutWithMock(hostutils.HostUtils, 'get_windows_version')
+ self._mox.StubOutWithMock(hostutils.HostUtils, 'get_local_ips')
self._mox.StubOutWithMock(networkutils.NetworkUtils,
'get_external_vswitch')
@@ -181,11 +193,6 @@ class HyperVAPITestCase(test.TestCase):
self._mox.StubOutWithMock(volumeutilsv2.VolumeUtilsV2,
'execute_log_out')
- self._mox.StubOutWithMock(shutil, 'copyfile')
- self._mox.StubOutWithMock(shutil, 'rmtree')
-
- self._mox.StubOutWithMock(os, 'remove')
-
self._mox.StubOutClassWithMocks(instance_metadata, 'InstanceMetadata')
self._mox.StubOutWithMock(instance_metadata.InstanceMetadata,
'metadata_for_config_drive')
@@ -332,7 +339,7 @@ class HyperVAPITestCase(test.TestCase):
mox.IsA(str),
mox.IsA(str),
attempts=1)
- os.remove(mox.IsA(str))
+ fake.PathUtils.remove(mox.IsA(str))
m = vmutils.VMUtils.attach_ide_drive(mox.IsA(str),
mox.IsA(str),
@@ -490,15 +497,22 @@ class HyperVAPITestCase(test.TestCase):
None)
self._mox.VerifyAll()
- def test_destroy(self):
- self._instance_data = self._get_instance_data()
-
+ def _setup_destroy_mocks(self):
m = vmutils.VMUtils.vm_exists(mox.Func(self._check_instance_name))
m.AndReturn(True)
- m = vmutils.VMUtils.destroy_vm(mox.Func(self._check_instance_name),
- True)
- m.AndReturn([])
+ func = mox.Func(self._check_instance_name)
+ vmutils.VMUtils.set_vm_state(func, constants.HYPERV_VM_STATE_DISABLED)
+
+ m = vmutils.VMUtils.get_vm_storage_paths(func)
+ m.AndReturn(([], []))
+
+ vmutils.VMUtils.destroy_vm(func)
+
+ def test_destroy(self):
+ self._instance_data = self._get_instance_data()
+
+ self._setup_destroy_mocks()
self._mox.ReplayAll()
self._conn.destroy(self._instance_data)
@@ -562,7 +576,9 @@ class HyperVAPITestCase(test.TestCase):
if cow:
m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(mox.IsA(str),
None)
- m.AndReturn([])
+ m.AndReturn(False)
+
+ vhdutils.VHDUtils.get_vhd_info(mox.Func(self._check_img_path))
self._mox.ReplayAll()
self._conn.pre_live_migration(self._context, instance_data,
@@ -617,7 +633,7 @@ class HyperVAPITestCase(test.TestCase):
def copy_dest_disk_path(src, dest):
self._fake_dest_disk_path = dest
- m = shutil.copyfile(mox.IsA(str), mox.IsA(str))
+ m = fake.PathUtils.copyfile(mox.IsA(str), mox.IsA(str))
m.WithSideEffects(copy_dest_disk_path)
self._fake_dest_base_disk_path = None
@@ -625,7 +641,7 @@ class HyperVAPITestCase(test.TestCase):
def copy_dest_base_disk_path(src, dest):
self._fake_dest_base_disk_path = dest
- m = shutil.copyfile(fake_parent_vhd_path, mox.IsA(str))
+ m = fake.PathUtils.copyfile(fake_parent_vhd_path, mox.IsA(str))
m.WithSideEffects(copy_dest_base_disk_path)
def check_dest_disk_path(path):
@@ -647,7 +663,7 @@ class HyperVAPITestCase(test.TestCase):
func = mox.Func(check_snapshot_path)
vmutils.VMUtils.remove_vm_snapshot(func)
- shutil.rmtree(mox.IsA(str))
+ fake.PathUtils.rmtree(mox.IsA(str))
m = fake.PathUtils.open(func2, 'rb')
m.AndReturn(io.BytesIO(b'fake content'))
@@ -702,65 +718,70 @@ class HyperVAPITestCase(test.TestCase):
mounted_disk_path):
self._instance_volume_disks.append(mounted_disk_path)
- def _setup_spawn_instance_mocks(self, cow, setup_vif_mocks_func=None,
- with_exception=False,
- block_device_info=None):
- self._test_vm_name = None
-
- def set_vm_name(vm_name):
- self._test_vm_name = vm_name
-
- def check_vm_name(vm_name):
- return vm_name == self._test_vm_name
-
- m = vmutils.VMUtils.vm_exists(mox.IsA(str))
- m.WithSideEffects(set_vm_name).AndReturn(False)
-
- if not block_device_info:
- m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(mox.IsA(str),
- None)
- m.AndReturn([])
- else:
- m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(
- mox.IsA(str), block_device_info)
- m.AndReturn(True)
-
- if cow:
- def check_path(parent_path):
- return parent_path == self._fetched_image
-
- vhdutils.VHDUtils.create_differencing_vhd(mox.IsA(str),
- mox.Func(check_path))
+ def _check_img_path(self, image_path):
+ return image_path == self._fetched_image
- vmutils.VMUtils.create_vm(mox.Func(check_vm_name), mox.IsA(int),
+ def _setup_create_instance_mocks(self, setup_vif_mocks_func=None,
+ boot_from_volume=False):
+ vmutils.VMUtils.create_vm(mox.Func(self._check_vm_name), mox.IsA(int),
mox.IsA(int), mox.IsA(bool))
- if not block_device_info:
- m = vmutils.VMUtils.attach_ide_drive(mox.Func(check_vm_name),
+ if not boot_from_volume:
+ m = vmutils.VMUtils.attach_ide_drive(mox.Func(self._check_vm_name),
mox.IsA(str),
mox.IsA(int),
mox.IsA(int),
mox.IsA(str))
m.WithSideEffects(self._add_ide_disk).InAnyOrder()
- m = vmutils.VMUtils.create_scsi_controller(mox.Func(check_vm_name))
+ func = mox.Func(self._check_vm_name)
+ m = vmutils.VMUtils.create_scsi_controller(func)
m.InAnyOrder()
- vmutils.VMUtils.create_nic(mox.Func(check_vm_name), mox.IsA(str),
+ vmutils.VMUtils.create_nic(mox.Func(self._check_vm_name), mox.IsA(str),
mox.IsA(str)).InAnyOrder()
if setup_vif_mocks_func:
setup_vif_mocks_func()
+ def _set_vm_name(self, vm_name):
+ self._test_vm_name = vm_name
+
+ def _check_vm_name(self, vm_name):
+ return vm_name == self._test_vm_name
+
+ def _setup_spawn_instance_mocks(self, cow, setup_vif_mocks_func=None,
+ with_exception=False,
+ block_device_info=None,
+ boot_from_volume=False):
+ m = vmutils.VMUtils.vm_exists(mox.IsA(str))
+ m.WithSideEffects(self._set_vm_name).AndReturn(False)
+
+ m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(
+ mox.IsA(str), block_device_info)
+ m.AndReturn(boot_from_volume)
+
+ if not boot_from_volume:
+ vhdutils.VHDUtils.get_vhd_info(mox.Func(self._check_img_path))
+
+ if cow:
+ vhdutils.VHDUtils.create_differencing_vhd(
+ mox.IsA(str), mox.Func(self._check_img_path))
+ else:
+ fake.PathUtils.copyfile(mox.IsA(str), mox.IsA(str))
+
+ self._setup_create_instance_mocks(setup_vif_mocks_func,
+ boot_from_volume)
+
# TODO(alexpilotti) Based on where the exception is thrown
# some of the above mock calls need to be skipped
if with_exception:
- m = vmutils.VMUtils.vm_exists(mox.Func(check_vm_name))
+ m = vmutils.VMUtils.vm_exists(mox.Func(self._check_vm_name))
m.AndReturn(True)
- vmutils.VMUtils.destroy_vm(mox.Func(check_vm_name), True)
+ vmutils.VMUtils.destroy_vm(mox.Func(self._check_vm_name))
else:
- vmutils.VMUtils.set_vm_state(mox.Func(check_vm_name),
+ vmutils.VMUtils.set_vm_state(mox.Func(self._check_vm_name),
constants.HYPERV_VM_STATE_ENABLED)
def _test_spawn_instance(self, cow=True,
@@ -772,14 +793,14 @@ class HyperVAPITestCase(test.TestCase):
with_exception)
self._mox.ReplayAll()
- self._spawn_instance(cow, )
+ self._spawn_instance(cow)
self._mox.VerifyAll()
self.assertEquals(len(self._instance_ide_disks), expected_ide_disks)
self.assertEquals(len(self._instance_ide_dvds), expected_ide_dvds)
- if not cow:
- self.assertEquals(self._fetched_image, self._instance_ide_disks[0])
+ vhd_path = pathutils.PathUtils().get_vhd_path(self._test_vm_name)
+ self.assertEquals(vhd_path, self._instance_ide_disks[0])
def test_attach_volume(self):
instance_data = self._get_instance_data()
@@ -897,10 +918,165 @@ class HyperVAPITestCase(test.TestCase):
m.WithSideEffects(self._add_volume_disk)
self._setup_spawn_instance_mocks(cow=False,
- block_device_info=block_device_info)
+ block_device_info=block_device_info,
+ boot_from_volume=True)
self._mox.ReplayAll()
self._spawn_instance(False, block_device_info)
self._mox.VerifyAll()
self.assertEquals(len(self._instance_volume_disks), 1)
+
+ def _setup_test_migrate_disk_and_power_off_mocks(self, same_host=False,
+ with_exception=False):
+ self._instance_data = self._get_instance_data()
+ instance = db.instance_create(self._context, self._instance_data)
+ network_info = fake_network.fake_get_instance_nw_info(
+ self.stubs, spectacular=True)
+
+ fake_local_ip = '10.0.0.1'
+ if same_host:
+ fake_dest_ip = fake_local_ip
+ else:
+ fake_dest_ip = '10.0.0.2'
+
+ fake_root_vhd_path = 'C:\\FakePath\\root.vhd'
+ fake_revert_path = ('C:\\FakeInstancesPath\\%s\\_revert' %
+ instance['name'])
+
+ func = mox.Func(self._check_instance_name)
+ vmutils.VMUtils.set_vm_state(func, constants.HYPERV_VM_STATE_DISABLED)
+
+ m = vmutils.VMUtils.get_vm_storage_paths(func)
+ m.AndReturn(([fake_root_vhd_path], []))
+
+ m = hostutils.HostUtils.get_local_ips()
+ m.AndReturn([fake_local_ip])
+
+ m = pathutils.PathUtils.get_instance_migr_revert_dir(instance['name'],
+ remove_dir=True)
+ m.AndReturn(fake_revert_path)
+
+ if same_host:
+ fake.PathUtils.makedirs(mox.IsA(str))
+
+ m = fake.PathUtils.copy(fake_root_vhd_path, mox.IsA(str))
+ if with_exception:
+ m.AndRaise(shutil.Error('Simulated copy error'))
+ else:
+ fake.PathUtils.rename(mox.IsA(str), mox.IsA(str))
+ if same_host:
+ fake.PathUtils.rename(mox.IsA(str), mox.IsA(str))
+
+ self._setup_destroy_mocks()
+
+ return (instance, fake_dest_ip, network_info)
+
+ def test_migrate_disk_and_power_off(self):
+ (instance,
+ fake_dest_ip,
+ network_info) = self._setup_test_migrate_disk_and_power_off_mocks()
+
+ self._mox.ReplayAll()
+ self._conn.migrate_disk_and_power_off(self._context, instance,
+ fake_dest_ip, None,
+ network_info)
+ self._mox.VerifyAll()
+
+ def test_migrate_disk_and_power_off_same_host(self):
+ args = self._setup_test_migrate_disk_and_power_off_mocks(
+ same_host=True)
+ (instance, fake_dest_ip, network_info) = args
+
+ self._mox.ReplayAll()
+ self._conn.migrate_disk_and_power_off(self._context, instance,
+ fake_dest_ip, None,
+ network_info)
+ self._mox.VerifyAll()
+
+ def test_migrate_disk_and_power_off_exception(self):
+ args = self._setup_test_migrate_disk_and_power_off_mocks(
+ with_exception=True)
+ (instance, fake_dest_ip, network_info) = args
+
+ self._mox.ReplayAll()
+ self.assertRaises(shutil.Error, self._conn.migrate_disk_and_power_off,
+ self._context, instance, fake_dest_ip, None,
+ network_info)
+ self._mox.VerifyAll()
+
+ def test_finish_migration(self):
+ self._instance_data = self._get_instance_data()
+ instance = db.instance_create(self._context, self._instance_data)
+ network_info = fake_network.fake_get_instance_nw_info(
+ self.stubs, spectacular=True)
+
+ m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(mox.IsA(str),
+ None)
+ m.AndReturn(False)
+
+ self._mox.StubOutWithMock(fake.PathUtils, 'exists')
+ m = fake.PathUtils.exists(mox.IsA(str))
+ m.AndReturn(True)
+
+ fake_parent_vhd_path = (os.path.join('FakeParentPath', '%s.vhd' %
+ instance["image_ref"]))
+
+ m = vhdutils.VHDUtils.get_vhd_info(mox.IsA(str))
+ m.AndReturn({'ParentPath': fake_parent_vhd_path,
+ 'MaxInternalSize': 1})
+
+ m = fake.PathUtils.exists(mox.IsA(str))
+ m.AndReturn(True)
+
+ vhdutils.VHDUtils.reconnect_parent_vhd(mox.IsA(str), mox.IsA(str))
+
+ self._set_vm_name(instance['name'])
+ self._setup_create_instance_mocks(None, False)
+
+ vmutils.VMUtils.set_vm_state(mox.Func(self._check_instance_name),
+ constants.HYPERV_VM_STATE_ENABLED)
+
+ self._mox.ReplayAll()
+ self._conn.finish_migration(self._context, None, instance, "",
+ network_info, None, False, None)
+ self._mox.VerifyAll()
+
+ def test_confirm_migration(self):
+ self._instance_data = self._get_instance_data()
+ instance = db.instance_create(self._context, self._instance_data)
+ network_info = fake_network.fake_get_instance_nw_info(
+ self.stubs, spectacular=True)
+
+ pathutils.PathUtils.get_instance_migr_revert_dir(instance['name'],
+ remove_dir=True)
+ self._mox.ReplayAll()
+ self._conn.confirm_migration(None, instance, network_info)
+ self._mox.VerifyAll()
+
+ def test_finish_revert_migration(self):
+ self._instance_data = self._get_instance_data()
+ instance = db.instance_create(self._context, self._instance_data)
+ network_info = fake_network.fake_get_instance_nw_info(
+ self.stubs, spectacular=True)
+
+ fake_revert_path = ('C:\\FakeInstancesPath\\%s\\_revert' %
+ instance['name'])
+
+ m = basevolumeutils.BaseVolumeUtils.volume_in_mapping(mox.IsA(str),
+ None)
+ m.AndReturn(False)
+
+ m = pathutils.PathUtils.get_instance_migr_revert_dir(instance['name'])
+ m.AndReturn(fake_revert_path)
+ fake.PathUtils.rename(fake_revert_path, mox.IsA(str))
+
+ self._set_vm_name(instance['name'])
+ self._setup_create_instance_mocks(None, False)
+
+ vmutils.VMUtils.set_vm_state(mox.Func(self._check_instance_name),
+ constants.HYPERV_VM_STATE_ENABLED)
+
+ self._mox.ReplayAll()
+ self._conn.finish_revert_migration(instance, network_info, None)
+ self._mox.VerifyAll()
diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py
index 4af3b8b05..aac47deef 100755
--- a/nova/virt/hyperv/driver.py
+++ b/nova/virt/hyperv/driver.py
@@ -23,6 +23,7 @@ from nova.openstack.common import log as logging
from nova.virt import driver
from nova.virt.hyperv import hostops
from nova.virt.hyperv import livemigrationops
+from nova.virt.hyperv import migrationops
from nova.virt.hyperv import snapshotops
from nova.virt.hyperv import vmops
from nova.virt.hyperv import volumeops
@@ -36,10 +37,10 @@ class HyperVDriver(driver.ComputeDriver):
self._hostops = hostops.HostOps()
self._volumeops = volumeops.VolumeOps()
- self._vmops = vmops.VMOps(self._volumeops)
+ self._vmops = vmops.VMOps()
self._snapshotops = snapshotops.SnapshotOps()
- self._livemigrationops = livemigrationops.LiveMigrationOps(
- self._volumeops)
+ self._livemigrationops = livemigrationops.LiveMigrationOps()
+ self._migrationops = migrationops.MigrationOps()
def init_host(self, host):
pass
@@ -146,7 +147,7 @@ class HyperVDriver(driver.ComputeDriver):
LOG.debug(_("plug_vifs called"), instance=instance)
def unplug_vifs(self, instance, network_info):
- LOG.debug(_("plug_vifs called"), instance=instance)
+ LOG.debug(_("unplug_vifs called"), instance=instance)
def ensure_filtering_rules_for_instance(self, instance_ref, network_info):
LOG.debug(_("ensure_filtering_rules_for_instance called"),
@@ -155,17 +156,33 @@ class HyperVDriver(driver.ComputeDriver):
def unfilter_instance(self, instance, network_info):
LOG.debug(_("unfilter_instance called"), instance=instance)
+ def migrate_disk_and_power_off(self, context, instance, dest,
+ instance_type, network_info,
+ block_device_info=None):
+ return self._migrationops.migrate_disk_and_power_off(context,
+ instance, dest,
+ instance_type,
+ network_info,
+ block_device_info)
+
def confirm_migration(self, migration, instance, network_info):
- LOG.debug(_("confirm_migration called"), instance=instance)
+ self._migrationops.confirm_migration(migration, instance, network_info)
def finish_revert_migration(self, instance, network_info,
block_device_info=None):
- LOG.debug(_("finish_revert_migration called"), instance=instance)
+ self._migrationops.finish_revert_migration(instance, network_info,
+ block_device_info)
def finish_migration(self, context, migration, instance, disk_info,
network_info, image_meta, resize_instance=False,
block_device_info=None):
- LOG.debug(_("finish_migration called"), instance=instance)
+ self._migrationops.finish_migration(context, migration, instance,
+ disk_info, network_info,
+ image_meta, resize_instance,
+ block_device_info)
+
+ def get_host_ip_addr(self):
+ return self._hostops.get_host_ip_addr()
def get_console_output(self, instance):
LOG.debug(_("get_console_output called"), instance=instance)
diff --git a/nova/virt/hyperv/hostops.py b/nova/virt/hyperv/hostops.py
index 5a22b60de..b3b38aab9 100644
--- a/nova/virt/hyperv/hostops.py
+++ b/nova/virt/hyperv/hostops.py
@@ -21,12 +21,15 @@ Management class for host operations.
import os
import platform
+from nova.openstack.common import cfg
from nova.openstack.common import jsonutils
from nova.openstack.common import log as logging
from nova.virt.hyperv import constants
from nova.virt.hyperv import hostutils
from nova.virt.hyperv import pathutils
+CONF = cfg.CONF
+CONF.import_opt('my_ip', 'nova.netconf')
LOG = logging.getLogger(__name__)
@@ -73,7 +76,7 @@ class HostOps(object):
return (total_mem_mb, free_mem_mb, total_mem_mb - free_mem_mb)
def _get_local_hdd_info_gb(self):
- (drive, _) = os.path.splitdrive(self._pathutils.get_instances_path())
+ drive = os.path.splitdrive(self._pathutils.get_instances_dir())[0]
(size, free_space) = self._hostutils.get_volume_info(drive)
total_gb = size / (1024 ** 3)
@@ -152,7 +155,7 @@ class HostOps(object):
def get_host_stats(self, refresh=False):
"""Return the current state of the host. If 'refresh' is
True, run the update first."""
- LOG.info(_("get_host_stats called"))
+ LOG.debug(_("get_host_stats called"))
if refresh or not self._stats:
self._update_stats()
@@ -161,3 +164,11 @@ class HostOps(object):
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
pass
+
+ def get_host_ip_addr(self):
+ host_ip = CONF.my_ip
+ if not host_ip:
+ # Return the first available address
+ host_ip = self._hostutils.get_local_ips()[0]
+ LOG.debug(_("Host IP address is: %s"), host_ip)
+ return host_ip
diff --git a/nova/virt/hyperv/hostutils.py b/nova/virt/hyperv/hostutils.py
index 71f3bc5b2..d28ce75a5 100644
--- a/nova/virt/hyperv/hostutils.py
+++ b/nova/virt/hyperv/hostutils.py
@@ -16,6 +16,7 @@
# under the License.
import ctypes
+import socket
import sys
if sys.platform == 'win32':
@@ -72,3 +73,9 @@ class HostUtils(object):
def get_windows_version(self):
return self._conn_cimv2.Win32_OperatingSystem()[0].Version
+
+ def get_local_ips(self):
+ addr_info = socket.getaddrinfo(socket.gethostname(), None, 0, 0, 0)
+ # Returns IPv4 and IPv6 addresses, ordered by protocol family
+ addr_info.sort()
+ return [a[4][0] for a in addr_info]
diff --git a/nova/virt/hyperv/imagecache.py b/nova/virt/hyperv/imagecache.py
new file mode 100644
index 000000000..93ea32b25
--- /dev/null
+++ b/nova/virt/hyperv/imagecache.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Cloudbase Solutions Srl
+# 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.
+"""
+Image caching and management.
+"""
+import os
+
+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
+
+LOG = logging.getLogger(__name__)
+
+
+class ImageCache(object):
+ def __init__(self):
+ self._pathutils = pathutils.PathUtils()
+ self._vhdutils = vhdutils.VHDUtils()
+
+ def _validate_vhd_image(self, vhd_path):
+ try:
+ self._vhdutils.get_vhd_info(vhd_path)
+ except Exception as ex:
+ LOG.exception(ex)
+ raise vmutils.HyperVException(_('The image is not a valid VHD: %s')
+ % vhd_path)
+
+ def get_cached_image(self, context, instance):
+ image_id = instance['image_ref']
+
+ base_vhd_dir = self._pathutils.get_base_vhd_dir()
+ vhd_path = os.path.join(base_vhd_dir, image_id + ".vhd")
+
+ @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)
+
+ fetch_image_if_not_existing()
+ return vhd_path
diff --git a/nova/virt/hyperv/livemigrationops.py b/nova/virt/hyperv/livemigrationops.py
index 8ee3005f1..108413b0b 100644
--- a/nova/virt/hyperv/livemigrationops.py
+++ b/nova/virt/hyperv/livemigrationops.py
@@ -18,15 +18,14 @@
"""
Management class for live migration VM operations.
"""
-import os
-
from nova.openstack.common import cfg
from nova.openstack.common import excutils
from nova.openstack.common import log as logging
+from nova.virt.hyperv import imagecache
from nova.virt.hyperv import livemigrationutils
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import vmutils
-from nova.virt import images
+from nova.virt.hyperv import volumeops
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@@ -34,12 +33,13 @@ CONF.import_opt('use_cow_images', 'nova.virt.driver')
class LiveMigrationOps(object):
- def __init__(self, volumeops):
+ def __init__(self):
self._pathutils = pathutils.PathUtils()
self._vmutils = vmutils.VMUtils()
self._livemigrutils = livemigrationutils.LiveMigrationUtils()
- self._volumeops = volumeops
+ self._volumeops = volumeops.VolumeOps()
+ self._imagecache = imagecache.ImageCache()
def live_migration(self, context, instance_ref, dest, post_method,
recover_method, block_migration=False,
@@ -65,15 +65,10 @@ class LiveMigrationOps(object):
self._livemigrutils.check_live_migration_config()
if CONF.use_cow_images:
- ebs_root = self._volumeops.volume_in_mapping(
- self._volumeops.get_default_root_device(),
+ ebs_root = self._volumeops.ebs_root_in_block_devices(
block_device_info)
if not ebs_root:
- base_vhd_path = self._pathutils.get_base_vhd_path(
- instance["image_ref"])
- if not os.path.exists(base_vhd_path):
- images.fetch(context, instance["image_ref"], base_vhd_path,
- instance["user_id"], instance["project_id"])
+ self._imagecache.get_cached_image(context, instance)
def post_live_migration_at_destination(self, ctxt, instance_ref,
network_info, block_migration):
diff --git a/nova/virt/hyperv/livemigrationutils.py b/nova/virt/hyperv/livemigrationutils.py
index 6af4f0fa5..d039a5016 100644
--- a/nova/virt/hyperv/livemigrationutils.py
+++ b/nova/virt/hyperv/livemigrationutils.py
@@ -41,7 +41,7 @@ class LiveMigrationUtils(object):
% host)
elif ex.com_error.hresult == -2147023174:
msg = (_('Target live migration host "%s" is unreachable')
- % host)
+ % host)
else:
msg = _('Live migration failed: %s') % ex.message
raise vmutils.HyperVException(msg)
diff --git a/nova/virt/hyperv/migrationops.py b/nova/virt/hyperv/migrationops.py
new file mode 100644
index 000000000..8d5b5e90c
--- /dev/null
+++ b/nova/virt/hyperv/migrationops.py
@@ -0,0 +1,233 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Cloudbase Solutions Srl
+# 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.
+
+"""
+Management class for migration / resize operations.
+"""
+import os
+
+from nova.openstack.common import excutils
+from nova.openstack.common import log as logging
+from nova.virt.hyperv import hostutils
+from nova.virt.hyperv import imagecache
+from nova.virt.hyperv import pathutils
+from nova.virt.hyperv import vhdutils
+from nova.virt.hyperv import vmops
+from nova.virt.hyperv import vmutils
+from nova.virt.hyperv import volumeops
+
+LOG = logging.getLogger(__name__)
+
+
+class MigrationOps(object):
+ def __init__(self):
+ self._hostutils = hostutils.HostUtils()
+ self._vmutils = vmutils.VMUtils()
+ self._vhdutils = vhdutils.VHDUtils()
+ self._pathutils = pathutils.PathUtils()
+ self._volumeops = volumeops.VolumeOps()
+ self._vmops = vmops.VMOps()
+ self._imagecache = imagecache.ImageCache()
+
+ def _migrate_disk_files(self, instance_name, disk_files, dest):
+ same_host = False
+ if dest in self._hostutils.get_local_ips():
+ same_host = True
+ LOG.debug(_("Migration target is the source host"))
+ else:
+ LOG.debug(_("Migration target host: %s") % dest)
+
+ instance_path = self._pathutils.get_instance_dir(instance_name)
+ revert_path = self._pathutils.get_instance_migr_revert_dir(
+ instance_name, remove_dir=True)
+ dest_path = None
+
+ try:
+ if same_host:
+ # Since source and target are the same, we copy the files to
+ # a temporary location before moving them into place
+ dest_path = '%s_tmp' % instance_path
+ if self._pathutils.exists(dest_path):
+ self._pathutils.rmtree(dest_path)
+ self._pathutils.makedirs(dest_path)
+ else:
+ dest_path = self._pathutils.get_instance_dir(
+ instance_name, dest, remove_dir=True)
+ for disk_file in disk_files:
+ # Skip the config drive as the instance is already configured
+ if os.path.basename(disk_file).lower() != 'configdrive.vhd':
+ LOG.debug(_('Copying disk "%(disk_file)s" to '
+ '"%(dest_path)s"') % locals())
+ self._pathutils.copy(disk_file, dest_path)
+
+ self._pathutils.rename(instance_path, revert_path)
+
+ if same_host:
+ self._pathutils.rename(dest_path, instance_path)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ self._cleanup_failed_disk_migration(instance_path, revert_path,
+ dest_path)
+
+ def _cleanup_failed_disk_migration(self, instance_path,
+ revert_path, dest_path):
+ try:
+ if dest_path and self._pathutils.exists(dest_path):
+ self._pathutils.rmtree(dest_path)
+ if self._pathutils.exists(revert_path):
+ self._pathutils.rename(revert_path, instance_path)
+ except Exception as ex:
+ # Log and ignore this exception
+ LOG.exception(ex)
+ LOG.error(_("Cannot cleanup migration files"))
+
+ def migrate_disk_and_power_off(self, context, instance, dest,
+ instance_type, network_info,
+ block_device_info=None):
+ LOG.debug(_("migrate_disk_and_power_off called"), instance=instance)
+
+ self._vmops.power_off(instance)
+
+ instance_name = instance["name"]
+
+ (disk_files,
+ volume_drives) = self._vmutils.get_vm_storage_paths(instance_name)
+
+ if disk_files:
+ self._migrate_disk_files(instance_name, disk_files, dest)
+
+ self._vmops.destroy(instance, destroy_disks=False)
+
+ # disk_info is not used
+ return ""
+
+ def confirm_migration(self, migration, instance, network_info):
+ LOG.debug(_("confirm_migration called"), instance=instance)
+
+ self._pathutils.get_instance_migr_revert_dir(instance['name'],
+ remove_dir=True)
+
+ def _revert_migration_files(self, instance_name):
+ instance_path = self._pathutils.get_instance_dir(
+ instance_name, create_dir=False, remove_dir=True)
+
+ revert_path = self._pathutils.get_instance_migr_revert_dir(
+ instance_name)
+ self._pathutils.rename(revert_path, instance_path)
+
+ def finish_revert_migration(self, instance, network_info,
+ block_device_info=None):
+ LOG.debug(_("finish_revert_migration called"), instance=instance)
+
+ instance_name = instance['name']
+ self._revert_migration_files(instance_name)
+
+ if self._volumeops.ebs_root_in_block_devices(block_device_info):
+ boot_vhd_path = None
+ else:
+ boot_vhd_path = self._pathutils.get_vhd_path(instance_name)
+ self._vmops.create_instance(instance, network_info, block_device_info,
+ boot_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:
+ LOG.debug(_('Copying base disk %(base_vhd_path)s to '
+ '%(base_vhd_copy_path)s'), locals())
+ self._pathutils.copyfile(base_vhd_path, base_vhd_copy_path)
+
+ LOG.debug(_("Reconnecting copied base VHD "
+ "%(base_vhd_copy_path)s and diff "
+ "VHD %(diff_vhd_path)s"), locals())
+ self._vhdutils.reconnect_parent_vhd(diff_vhd_path,
+ base_vhd_copy_path)
+
+ LOG.debug(_("Merging base disk %(base_vhd_copy_path)s and "
+ "diff disk %(diff_vhd_path)s"), locals())
+ self._vhdutils.merge_vhd(diff_vhd_path, base_vhd_copy_path)
+
+ # Replace the differential VHD with the merged one
+ self._pathutils.rename(base_vhd_copy_path, diff_vhd_path)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ if self._pathutils.exists(base_vhd_copy_path):
+ self._pathutils.remove(base_vhd_copy_path)
+
+ def _resize_vhd(self, vhd_path, new_size):
+ LOG.debug(_("Getting info for disk: %s"), vhd_path)
+ base_disk_path = self._vhdutils.get_vhd_parent_path(vhd_path)
+ if base_disk_path:
+ # A differential VHD cannot be resized
+ self._merge_base_vhd(vhd_path, base_disk_path)
+ LOG.debug(_("Resizing disk \"%(vhd_path)s\" to new max "
+ "size %(new_size)s"), locals())
+ self._vhdutils.resize_vhd(vhd_path, new_size)
+
+ 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
+ # and target hosts we need to reconnect the base disk
+ if src_base_disk_path.lower() != base_vhd_path.lower():
+ LOG.debug(_("Reconnecting copied base VHD "
+ "%(base_vhd_path)s and diff "
+ "VHD %(diff_vhd_path)s"), locals())
+ self._vhdutils.reconnect_parent_vhd(diff_vhd_path,
+ base_vhd_path)
+
+ def finish_migration(self, context, migration, instance, disk_info,
+ network_info, image_meta, resize_instance=False,
+ block_device_info=None):
+ LOG.debug(_("finish_migration called"), instance=instance)
+
+ instance_name = instance['name']
+
+ if self._volumeops.ebs_root_in_block_devices(block_device_info):
+ boot_vhd_path = None
+ else:
+ boot_vhd_path = self._pathutils.get_vhd_path(instance_name)
+ if not self._pathutils.exists(boot_vhd_path):
+ raise vmutils.HyperVException(_("Cannot find boot VHD "
+ "file: %s") % boot_vhd_path)
+
+ vhd_info = self._vhdutils.get_vhd_info(boot_vhd_path)
+ src_base_disk_path = vhd_info.get("ParentPath")
+ if src_base_disk_path:
+ self._check_base_disk(context, instance, boot_vhd_path,
+ src_base_disk_path)
+
+ if resize_instance:
+ curr_size = vhd_info['MaxInternalSize']
+ new_size = instance['root_gb'] * 1024 * 1024 * 1024
+ 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._vmops.create_instance(instance, network_info, block_device_info,
+ boot_vhd_path)
+ self._vmops.power_on(instance)
diff --git a/nova/virt/hyperv/pathutils.py b/nova/virt/hyperv/pathutils.py
index 7bc2e7ac2..05cfffaac 100644
--- a/nova/virt/hyperv/pathutils.py
+++ b/nova/virt/hyperv/pathutils.py
@@ -23,7 +23,18 @@ from nova.openstack.common import log as logging
LOG = logging.getLogger(__name__)
+hyperv_opts = [
+ cfg.StrOpt('instances_path_share',
+ default="",
+ help='The name of a Windows share name mapped to the '
+ '"instances_path" dir and used by the resize feature '
+ 'to copy files to the target host. If left blank, an '
+ 'administrative share will be used, looking for the same '
+ '"instances_path" used locally'),
+]
+
CONF = cfg.CONF
+CONF.register_opts(hyperv_opts, 'HYPERV')
CONF.import_opt('instances_path', 'nova.compute.manager')
@@ -33,35 +44,79 @@ class PathUtils(object):
import __builtin__
return __builtin__.open(path, mode)
- def get_instances_path(self):
- return os.path.normpath(CONF.instances_path)
+ def exists(self, path):
+ return os.path.exists(path)
+
+ def makedirs(self, path):
+ os.makedirs(path)
+
+ def remove(self, path):
+ os.remove(path)
+
+ def rename(self, src, dest):
+ os.rename(src, dest)
+
+ def copyfile(self, src, dest):
+ shutil.copyfile(src, dest)
+
+ def copy(self, src, dest):
+ shutil.copy(src, dest)
+
+ def rmtree(self, path):
+ shutil.rmtree(path)
+
+ def get_instances_dir(self, remote_server=None):
+ local_instance_path = os.path.normpath(CONF.instances_path)
- def get_instance_path(self, instance_name):
- instance_path = os.path.join(self.get_instances_path(), instance_name)
- if not os.path.exists(instance_path):
- LOG.debug(_('Creating folder %s '), instance_path)
- os.makedirs(instance_path)
- return instance_path
+ if remote_server:
+ if CONF.HYPERV.instances_path_share:
+ path = CONF.HYPERV.instances_path_share
+ else:
+ # Use an administrative share
+ path = local_instance_path.replace(':', '$')
+ return '\\\\%(remote_server)s\\%(path)s' % locals()
+ else:
+ return local_instance_path
+
+ def _check_create_dir(self, path):
+ if not self.exists(path):
+ LOG.debug(_('Creating directory: %s') % path)
+ self.makedirs(path)
+
+ def _check_remove_dir(self, path):
+ if self.exists(path):
+ LOG.debug(_('Removing directory: %s') % path)
+ self.rmtree(path)
+
+ def _get_instances_sub_dir(self, dir_name, remote_server=None,
+ create_dir=True, remove_dir=False):
+ instances_path = self.get_instances_dir(remote_server)
+ path = os.path.join(instances_path, dir_name)
+ if remove_dir:
+ self._check_remove_dir(path)
+ if create_dir:
+ self._check_create_dir(path)
+ return path
+
+ def get_instance_migr_revert_dir(self, instance_name, create_dir=False,
+ remove_dir=False):
+ dir_name = '%s_revert' % instance_name
+ return self._get_instances_sub_dir(dir_name, None, create_dir,
+ remove_dir)
+
+ def get_instance_dir(self, instance_name, remote_server=None,
+ create_dir=True, remove_dir=False):
+ return self._get_instances_sub_dir(instance_name, remote_server,
+ create_dir, remove_dir)
def get_vhd_path(self, instance_name):
- instance_path = self.get_instance_path(instance_name)
- return os.path.join(instance_path, instance_name + ".vhd")
-
- def get_base_vhd_path(self, image_name):
- base_dir = os.path.join(self.get_instances_path(), '_base')
- if not os.path.exists(base_dir):
- os.makedirs(base_dir)
- return os.path.join(base_dir, image_name + ".vhd")
-
- def make_export_path(self, instance_name):
- export_folder = os.path.join(self.get_instances_path(), "export",
- instance_name)
- if os.path.isdir(export_folder):
- LOG.debug(_('Removing existing folder %s '), export_folder)
- shutil.rmtree(export_folder)
- LOG.debug(_('Creating folder %s '), export_folder)
- os.makedirs(export_folder)
- return export_folder
-
- def vhd_exists(self, path):
- return os.path.exists(path)
+ instance_path = self.get_instance_dir(instance_name)
+ return os.path.join(instance_path, 'root.vhd')
+
+ def get_base_vhd_dir(self):
+ return self._get_instances_sub_dir('_base')
+
+ def get_export_dir(self, instance_name):
+ dir_name = os.path.join('export', instance_name)
+ return self._get_instances_sub_dir(dir_name, create_dir=True,
+ remove_dir=True)
diff --git a/nova/virt/hyperv/snapshotops.py b/nova/virt/hyperv/snapshotops.py
index c43f59b70..ab7c96943 100644
--- a/nova/virt/hyperv/snapshotops.py
+++ b/nova/virt/hyperv/snapshotops.py
@@ -19,7 +19,6 @@
Management class for VM snapshot operations.
"""
import os
-import shutil
from nova.compute import task_states
from nova.image import glance
@@ -57,7 +56,7 @@ class SnapshotOps(object):
snapshot_path = self._vmutils.take_vm_snapshot(instance_name)
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
- export_folder = None
+ export_dir = None
try:
src_vhd_path = self._pathutils.get_vhd_path(instance_name)
@@ -66,23 +65,24 @@ class SnapshotOps(object):
src_base_disk_path = self._vhdutils.get_vhd_parent_path(
src_vhd_path)
- export_folder = self._pathutils.make_export_path(instance_name)
+ export_dir = self._pathutils.get_export_dir(instance_name)
- dest_vhd_path = os.path.join(export_folder, os.path.basename(
+ dest_vhd_path = os.path.join(export_dir, os.path.basename(
src_vhd_path))
LOG.debug(_('Copying VHD %(src_vhd_path)s to %(dest_vhd_path)s'),
locals())
- shutil.copyfile(src_vhd_path, dest_vhd_path)
+ self._pathutils.copyfile(src_vhd_path, dest_vhd_path)
image_vhd_path = None
if not src_base_disk_path:
image_vhd_path = dest_vhd_path
else:
basename = os.path.basename(src_base_disk_path)
- dest_base_disk_path = os.path.join(export_folder, basename)
+ dest_base_disk_path = os.path.join(export_dir, basename)
LOG.debug(_('Copying base disk %(src_vhd_path)s to '
'%(dest_base_disk_path)s'), locals())
- shutil.copyfile(src_base_disk_path, dest_base_disk_path)
+ self._pathutils.copyfile(src_base_disk_path,
+ dest_base_disk_path)
LOG.debug(_("Reconnecting copied base VHD "
"%(dest_base_disk_path)s and diff "
@@ -111,6 +111,6 @@ class SnapshotOps(object):
LOG.exception(ex)
LOG.warning(_('Failed to remove snapshot for VM %s')
% instance_name)
- if export_folder:
- LOG.debug(_('Removing folder %s '), export_folder)
- shutil.rmtree(export_folder)
+ if export_dir:
+ LOG.debug(_('Removing directory: %s'), export_dir)
+ self._pathutils.rmtree(export_dir)
diff --git a/nova/virt/hyperv/vhdutils.py b/nova/virt/hyperv/vhdutils.py
index 21c4b4a6d..1e529807d 100644
--- a/nova/virt/hyperv/vhdutils.py
+++ b/nova/virt/hyperv/vhdutils.py
@@ -55,7 +55,17 @@ class VHDUtils(object):
DestinationPath=dest_vhd_path)
self._vmutils.check_ret_val(ret_val, job_path)
+ def resize_vhd(self, vhd_path, new_max_size):
+ image_man_svc = self._conn.Msvm_ImageManagementService()[0]
+
+ (job_path, ret_val) = image_man_svc.ExpandVirtualHardDisk(
+ Path=vhd_path, MaxInternalSize=new_max_size)
+ self._vmutils.check_ret_val(ret_val, job_path)
+
def get_vhd_parent_path(self, vhd_path):
+ return self.get_vhd_info(vhd_path).get("ParentPath")
+
+ def get_vhd_info(self, vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(vhd_info,
@@ -63,10 +73,19 @@ class VHDUtils(object):
ret_val) = image_man_svc.GetVirtualHardDiskInfo(vhd_path)
self._vmutils.check_ret_val(ret_val, job_path)
- base_disk_path = None
+ vhd_info_dict = {}
+
et = ElementTree.fromstring(vhd_info)
for item in et.findall("PROPERTY"):
- if item.attrib["NAME"] == "ParentPath":
- base_disk_path = item.find("VALUE").text
- break
- return base_disk_path
+ name = item.attrib["NAME"]
+ value_text = item.find("VALUE").text
+ if name == "ParentPath":
+ vhd_info_dict[name] = value_text
+ elif name in ["FileSize", "MaxInternalSize"]:
+ vhd_info_dict[name] = long(value_text)
+ elif name in ["InSavedState", "InUse"]:
+ vhd_info_dict[name] = bool(value_text)
+ elif name == "Type":
+ vhd_info_dict[name] = int(value_text)
+
+ return vhd_info_dict
diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py
index 58c1fc66a..c8acc0fa1 100644
--- a/nova/virt/hyperv/vmops.py
+++ b/nova/virt/hyperv/vmops.py
@@ -24,16 +24,18 @@ import os
from nova.api.metadata import base as instance_metadata
from nova import exception
from nova.openstack.common import cfg
+from nova.openstack.common import excutils
from nova.openstack.common import importutils
-from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova import utils
from nova.virt import configdrive
from nova.virt.hyperv import constants
+from nova.virt.hyperv import hostutils
+from nova.virt.hyperv import imagecache
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import vhdutils
from nova.virt.hyperv import vmutils
-from nova.virt import images
+from nova.virt.hyperv import volumeops
LOG = logging.getLogger(__name__)
@@ -69,11 +71,13 @@ class VMOps(object):
'nova.virt.hyperv.vif.HyperVNovaNetworkVIFDriver',
}
- def __init__(self, volumeops):
+ def __init__(self):
+ self._hostutils = hostutils.HostUtils()
self._vmutils = vmutils.VMUtils()
self._vhdutils = vhdutils.VHDUtils()
self._pathutils = pathutils.PathUtils()
- self._volumeops = volumeops
+ self._volumeops = volumeops.VolumeOps()
+ self._imagecache = imagecache.ImageCache()
self._vif_driver = None
self._load_vif_driver_class()
@@ -106,71 +110,77 @@ class VMOps(object):
'num_cpu': info['NumberOfProcessors'],
'cpu_time': info['UpTime']}
+ def _create_boot_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
+
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info, block_device_info=None):
"""Create a new VM and start it."""
+ LOG.info(_("Spawning new instance"), instance=instance)
instance_name = instance['name']
if self._vmutils.vm_exists(instance_name):
raise exception.InstanceExists(name=instance_name)
- ebs_root = self._volumeops.volume_in_mapping(
- self._volumeops.get_default_root_device(),
- block_device_info)
-
- #If is not a boot from volume spawn
- if not (ebs_root):
- #Fetch the file, assume it is a VHD file.
- vhdfile = self._pathutils.get_vhd_path(instance_name)
- try:
- self._cache_image(fn=self._fetch_image,
- context=context,
- target=vhdfile,
- fname=instance['image_ref'],
- image_id=instance['image_ref'],
- user=instance['user_id'],
- project=instance['project_id'],
- cow=CONF.use_cow_images)
- except Exception as exn:
- LOG.exception(_('cache image failed: %s'), exn)
- raise
+ if self._volumeops.ebs_root_in_block_devices(block_device_info):
+ boot_vhd_path = None
+ else:
+ boot_vhd_path = self._create_boot_vhd(context, instance)
try:
- self._vmutils.create_vm(instance_name,
- instance['memory_mb'],
- instance['vcpus'],
- CONF.limit_cpu_features)
-
- if not ebs_root:
- self._vmutils.attach_ide_drive(instance_name,
- vhdfile,
- 0,
- 0,
- constants.IDE_DISK)
- else:
- self._volumeops.attach_boot_volume(block_device_info,
- instance_name)
-
- self._vmutils.create_scsi_controller(instance_name)
-
- for vif in network_info:
- LOG.debug(_('Creating nic for instance: %s'), instance_name)
- self._vmutils.create_nic(instance_name,
- vif['id'],
- vif['address'])
- self._vif_driver.plug(instance, vif)
+ self.create_instance(instance, network_info, block_device_info,
+ boot_vhd_path)
if configdrive.required_by(instance):
self._create_config_drive(instance, injected_files,
admin_password)
- self._set_vm_state(instance_name,
- constants.HYPERV_VM_STATE_ENABLED)
+ self.power_on(instance)
except Exception as ex:
LOG.exception(ex)
self.destroy(instance)
raise vmutils.HyperVException(_('Spawn instance failed'))
+ def create_instance(self, instance, network_info,
+ block_device_info, boot_vhd_path):
+ instance_name = instance['name']
+
+ self._vmutils.create_vm(instance_name,
+ instance['memory_mb'],
+ instance['vcpus'],
+ CONF.limit_cpu_features)
+
+ if boot_vhd_path:
+ self._vmutils.attach_ide_drive(instance_name,
+ boot_vhd_path,
+ 0,
+ 0,
+ constants.IDE_DISK)
+
+ self._vmutils.create_scsi_controller(instance_name)
+
+ self._volumeops.attach_volumes(block_device_info,
+ instance_name,
+ boot_vhd_path is None)
+
+ for vif in network_info:
+ LOG.debug(_('Creating nic for instance: %s'), instance_name)
+ self._vmutils.create_nic(instance_name,
+ vif['id'],
+ vif['address'])
+ self._vif_driver.plug(instance, vif)
+
def _create_config_drive(self, instance, injected_files, admin_password):
if CONF.config_drive_format != 'iso9660':
vmutils.HyperVException(_('Invalid config_drive_format "%s"') %
@@ -186,7 +196,7 @@ class VMOps(object):
content=injected_files,
extra_md=extra_md)
- instance_path = self._pathutils.get_instance_path(
+ instance_path = self._pathutils.get_instance_dir(
instance['name'])
configdrive_path_iso = os.path.join(instance_path, 'configdrive.iso')
LOG.info(_('Creating config drive at %(path)s'),
@@ -196,9 +206,9 @@ class VMOps(object):
try:
cdb.make_drive(configdrive_path_iso)
except exception.ProcessExecutionError, e:
- LOG.error(_('Creating config drive failed with error: %s'),
- e, instance=instance)
- raise
+ with excutils.save_and_reraise_exception():
+ LOG.error(_('Creating config drive failed with error: %s'),
+ e, instance=instance)
if not CONF.config_drive_cdrom:
drive_type = constants.IDE_DISK
@@ -213,7 +223,7 @@ class VMOps(object):
configdrive_path_iso,
configdrive_path,
attempts=1)
- os.remove(configdrive_path_iso)
+ self._pathutils.remove(configdrive_path_iso)
else:
drive_type = constants.IDE_DVD
configdrive_path = configdrive_path_iso
@@ -221,19 +231,35 @@ class VMOps(object):
self._vmutils.attach_ide_drive(instance['name'], configdrive_path,
1, 0, drive_type)
+ def _disconnect_volumes(self, volume_drives):
+ for volume_drive in volume_drives:
+ self._volumeops.disconnect_volume(volume_drive)
+
+ def _delete_disk_files(self, instance_name):
+ self._pathutils.get_instance_dir(instance_name,
+ create_dir=False,
+ remove_dir=True)
+
def destroy(self, instance, network_info=None, cleanup=True,
destroy_disks=True):
instance_name = instance['name']
- LOG.debug(_("Got request to destroy instance: %s"), instance_name)
+ LOG.info(_("Got request to destroy instance: %s"), instance_name)
try:
if self._vmutils.vm_exists(instance_name):
- volumes_drives_list = self._vmutils.destroy_vm(instance_name,
- destroy_disks)
- #Disconnect volumes
- for volume_drive in volumes_drives_list:
- self._volumeops.disconnect_volume(volume_drive)
+
+ #Stop the VM first.
+ self.power_off(instance)
+
+ storage = self._vmutils.get_vm_storage_paths(instance_name)
+ (disk_files, volume_drives) = storage
+
+ self._vmutils.destroy_vm(instance_name)
+ self._disconnect_volumes(volume_drives)
else:
LOG.debug(_("Instance not found: %s"), instance_name)
+
+ if destroy_disks:
+ self._delete_disk_files(instance_name)
except Exception as ex:
LOG.exception(ex)
raise vmutils.HyperVException(_('Failed to destroy instance: %s') %
@@ -292,45 +318,3 @@ class VMOps(object):
msg = _("Failed to change vm state of %(vm_name)s"
" to %(req_state)s") % locals()
raise vmutils.HyperVException(msg)
-
- def _fetch_image(self, target, context, image_id, user, project,
- *args, **kwargs):
- images.fetch(context, image_id, target, user, project)
-
- def _cache_image(self, fn, target, fname, cow=False, size=None,
- *args, **kwargs):
- """Wrapper for a method that creates and caches an 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.
- """
- @lockutils.synchronized(fname, 'nova-')
- def call_if_not_exists(path, fn, *args, **kwargs):
- if not os.path.exists(path):
- fn(target=path, *args, **kwargs)
-
- if not self._pathutils.vhd_exists(target):
- LOG.debug(_("Use CoW image: %s"), cow)
- if cow:
- parent_path = self._pathutils.get_base_vhd_path(fname)
- call_if_not_exists(parent_path, fn, *args, **kwargs)
-
- LOG.debug(_("Creating differencing VHD. Parent: "
- "%(parent_path)s, Target: %(target)s") % locals())
- try:
- self._vhdutils.create_differencing_vhd(target, parent_path)
- except Exception as ex:
- LOG.exception(ex)
- raise vmutils.HyperVException(
- _('Failed to create a differencing disk from '
- '%(parent_path)s to %(target)s') % locals())
- else:
- call_if_not_exists(target, fn, *args, **kwargs)
diff --git a/nova/virt/hyperv/vmutils.py b/nova/virt/hyperv/vmutils.py
index 876153902..d80144b65 100644
--- a/nova/virt/hyperv/vmutils.py
+++ b/nova/virt/hyperv/vmutils.py
@@ -135,6 +135,12 @@ class VMUtils(object):
self._modify_virt_resource(procsetting, vm.path_())
+ def update_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features):
+ vm = self._lookup_vm_check(vm_name)
+ vmsetting = self._get_vm_setting_data(vm)
+ self._set_vm_memory(vm, vmsetting, memory_mb)
+ self._set_vm_vcpus(vm, vmsetting, vcpus_num, limit_cpu_features)
+
def create_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features):
"""Creates a VM."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
@@ -318,14 +324,9 @@ class VMUtils(object):
LOG.debug(_("Successfully changed vm state of %(vm_name)s"
" to %(req_state)s") % locals())
- def destroy_vm(self, vm_name, destroy_disks=True):
- """Destroy the VM. Also destroy the associated VHD disk files."""
-
+ def get_vm_storage_paths(self, vm_name):
vm = self._lookup_vm_check(vm_name)
- #Stop the VM first.
- self.set_vm_state(vm_name, constants.HYPERV_VM_STATE_DISABLED)
-
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
vmsettings = vm.associators(
wmi_result_class='Msvm_VirtualSystemSettingData')
@@ -338,35 +339,25 @@ class VMUtils(object):
if r.ResourceSubType ==
'Microsoft Physical Disk Drive']
- #Collect volumes information before destroying the VM.
- volumes_drives_list = []
+ volume_drives = []
for volume_resource in volume_resources:
drive_path = volume_resource.HostResource[0]
- #Appending the Msvm_Disk path
- volumes_drives_list.append(drive_path)
+ volume_drives.append(drive_path)
- #Collect disk file information before destroying the VM.
disk_files = []
for disk_resource in disk_resources:
disk_files.extend([c for c in disk_resource.Connection])
+ return (disk_files, volume_drives)
+
+ def destroy_vm(self, vm_name):
+ vm = self._lookup_vm_check(vm_name)
+
+ vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
#Remove the VM. Does not destroy disks.
(job_path, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_())
self.check_ret_val(ret_val, job_path)
- if destroy_disks:
- #Delete associated vhd disk files.
- for disk in disk_files:
- LOG.debug(_("Deleting disk file: %(disk)s") % locals())
- self._delete_file(disk)
-
- return volumes_drives_list
-
- def _delete_file(self, path):
- f = self._conn_cimv2.query("Select * from CIM_DataFile where "
- "Name = '%s'" % path.replace("'", "''"))[0]
- f.Delete()
-
def check_ret_val(self, ret_val, job_path, success_values=[0]):
if ret_val == constants.WMI_JOB_STATUS_STARTED:
self._wait_for_job(job_path)
diff --git a/nova/virt/hyperv/volumeops.py b/nova/virt/hyperv/volumeops.py
index a7e56b739..c665cbeb7 100644
--- a/nova/virt/hyperv/volumeops.py
+++ b/nova/virt/hyperv/volumeops.py
@@ -68,42 +68,25 @@ class VolumeOps(object):
else:
return volumeutils.VolumeUtils()
- def attach_boot_volume(self, block_device_info, vm_name):
- """Attach the boot volume to the IDE controller."""
-
- LOG.debug(_("block device info: %s"), block_device_info)
- ebs_root = driver.block_device_info_get_mapping(
- block_device_info)[0]
-
- connection_info = ebs_root['connection_info']
- data = connection_info['data']
- target_lun = data['target_lun']
- target_iqn = data['target_iqn']
- target_portal = data['target_portal']
- self._volutils.login_storage_target(target_lun, target_iqn,
- target_portal)
- try:
- #Getting the mounted disk
- mounted_disk_path = self._get_mounted_disk_from_lun(target_iqn,
- target_lun)
- #Find the IDE controller for the vm.
- ctrller_path = self._vmutils.get_vm_ide_controller(vm_name, 0)
- #Attaching to the same slot as the VHD disk file
- self._vmutils.attach_volume_to_controller(vm_name,
- ctrller_path, 0,
- mounted_disk_path)
- except Exception as exn:
- LOG.exception(_('Attach boot from volume failed: %s'), exn)
- self._volutils.logout_storage_target(target_iqn)
- raise vmutils.HyperVException(
- _('Unable to attach boot volume to instance %s') % vm_name)
-
- def volume_in_mapping(self, mount_device, block_device_info):
- return self._volutils.volume_in_mapping(mount_device,
+ def ebs_root_in_block_devices(self, block_device_info):
+ return self._volutils.volume_in_mapping(self._default_root_device,
block_device_info)
- def attach_volume(self, connection_info, instance_name):
- """Attach a volume to the SCSI controller."""
+ def attach_volumes(self, block_device_info, instance_name, ebs_root):
+ mapping = driver.block_device_info_get_mapping(block_device_info)
+
+ if ebs_root:
+ self.attach_volume(mapping[0]['connection_info'],
+ instance_name, True)
+ mapping = mapping[1:]
+ for vol in mapping:
+ self.attach_volume(vol['connection_info'], instance_name)
+
+ def attach_volume(self, connection_info, instance_name, ebs_root=False):
+ """
+ Attach a volume to the SCSI controller or to the IDE controller if
+ ebs_root is True
+ """
LOG.debug(_("Attach_volume: %(connection_info)s to %(instance_name)s")
% locals())
data = connection_info['data']
@@ -116,10 +99,19 @@ class VolumeOps(object):
#Getting the mounted disk
mounted_disk_path = self._get_mounted_disk_from_lun(target_iqn,
target_lun)
- #Find the SCSI controller for the vm
- ctrller_path = self._vmutils.get_vm_iscsi_controller(instance_name)
- slot = self._get_free_controller_slot(ctrller_path)
+ if ebs_root:
+ #Find the IDE controller for the vm.
+ ctrller_path = self._vmutils.get_vm_ide_controller(
+ instance_name, 0)
+ #Attaching to the first slot
+ slot = 0
+ else:
+ #Find the SCSI controller for the vm
+ ctrller_path = self._vmutils.get_vm_iscsi_controller(
+ instance_name)
+ slot = self._get_free_controller_slot(ctrller_path)
+
self._vmutils.attach_volume_to_controller(instance_name,
ctrller_path,
slot,
@@ -134,6 +126,11 @@ class VolumeOps(object):
#Slots starts from 0, so the lenght of the disks gives us the free slot
return self._vmutils.get_attached_disks_count(scsi_controller_path)
+ def detach_volumes(self, block_device_info, instance_name):
+ mapping = driver.block_device_info_get_mapping(block_device_info)
+ for vol in mapping:
+ self.detach_volume(vol['connection_info'], instance_name)
+
def detach_volume(self, connection_info, instance_name):
"""Dettach a volume to the SCSI controller."""
LOG.debug(_("Detach_volume: %(connection_info)s "
@@ -192,6 +189,3 @@ class VolumeOps(object):
physical_drive_path)
#Logging out the target
self._volutils.execute_log_out(session_id)
-
- def get_default_root_device(self):
- return self._default_root_device