Merge "Fix attaching config drive issue on Hyper-V when migrate instances"
This commit is contained in:
commit
29d174e541
@ -58,6 +58,10 @@ class PathUtils(object):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'root.vhd')
|
||||
|
||||
def lookup_configdrive_path(self, instance_name):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'configdrive.iso')
|
||||
|
||||
def lookup_ephemeral_vhd_path(self, instance_name):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
if instance_path:
|
||||
|
@ -1508,7 +1508,39 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase,
|
||||
flavor, network_info)
|
||||
self._mox.VerifyAll()
|
||||
|
||||
def _test_finish_migration(self, power_on, ephemeral_storage=False):
|
||||
def _mock_attach_config_drive(self, instance, config_drive_format):
|
||||
instance['config_drive'] = True
|
||||
self._mox.StubOutWithMock(fake.PathUtils, 'lookup_configdrive_path')
|
||||
m = fake.PathUtils.lookup_configdrive_path(
|
||||
mox.Func(self._check_instance_name))
|
||||
|
||||
if config_drive_format in constants.DISK_FORMAT_MAP:
|
||||
m.AndReturn(self._test_instance_dir + '/configdrive.' +
|
||||
config_drive_format)
|
||||
else:
|
||||
m.AndReturn(None)
|
||||
|
||||
m = vmutils.VMUtils.attach_ide_drive(
|
||||
mox.Func(self._check_instance_name),
|
||||
mox.IsA(str),
|
||||
mox.IsA(int),
|
||||
mox.IsA(int),
|
||||
mox.IsA(str))
|
||||
m.WithSideEffects(self._add_ide_disk).InAnyOrder()
|
||||
|
||||
def _verify_attach_config_drive(self, config_drive_format):
|
||||
if config_drive_format == constants.IDE_DISK_FORMAT.lower():
|
||||
self.assertEqual(self._instance_ide_disks[1],
|
||||
self._test_instance_dir + '/configdrive.' +
|
||||
config_drive_format)
|
||||
elif config_drive_format == constants.IDE_DVD_FORMAT.lower():
|
||||
self.assertEqual(self._instance_ide_dvds[0],
|
||||
self._test_instance_dir + '/configdrive.' +
|
||||
config_drive_format)
|
||||
|
||||
def _test_finish_migration(self, power_on, ephemeral_storage=False,
|
||||
config_drive=False,
|
||||
config_drive_format='iso'):
|
||||
self._instance_data = self._get_instance_data()
|
||||
instance = db.instance_create(self._context, self._instance_data)
|
||||
instance['system_metadata'] = {}
|
||||
@ -1557,11 +1589,17 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase,
|
||||
vmutils.VMUtils.set_vm_state(mox.Func(self._check_instance_name),
|
||||
constants.HYPERV_VM_STATE_ENABLED)
|
||||
|
||||
if config_drive:
|
||||
self._mock_attach_config_drive(instance, config_drive_format)
|
||||
|
||||
self._mox.ReplayAll()
|
||||
self._conn.finish_migration(self._context, None, instance, "",
|
||||
network_info, None, False, None, power_on)
|
||||
self._mox.VerifyAll()
|
||||
|
||||
if config_drive:
|
||||
self._verify_attach_config_drive(config_drive_format)
|
||||
|
||||
def test_finish_migration_power_on(self):
|
||||
self._test_finish_migration(True)
|
||||
|
||||
@ -1571,6 +1609,14 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase,
|
||||
def test_finish_migration_with_ephemeral_storage(self):
|
||||
self._test_finish_migration(False, ephemeral_storage=True)
|
||||
|
||||
def test_finish_migration_attach_config_drive_iso(self):
|
||||
self._test_finish_migration(False, config_drive=True,
|
||||
config_drive_format=constants.IDE_DVD_FORMAT.lower())
|
||||
|
||||
def test_finish_migration_attach_config_drive_vhd(self):
|
||||
self._test_finish_migration(False, config_drive=True,
|
||||
config_drive_format=constants.IDE_DISK_FORMAT.lower())
|
||||
|
||||
def test_confirm_migration(self):
|
||||
self._instance_data = self._get_instance_data()
|
||||
instance = db.instance_create(self._context, self._instance_data)
|
||||
@ -1582,7 +1628,9 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase,
|
||||
self._conn.confirm_migration(None, instance, network_info)
|
||||
self._mox.VerifyAll()
|
||||
|
||||
def _test_finish_revert_migration(self, power_on, ephemeral_storage=False):
|
||||
def _test_finish_revert_migration(self, power_on, ephemeral_storage=False,
|
||||
config_drive=False,
|
||||
config_drive_format='iso'):
|
||||
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)
|
||||
@ -1620,12 +1668,18 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase,
|
||||
vmutils.VMUtils.set_vm_state(mox.Func(self._check_instance_name),
|
||||
constants.HYPERV_VM_STATE_ENABLED)
|
||||
|
||||
if config_drive:
|
||||
self._mock_attach_config_drive(instance, config_drive_format)
|
||||
|
||||
self._mox.ReplayAll()
|
||||
self._conn.finish_revert_migration(self._context, instance,
|
||||
network_info, None,
|
||||
power_on)
|
||||
self._mox.VerifyAll()
|
||||
|
||||
if config_drive:
|
||||
self._verify_attach_config_drive(config_drive_format)
|
||||
|
||||
def test_finish_revert_migration_power_on(self):
|
||||
self._test_finish_revert_migration(True)
|
||||
|
||||
@ -1641,6 +1695,14 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase,
|
||||
def test_finish_revert_migration_with_ephemeral_storage(self):
|
||||
self._test_finish_revert_migration(False, ephemeral_storage=True)
|
||||
|
||||
def test_finish_revert_migration_attach_config_drive_iso(self):
|
||||
self._test_finish_revert_migration(False, config_drive=True,
|
||||
config_drive_format=constants.IDE_DVD_FORMAT.lower())
|
||||
|
||||
def test_finish_revert_migration_attach_config_drive_vhd(self):
|
||||
self._test_finish_revert_migration(False, config_drive=True,
|
||||
config_drive_format=constants.IDE_DISK_FORMAT.lower())
|
||||
|
||||
def test_plug_vifs(self):
|
||||
# Check to make sure the method raises NotImplementedError.
|
||||
self.assertRaises(NotImplementedError,
|
||||
|
40
nova/tests/virt/hyperv/test_migrationops.py
Normal file
40
nova/tests/virt/hyperv/test_migrationops.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from nova import test
|
||||
from nova.tests import fake_instance
|
||||
from nova.virt.hyperv import migrationops
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class MigrationOpsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V MigrationOps class."""
|
||||
|
||||
def setUp(self):
|
||||
super(MigrationOpsTestCase, self).setUp()
|
||||
self.context = 'fake-context'
|
||||
self.flags(force_hyperv_utils_v1=True, group='hyperv')
|
||||
self.flags(force_volumeutils_v1=True, group='hyperv')
|
||||
self._migrationops = migrationops.MigrationOps()
|
||||
|
||||
def test_check_and_attach_config_drive_unknown_path(self):
|
||||
instance = fake_instance.fake_instance_obj(self.context)
|
||||
instance.config_drive = 'True'
|
||||
self._migrationops._pathutils.lookup_configdrive_path = mock.MagicMock(
|
||||
return_value=None)
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._migrationops._check_and_attach_config_drive,
|
||||
instance)
|
56
nova/tests/virt/hyperv/test_pathutils.py
Normal file
56
nova/tests/virt/hyperv/test_pathutils.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import pathutils
|
||||
|
||||
|
||||
class PathUtilsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V PathUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
self.fake_instance_dir = 'C:/fake_instance_dir'
|
||||
self.fake_instance_name = 'fake_instance_name'
|
||||
self._pathutils = pathutils.PathUtils()
|
||||
super(PathUtilsTestCase, self).setUp()
|
||||
|
||||
def _mock_lookup_configdrive_path(self, ext):
|
||||
self._pathutils.get_instance_dir = mock.MagicMock(
|
||||
return_value=self.fake_instance_dir)
|
||||
|
||||
def mock_exists(*args, **kwargs):
|
||||
path = args[0]
|
||||
return True if path[(path.rfind('.') + 1):] == ext else False
|
||||
self._pathutils.exists = mock_exists
|
||||
configdrive_path = self._pathutils.lookup_configdrive_path(
|
||||
self.fake_instance_name)
|
||||
return configdrive_path
|
||||
|
||||
def test_lookup_configdrive_path(self):
|
||||
for format_ext in constants.DISK_FORMAT_MAP:
|
||||
configdrive_path = self._mock_lookup_configdrive_path(format_ext)
|
||||
self.assertEqual(configdrive_path,
|
||||
self.fake_instance_dir + '/configdrive.' +
|
||||
format_ext)
|
||||
|
||||
def test_lookup_configdrive_path_non_exist(self):
|
||||
self._pathutils.get_instance_dir = mock.MagicMock(
|
||||
return_value=self.fake_instance_dir)
|
||||
self._pathutils.exists = mock.MagicMock(return_value=False)
|
||||
configdrive_path = self._pathutils.lookup_configdrive_path(
|
||||
self.fake_instance_name)
|
||||
self.assertIsNone(configdrive_path)
|
38
nova/tests/virt/hyperv/test_vmops.py
Normal file
38
nova/tests/virt/hyperv/test_vmops.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests import fake_instance
|
||||
from nova.virt.hyperv import vmops
|
||||
|
||||
|
||||
class VMOpsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V VMOps class."""
|
||||
|
||||
def __init__(self, test_case_name):
|
||||
super(VMOpsTestCase, self).__init__(test_case_name)
|
||||
|
||||
def setUp(self):
|
||||
super(VMOpsTestCase, self).setUp()
|
||||
self.context = 'fake-context'
|
||||
self.flags(force_hyperv_utils_v1=True, group='hyperv')
|
||||
self.flags(force_volumeutils_v1=True, group='hyperv')
|
||||
self._vmops = vmops.VMOps()
|
||||
|
||||
def test_attach_config_drive(self):
|
||||
instance = fake_instance.fake_instance_obj(self.context)
|
||||
self.assertRaises(exception.InvalidDiskFormat,
|
||||
self._vmops.attach_config_drive,
|
||||
instance, 'C:/fake_instance_dir/configdrive.xxx')
|
@ -66,7 +66,14 @@ VM_SUMMARY_MEMORY_USAGE = 103
|
||||
VM_SUMMARY_UPTIME = 105
|
||||
|
||||
IDE_DISK = "VHD"
|
||||
IDE_DISK_FORMAT = IDE_DISK
|
||||
IDE_DVD = "DVD"
|
||||
IDE_DVD_FORMAT = "ISO"
|
||||
|
||||
DISK_FORMAT_MAP = {
|
||||
IDE_DISK_FORMAT.lower(): IDE_DISK,
|
||||
IDE_DVD_FORMAT.lower(): IDE_DVD
|
||||
}
|
||||
|
||||
DISK_FORMAT_VHD = "VHD"
|
||||
DISK_FORMAT_VHDX = "VHDX"
|
||||
|
@ -22,6 +22,7 @@ from nova.i18n import _
|
||||
from nova.openstack.common import excutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import units
|
||||
from nova.virt import configdrive
|
||||
from nova.virt.hyperv import imagecache
|
||||
from nova.virt.hyperv import utilsfactory
|
||||
from nova.virt.hyperv import vmops
|
||||
@ -143,6 +144,17 @@ class MigrationOps(object):
|
||||
instance_name)
|
||||
self._pathutils.rename(revert_path, instance_path)
|
||||
|
||||
def _check_and_attach_config_drive(self, instance):
|
||||
if configdrive.required_by(instance):
|
||||
configdrive_path = self._pathutils.lookup_configdrive_path(
|
||||
instance.name)
|
||||
if configdrive_path:
|
||||
self._vmops.attach_config_drive(instance, configdrive_path)
|
||||
else:
|
||||
raise vmutils.HyperVException(
|
||||
_("Config drive is required by instance: %s, "
|
||||
"but it does not exist.") % instance.name)
|
||||
|
||||
def finish_revert_migration(self, context, instance, network_info,
|
||||
block_device_info=None, power_on=True):
|
||||
LOG.debug("finish_revert_migration called", instance=instance)
|
||||
@ -160,6 +172,8 @@ class MigrationOps(object):
|
||||
self._vmops.create_instance(instance, network_info, block_device_info,
|
||||
root_vhd_path, eph_vhd_path)
|
||||
|
||||
self._check_and_attach_config_drive(instance)
|
||||
|
||||
if power_on:
|
||||
self._vmops.power_on(instance)
|
||||
|
||||
@ -268,5 +282,8 @@ class MigrationOps(object):
|
||||
|
||||
self._vmops.create_instance(instance, network_info, block_device_info,
|
||||
root_vhd_path, eph_vhd_path)
|
||||
|
||||
self._check_and_attach_config_drive(instance)
|
||||
|
||||
if power_on:
|
||||
self._vmops.power_on(instance)
|
||||
|
@ -21,6 +21,7 @@ from oslo.config import cfg
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import utils
|
||||
from nova.virt.hyperv import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -132,6 +133,15 @@ class PathUtils(object):
|
||||
def lookup_root_vhd_path(self, instance_name):
|
||||
return self._lookup_vhd_path(instance_name, self.get_root_vhd_path)
|
||||
|
||||
def lookup_configdrive_path(self, instance_name):
|
||||
configdrive_path = None
|
||||
for format_ext in constants.DISK_FORMAT_MAP:
|
||||
test_path = self.get_configdrive_path(instance_name, format_ext)
|
||||
if self.exists(test_path):
|
||||
configdrive_path = test_path
|
||||
break
|
||||
return configdrive_path
|
||||
|
||||
def lookup_ephemeral_vhd_path(self, instance_name):
|
||||
return self._lookup_vhd_path(instance_name,
|
||||
self.get_ephemeral_vhd_path)
|
||||
@ -140,6 +150,10 @@ class PathUtils(object):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'root.' + format_ext.lower())
|
||||
|
||||
def get_configdrive_path(self, instance_name, format_ext):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'configdrive.' + format_ext.lower())
|
||||
|
||||
def get_ephemeral_vhd_path(self, instance_name, format_ext):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'ephemeral.' + format_ext.lower())
|
||||
|
@ -246,8 +246,10 @@ class VMOps(object):
|
||||
root_vhd_path, eph_vhd_path)
|
||||
|
||||
if configdrive.required_by(instance):
|
||||
self._create_config_drive(instance, injected_files,
|
||||
admin_password)
|
||||
configdrive_path = self._create_config_drive(instance,
|
||||
injected_files,
|
||||
admin_password)
|
||||
self.attach_config_drive(instance, configdrive_path)
|
||||
|
||||
self.power_on(instance)
|
||||
except Exception:
|
||||
@ -327,7 +329,6 @@ class VMOps(object):
|
||||
e, instance=instance)
|
||||
|
||||
if not CONF.hyperv.config_drive_cdrom:
|
||||
drive_type = constants.IDE_DISK
|
||||
configdrive_path = os.path.join(instance_path,
|
||||
'configdrive.vhd')
|
||||
utils.execute(CONF.hyperv.qemu_img_cmd,
|
||||
@ -341,11 +342,19 @@ class VMOps(object):
|
||||
attempts=1)
|
||||
self._pathutils.remove(configdrive_path_iso)
|
||||
else:
|
||||
drive_type = constants.IDE_DVD
|
||||
configdrive_path = configdrive_path_iso
|
||||
|
||||
self._vmutils.attach_ide_drive(instance['name'], configdrive_path,
|
||||
1, 0, drive_type)
|
||||
return configdrive_path
|
||||
|
||||
def attach_config_drive(self, instance, configdrive_path):
|
||||
configdrive_ext = configdrive_path[(configdrive_path.rfind('.') + 1):]
|
||||
# Do the attach here and if there is a certain file format that isn't
|
||||
# supported in constants.DISK_FORMAT_MAP then bomb out.
|
||||
try:
|
||||
self._vmutils.attach_ide_drive(instance.name, configdrive_path,
|
||||
1, 0, constants.DISK_FORMAT_MAP[configdrive_ext])
|
||||
except KeyError:
|
||||
raise exception.InvalidDiskFormat(disk_format=configdrive_ext)
|
||||
|
||||
def _disconnect_volumes(self, volume_drives):
|
||||
for volume_drive in volume_drives:
|
||||
|
Loading…
Reference in New Issue
Block a user