Merge "Fix attaching config drive issue on Hyper-V when migrate instances"

This commit is contained in:
Jenkins 2014-08-08 14:40:30 +00:00 committed by Gerrit Code Review
commit 29d174e541
9 changed files with 255 additions and 8 deletions

View File

@ -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:

View File

@ -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,

View 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)

View 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)

View 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')

View File

@ -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"

View File

@ -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)

View File

@ -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())

View File

@ -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: