Merge "Support the nova evacuate spawn semantic"
This commit is contained in:
commit
aa077c2ea8
|
@ -37,6 +37,7 @@ def cna(mac):
|
||||||
class TestNetwork(test.TestCase):
|
class TestNetwork(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestNetwork, self).setUp()
|
super(TestNetwork, self).setUp()
|
||||||
|
self.flags(host='host1')
|
||||||
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
||||||
|
|
||||||
self.mock_lpar_wrap = mock.MagicMock()
|
self.mock_lpar_wrap = mock.MagicMock()
|
||||||
|
@ -181,6 +182,66 @@ class TestNetwork(test.TestCase):
|
||||||
# The create should have only been called once.
|
# The create should have only been called once.
|
||||||
self.assertEqual(1, mock_vm_crt.call_count)
|
self.assertEqual(1, mock_vm_crt.call_count)
|
||||||
|
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
|
def test_plug_vifs_diff_host(self, mock_vm_get, mock_vm_crt):
|
||||||
|
"""Tests that crt vif handles bad inst.host value."""
|
||||||
|
inst = powervm.TEST_INST1
|
||||||
|
# Set this up as a different host from the inst.host
|
||||||
|
self.flags(host='host2')
|
||||||
|
|
||||||
|
# Mock up the CNA response. Only doing one for simplicity
|
||||||
|
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
||||||
|
|
||||||
|
# Mock up the network info.
|
||||||
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
||||||
|
|
||||||
|
# Run method
|
||||||
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
||||||
|
'host_uuid')
|
||||||
|
with mock.patch.object(inst, 'save') as mock_inst_save:
|
||||||
|
p_vifs.execute(self.mock_lpar_wrap)
|
||||||
|
|
||||||
|
# The create should have only been called once.
|
||||||
|
self.assertEqual(1, mock_vm_crt.call_count)
|
||||||
|
# Should have called save to save the new host and then changed it back
|
||||||
|
self.assertEqual(2, mock_inst_save.call_count)
|
||||||
|
self.assertEqual('host1', inst.host)
|
||||||
|
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
|
def test_plug_vifs_diff_host_except(self, mock_vm_get, mock_vm_crt):
|
||||||
|
"""Tests that crt vif handles bad inst.host value.
|
||||||
|
|
||||||
|
This test ensures that if we get a timeout exception we still reset
|
||||||
|
the inst.host value back to the original value
|
||||||
|
"""
|
||||||
|
inst = powervm.TEST_INST1
|
||||||
|
# Set this up as a different host from the inst.host
|
||||||
|
self.flags(host='host2')
|
||||||
|
|
||||||
|
# Mock up the CNA response. Only doing one for simplicity
|
||||||
|
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
||||||
|
|
||||||
|
# Mock up the network info.
|
||||||
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
||||||
|
|
||||||
|
# Ensure that an exception is raised by a timeout.
|
||||||
|
mock_vm_crt.side_effect = eventlet.timeout.Timeout()
|
||||||
|
|
||||||
|
# Run method
|
||||||
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
||||||
|
'host_uuid')
|
||||||
|
with mock.patch.object(inst, 'save') as mock_inst_save:
|
||||||
|
self.assertRaises(exception.VirtualInterfaceCreateException,
|
||||||
|
p_vifs.execute, self.mock_lpar_wrap)
|
||||||
|
|
||||||
|
# The create should have only been called once.
|
||||||
|
self.assertEqual(1, mock_vm_crt.call_count)
|
||||||
|
# Should have called save to save the new host and then changed it back
|
||||||
|
self.assertEqual(2, mock_inst_save.call_count)
|
||||||
|
self.assertEqual('host1', inst.host)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_secure_rmc_vif')
|
@mock.patch('nova_powervm.virt.powervm.vm.crt_secure_rmc_vif')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_secure_rmc_vswitch')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_secure_rmc_vswitch')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
||||||
|
|
|
@ -28,6 +28,7 @@ from nova.objects import block_device as bdmobj
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.tests.unit import fake_instance
|
from nova.tests.unit import fake_instance
|
||||||
from nova.virt import block_device as nova_virt_bdm
|
from nova.virt import block_device as nova_virt_bdm
|
||||||
|
from nova.virt import driver as virt_driver
|
||||||
from nova.virt import fake
|
from nova.virt import fake
|
||||||
import pypowervm.adapter as pvm_adp
|
import pypowervm.adapter as pvm_adp
|
||||||
import pypowervm.exceptions as pvm_exc
|
import pypowervm.exceptions as pvm_exc
|
||||||
|
@ -170,6 +171,39 @@ class TestPowerVMDriver(test.TestCase):
|
||||||
self.assertTrue(self.drv.instance_exists(mock.Mock()))
|
self.assertTrue(self.drv.instance_exists(mock.Mock()))
|
||||||
self.assertFalse(self.drv.instance_exists(mock.Mock()))
|
self.assertFalse(self.drv.instance_exists(mock.Mock()))
|
||||||
|
|
||||||
|
def test_instance_on_disk(self):
|
||||||
|
"""Validates the instance_on_disk method."""
|
||||||
|
|
||||||
|
@mock.patch.object(self.drv, '_is_booted_from_volume')
|
||||||
|
@mock.patch.object(self.drv, '_get_block_device_info')
|
||||||
|
@mock.patch.object(self.disk_dvr, 'capabilities')
|
||||||
|
@mock.patch.object(self.disk_dvr, 'get_disk_ref')
|
||||||
|
def inst_on_disk(mock_disk_ref, mock_capb, mock_block, mock_boot):
|
||||||
|
# Test boot from volume.
|
||||||
|
mock_boot.return_value = True
|
||||||
|
self.assertTrue(self.drv.instance_on_disk(self.inst))
|
||||||
|
|
||||||
|
mock_boot.return_value = False
|
||||||
|
# Disk driver is shared storage and can find the disk
|
||||||
|
mock_capb['shared_storage'] = True
|
||||||
|
mock_disk_ref.return_value = 'disk_reference'
|
||||||
|
self.assertTrue(self.drv.instance_on_disk(self.inst))
|
||||||
|
|
||||||
|
# Disk driver can't find it
|
||||||
|
mock_disk_ref.return_value = None
|
||||||
|
self.assertFalse(self.drv.instance_on_disk(self.inst))
|
||||||
|
|
||||||
|
# Disk driver exception
|
||||||
|
mock_disk_ref.side_effect = ValueError('Bad disk')
|
||||||
|
self.assertFalse(self.drv.instance_on_disk(self.inst))
|
||||||
|
mock_disk_ref.side_effect = None
|
||||||
|
|
||||||
|
# Not on shared storage
|
||||||
|
mock_capb['shared_storage'] = False
|
||||||
|
self.assertFalse(self.drv.instance_on_disk(self.inst))
|
||||||
|
|
||||||
|
inst_on_disk()
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.tasks.storage.'
|
@mock.patch('nova_powervm.virt.powervm.tasks.storage.'
|
||||||
'CreateAndConnectCfgDrive.execute')
|
'CreateAndConnectCfgDrive.execute')
|
||||||
@mock.patch('nova_powervm.virt.powervm.tasks.storage.ConnectVolume'
|
@mock.patch('nova_powervm.virt.powervm.tasks.storage.ConnectVolume'
|
||||||
|
@ -405,6 +439,49 @@ class TestPowerVMDriver(test.TestCase):
|
||||||
|
|
||||||
self.scrub_stg.assert_called_with([9], self.stg_ftsk, lpars_exist=True)
|
self.scrub_stg.assert_called_with([9], self.stg_ftsk, lpars_exist=True)
|
||||||
|
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.tasks.storage.'
|
||||||
|
'CreateAndConnectCfgDrive.execute')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.tasks.storage.ConnectVolume'
|
||||||
|
'.execute')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.tasks.storage.FindDisk'
|
||||||
|
'.execute')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.driver.PowerVMDriver.'
|
||||||
|
'_is_booted_from_volume')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.tasks.network.PlugMgmtVif.execute')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.tasks.network.PlugVifs.execute')
|
||||||
|
@mock.patch('nova.virt.configdrive.required_by')
|
||||||
|
@mock.patch('nova.objects.flavor.Flavor.get_by_id')
|
||||||
|
@mock.patch('pypowervm.tasks.power.power_on')
|
||||||
|
def test_spawn_recreate(
|
||||||
|
self, mock_pwron, mock_get_flv, mock_cfg_drv, mock_plug_vifs,
|
||||||
|
mock_plug_mgmt_vif, mock_boot_from_vol, mock_find_disk,
|
||||||
|
mock_conn_vol, mock_crt_cfg_drv):
|
||||||
|
"""Validates the 'recreate' spawn flow.
|
||||||
|
|
||||||
|
Uses a basic disk image, attaching networks and powering on.
|
||||||
|
"""
|
||||||
|
# Set up the mocks to the tasks.
|
||||||
|
mock_get_flv.return_value = self.inst.get_flavor()
|
||||||
|
mock_cfg_drv.return_value = False
|
||||||
|
mock_boot_from_vol.return_value = False
|
||||||
|
self.inst.task_state = task_states.REBUILD_SPAWNING
|
||||||
|
# Invoke the method.
|
||||||
|
self.drv.spawn('context', self.inst, powervm.EMPTY_IMAGE,
|
||||||
|
'injected_files', 'admin_password')
|
||||||
|
|
||||||
|
# Assert the correct tasks were called
|
||||||
|
self.assertTrue(mock_plug_vifs.called)
|
||||||
|
self.assertTrue(mock_plug_mgmt_vif.called)
|
||||||
|
self.assertTrue(mock_find_disk.called)
|
||||||
|
self.crt_lpar.assert_called_with(
|
||||||
|
self.apt, self.drv.host_wrapper, self.inst, self.inst.get_flavor())
|
||||||
|
self.assertTrue(mock_pwron.called)
|
||||||
|
self.assertFalse(mock_pwron.call_args[1]['synchronous'])
|
||||||
|
# Assert that tasks that are not supposed to be called are not called
|
||||||
|
self.assertFalse(mock_conn_vol.called)
|
||||||
|
self.assertFalse(mock_crt_cfg_drv.called)
|
||||||
|
self.scrub_stg.assert_called_with([9], self.stg_ftsk, lpars_exist=True)
|
||||||
|
|
||||||
@mock.patch('nova.virt.block_device.DriverVolumeBlockDevice.save')
|
@mock.patch('nova.virt.block_device.DriverVolumeBlockDevice.save')
|
||||||
@mock.patch('nova_powervm.virt.powervm.tasks.network.PlugMgmtVif.execute')
|
@mock.patch('nova_powervm.virt.powervm.tasks.network.PlugMgmtVif.execute')
|
||||||
@mock.patch('nova_powervm.virt.powervm.tasks.network.PlugVifs.execute')
|
@mock.patch('nova_powervm.virt.powervm.tasks.network.PlugVifs.execute')
|
||||||
|
@ -1486,3 +1563,10 @@ class TestPowerVMDriver(test.TestCase):
|
||||||
drivers = self.drv._build_vol_drivers('context', 'instance')
|
drivers = self.drv._build_vol_drivers('context', 'instance')
|
||||||
|
|
||||||
self.assertEqual(['drv0', 'drv1'], drivers)
|
self.assertEqual(['drv0', 'drv1'], drivers)
|
||||||
|
|
||||||
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
||||||
|
@mock.patch.object(virt_driver, 'get_block_device_info')
|
||||||
|
def test_get_block_device_info(self, mock_bk_dev, mock_bdml):
|
||||||
|
mock_bk_dev.return_value = 'info'
|
||||||
|
self.assertEqual('info',
|
||||||
|
self.drv._get_block_device_info('ctx', self.inst))
|
||||||
|
|
|
@ -85,6 +85,12 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||||
|
|
||||||
"""PowerVM Implementation of Compute Driver."""
|
"""PowerVM Implementation of Compute Driver."""
|
||||||
|
|
||||||
|
capabilities = {
|
||||||
|
"has_imagecache": False,
|
||||||
|
"supports_recreate": True,
|
||||||
|
"supports_migrate_to_same_host": False
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, virtapi):
|
def __init__(self, virtapi):
|
||||||
super(PowerVMDriver, self).__init__(virtapi)
|
super(PowerVMDriver, self).__init__(virtapi)
|
||||||
|
|
||||||
|
@ -220,6 +226,44 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||||
"""Return the current CPU state of the host."""
|
"""Return the current CPU state of the host."""
|
||||||
return self.host_cpu_stats.get_host_cpu_stats()
|
return self.host_cpu_stats.get_host_cpu_stats()
|
||||||
|
|
||||||
|
def instance_on_disk(self, instance):
|
||||||
|
"""Checks access of instance files on the host.
|
||||||
|
|
||||||
|
:param instance: nova.objects.instance.Instance to lookup
|
||||||
|
|
||||||
|
Returns True if files of an instance with the supplied ID accessible on
|
||||||
|
the host, False otherwise.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Used in rebuild for HA implementation and required for validation
|
||||||
|
of access to instance shared disk files
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If the instance is booted from volume then we shouldn't
|
||||||
|
# really care if instance "disks" are on shared storage.
|
||||||
|
context = ctx.get_admin_context()
|
||||||
|
block_device_info = self._get_block_device_info(context, instance)
|
||||||
|
if self._is_booted_from_volume(block_device_info):
|
||||||
|
LOG.debug('Instance booted from volume.', instance=instance)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# If configured for shared storage, see if we can find the disks
|
||||||
|
if self.disk_dvr.capabilities['shared_storage']:
|
||||||
|
LOG.debug('Looking for instance disks on shared storage.',
|
||||||
|
instance=instance)
|
||||||
|
# Try to get a reference to the disk
|
||||||
|
try:
|
||||||
|
if self.disk_dvr.get_disk_ref(instance,
|
||||||
|
disk_dvr.DiskType.BOOT):
|
||||||
|
LOG.debug('Disks found on shared storage.',
|
||||||
|
instance=instance)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
|
||||||
|
LOG.debug('Instance disks not found on this host.', instance=instance)
|
||||||
|
return False
|
||||||
|
|
||||||
def spawn(self, context, instance, image_meta, injected_files,
|
def spawn(self, context, instance, image_meta, injected_files,
|
||||||
admin_password, network_info=None, block_device_info=None,
|
admin_password, network_info=None, block_device_info=None,
|
||||||
flavor=None):
|
flavor=None):
|
||||||
|
@ -232,6 +276,11 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||||
cleaned up, and the virtualization platform should be in the state
|
cleaned up, and the virtualization platform should be in the state
|
||||||
that it was before this call began.
|
that it was before this call began.
|
||||||
|
|
||||||
|
Spawn can be called while deploying an instance for the first time or
|
||||||
|
it can be called to recreate an instance that was shelved or during
|
||||||
|
evacuation. We have to be careful to handle all these cases. During
|
||||||
|
evacuation, when on shared storage, the image_meta will be empty.
|
||||||
|
|
||||||
:param context: security context
|
:param context: security context
|
||||||
:param instance: Instance object as returned by DB layer.
|
:param instance: Instance object as returned by DB layer.
|
||||||
This function should use the data there to guide
|
This function should use the data there to guide
|
||||||
|
@ -276,11 +325,17 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||||
|
|
||||||
# Only add the image disk if this is from Glance.
|
# Only add the image disk if this is from Glance.
|
||||||
if not self._is_booted_from_volume(block_device_info):
|
if not self._is_booted_from_volume(block_device_info):
|
||||||
# Creates the boot image.
|
|
||||||
flow_spawn.add(tf_stg.CreateDiskForImg(
|
|
||||||
self.disk_dvr, context, instance, image_meta,
|
|
||||||
disk_size=flavor.root_gb))
|
|
||||||
|
|
||||||
|
# If a rebuild, just hookup the existing disk on shared storage.
|
||||||
|
if (instance.task_state == task_states.REBUILD_SPAWNING and
|
||||||
|
'id' not in image_meta):
|
||||||
|
flow_spawn.add(tf_stg.FindDisk(
|
||||||
|
self.disk_dvr, context, instance, disk_dvr.DiskType.BOOT))
|
||||||
|
else:
|
||||||
|
# Creates the boot image.
|
||||||
|
flow_spawn.add(tf_stg.CreateDiskForImg(
|
||||||
|
self.disk_dvr, context, instance, image_meta,
|
||||||
|
disk_size=flavor.root_gb))
|
||||||
# Connects up the disk to the LPAR
|
# Connects up the disk to the LPAR
|
||||||
flow_spawn.add(tf_stg.ConnectDisk(self.disk_dvr, context, instance,
|
flow_spawn.add(tf_stg.ConnectDisk(self.disk_dvr, context, instance,
|
||||||
stg_ftsk=stg_ftsk))
|
stg_ftsk=stg_ftsk))
|
||||||
|
@ -356,6 +411,13 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||||
stg_ftsk=stg_ftsk):
|
stg_ftsk=stg_ftsk):
|
||||||
flow.add(tf_stg.DisconnectVolume(vol_drv))
|
flow.add(tf_stg.DisconnectVolume(vol_drv))
|
||||||
|
|
||||||
|
def _get_block_device_info(self, context, instance):
|
||||||
|
"""Retrieves the instance's block_device_info."""
|
||||||
|
|
||||||
|
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
|
||||||
|
context, instance.uuid)
|
||||||
|
return driver.get_block_device_info(instance, bdms)
|
||||||
|
|
||||||
def _is_booted_from_volume(self, block_device_info):
|
def _is_booted_from_volume(self, block_device_info):
|
||||||
"""Determine whether the root device is listed in block_device_info.
|
"""Determine whether the root device is listed in block_device_info.
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,22 @@ class PlugVifs(task.Task):
|
||||||
instance=self.instance)
|
instance=self.instance)
|
||||||
raise exception.VirtualInterfaceCreateException()
|
raise exception.VirtualInterfaceCreateException()
|
||||||
|
|
||||||
|
# TODO(KYLEH): We're setting up to wait for an instance event. The
|
||||||
|
# event needs to come back to our compute manager so we need to ensure
|
||||||
|
# the instance.host is set to our host. We shouldn't need to do this
|
||||||
|
# but in the evacuate/recreate case it may reflect the old host.
|
||||||
|
# See: https://bugs.launchpad.net/nova/+bug/1535918
|
||||||
|
undo_host_change = False
|
||||||
|
if self.instance.host != CONF.host:
|
||||||
|
LOG.warning(_LW('Instance was not assigned to this host. '
|
||||||
|
'It was assigned to: %s'), self.instance.host,
|
||||||
|
instance=self.instance)
|
||||||
|
# Update the instance...
|
||||||
|
old_host = self.instance.host
|
||||||
|
self.instance.host = CONF.host
|
||||||
|
self.instance.save()
|
||||||
|
undo_host_change = True
|
||||||
|
|
||||||
# For the VIFs, run the creates (and wait for the events back)
|
# For the VIFs, run the creates (and wait for the events back)
|
||||||
try:
|
try:
|
||||||
with self.virt_api.wait_for_instance_event(
|
with self.virt_api.wait_for_instance_event(
|
||||||
|
@ -185,6 +201,12 @@ class PlugVifs(task.Task):
|
||||||
'%(sys)s'), {'sys': self.instance.name},
|
'%(sys)s'), {'sys': self.instance.name},
|
||||||
instance=self.instance)
|
instance=self.instance)
|
||||||
raise exception.VirtualInterfaceCreateException()
|
raise exception.VirtualInterfaceCreateException()
|
||||||
|
finally:
|
||||||
|
if undo_host_change:
|
||||||
|
LOG.info(_LI('Undoing temporary host assignment to instance.'),
|
||||||
|
instance=self.instance)
|
||||||
|
self.instance.host = old_host
|
||||||
|
self.instance.save()
|
||||||
|
|
||||||
# Return the list of created VIFs.
|
# Return the list of created VIFs.
|
||||||
return cna_w_list
|
return cna_w_list
|
||||||
|
|
Loading…
Reference in New Issue