fuel-plugin-xenserver/plugin_source/deployment_scripts/patchset/nova-neutron-race-condition...

369 lines
18 KiB
Diff

diff --git a/nova/tests/unit/virt/xenapi/test_vmops.py b/nova/tests/unit/virt/xenapi/test_vmops.py
index 58c64c0..33bec73 100644
--- a/nova/tests/unit/virt/xenapi/test_vmops.py
+++ b/nova/tests/unit/virt/xenapi/test_vmops.py
@@ -28,6 +28,7 @@ from nova import test
from nova.tests.unit import fake_flavor
from nova.tests.unit import fake_instance
from nova.tests.unit.virt.xenapi import stubs
+from nova import utils
from nova.virt import fake
from nova.virt.xenapi import agent as xenapi_agent
from nova.virt.xenapi.client import session as xenapi_session
@@ -315,10 +316,11 @@ class SpawnTestCase(VMOpsTestBase):
self.mox.StubOutWithMock(self.vmops.firewall_driver,
'apply_instance_filter')
self.mox.StubOutWithMock(self.vmops, '_update_last_dom_id')
+ self.mox.StubOutWithMock(self.vmops._session, 'call_xenapi')
def _test_spawn(self, name_label_param=None, block_device_info_param=None,
rescue=False, include_root_vdi=True, throw_exception=None,
- attach_pci_dev=False):
+ attach_pci_dev=False, neutron_exception=False):
self._stub_out_common()
instance = {"name": "dummy", "uuid": "fake_uuid"}
@@ -411,38 +413,55 @@ class SpawnTestCase(VMOpsTestBase):
step += 1
self.vmops._update_instance_progress(context, instance, step, steps)
- self.vmops._create_vifs(instance, vm_ref, network_info)
- self.vmops.firewall_driver.setup_basic_filtering(instance,
- network_info).AndRaise(NotImplementedError)
- self.vmops.firewall_driver.prepare_instance_filter(instance,
- network_info)
- step += 1
- self.vmops._update_instance_progress(context, instance, step, steps)
-
- if rescue:
- self.vmops._attach_orig_disks(instance, vm_ref)
+ if neutron_exception:
+ events = [('network-vif-plugged', 1)]
+ self.vmops._get_neutron_events(network_info,
+ True, True).AndReturn(events)
+ self.mox.StubOutWithMock(self.vmops, '_neutron_failed_callback')
+ self.mox.StubOutWithMock(self.vmops._virtapi,
+ 'wait_for_instance_event')
+ self.vmops._virtapi.wait_for_instance_event(instance, events,
+ deadline=300,
+ error_callback=self.vmops._neutron_failed_callback).\
+ AndRaise(exception.VirtualInterfaceCreateException)
+ else:
+ self.vmops._create_vifs(instance, vm_ref, network_info)
+ self.vmops.firewall_driver.setup_basic_filtering(instance,
+ network_info).AndRaise(NotImplementedError)
+ self.vmops.firewall_driver.prepare_instance_filter(instance,
+ network_info)
step += 1
- self.vmops._update_instance_progress(context, instance, step,
- steps)
- self.vmops._start(instance, vm_ref)
- self.vmops._wait_for_instance_to_start(instance, vm_ref)
- self.vmops._update_last_dom_id(vm_ref)
- step += 1
- self.vmops._update_instance_progress(context, instance, step, steps)
-
- self.vmops._configure_new_instance_with_agent(instance, vm_ref,
- injected_files, admin_password)
- self.vmops._remove_hostname(instance, vm_ref)
- step += 1
- self.vmops._update_instance_progress(context, instance, step, steps)
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+
+ if rescue:
+ self.vmops._attach_orig_disks(instance, vm_ref)
+ step += 1
+ self.vmops._update_instance_progress(context, instance, step,
+ steps)
+ start_pause = True
+ self.vmops._start(instance, vm_ref, start_pause=start_pause)
+ step += 1
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+ self.vmops.firewall_driver.apply_instance_filter(instance,
+ network_info)
+ step += 1
+ self.vmops._update_instance_progress(context, instance,
+ step, steps)
+ self.vmops._session.call_xenapi('VM.unpause', vm_ref)
+ self.vmops._wait_for_instance_to_start(instance, vm_ref)
+ self.vmops._update_last_dom_id(vm_ref)
+ self.vmops._configure_new_instance_with_agent(instance, vm_ref,
+ injected_files, admin_password)
+ self.vmops._remove_hostname(instance, vm_ref)
+ step += 1
+ last_call = self.vmops._update_instance_progress(context, instance,
+ step, steps)
- self.vmops.firewall_driver.apply_instance_filter(instance,
- network_info)
- step += 1
- last_call = self.vmops._update_instance_progress(context, instance,
- step, steps)
if throw_exception:
last_call.AndRaise(throw_exception)
+ if throw_exception or neutron_exception:
self.vmops._destroy(instance, vm_ref, network_info=network_info)
vm_utils.destroy_kernel_ramdisk(self.vmops._session, instance,
kernel_file, ramdisk_file)
@@ -469,11 +488,25 @@ class SpawnTestCase(VMOpsTestBase):
self.assertRaises(test.TestingException, self._test_spawn,
throw_exception=test.TestingException())
+ def test_spawn_with_neutron(self):
+ self.mox.StubOutWithMock(self.vmops, '_get_neutron_events')
+ events = [('network-vif-plugged', 1)]
+ network_info = "net_info"
+ self.vmops._get_neutron_events(network_info,
+ True, True).AndReturn(events)
+ self.mox.StubOutWithMock(self.vmops,
+ '_neutron_failed_callback')
+ self._test_spawn()
+
+ def test_spawn_with_neutron_exception(self):
+ self.mox.StubOutWithMock(self.vmops, '_get_neutron_events')
+ self.assertRaises(exception.VirtualInterfaceCreateException,
+ self._test_spawn, neutron_exception=True)
+
def _test_finish_migration(self, power_on=True, resize_instance=True,
throw_exception=None, booted_from_volume=False):
self._stub_out_common()
self.mox.StubOutWithMock(volumeops.VolumeOps, "connect_volume")
- self.mox.StubOutWithMock(self.vmops._session, 'call_xenapi')
self.mox.StubOutWithMock(vm_utils, "import_all_migrated_disks")
self.mox.StubOutWithMock(self.vmops, "_attach_mapped_block_devices")
@@ -541,12 +574,14 @@ class SpawnTestCase(VMOpsTestBase):
network_info)
if power_on:
- self.vmops._start(instance, vm_ref)
- self.vmops._wait_for_instance_to_start(instance, vm_ref)
- self.vmops._update_last_dom_id(vm_ref)
+ self.vmops._start(instance, vm_ref, start_pause=True)
self.vmops.firewall_driver.apply_instance_filter(instance,
network_info)
+ if power_on:
+ self.vmops._session.call_xenapi('VM.unpause', vm_ref)
+ self.vmops._wait_for_instance_to_start(instance, vm_ref)
+ self.vmops._update_last_dom_id(vm_ref)
last_call = self.vmops._update_instance_progress(context, instance,
step=5, total_steps=5)
@@ -704,6 +739,57 @@ class SpawnTestCase(VMOpsTestBase):
self.vmops._configure_new_instance_with_agent(instance, vm_ref,
None, None)
+ @mock.patch.object(utils, 'is_neutron', return_value=True)
+ def test_get_neutron_event(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = True
+ first_boot = True
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual("network-vif-plugged", events[0][0])
+ self.assertEqual(1, events[0][1])
+ self.assertEqual("network-vif-plugged", events[1][0])
+ self.assertEqual(3, events[1][1])
+
+ @mock.patch.object(utils, 'is_neutron', return_value=False)
+ def test_get_neutron_event_not_neutron_network(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = True
+ first_boot = True
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual([], events)
+
+ @mock.patch.object(utils, 'is_neutron', return_value=True)
+ def test_get_neutron_event_power_off(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = False
+ first_boot = True
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual([], events)
+
+ @mock.patch.object(utils, 'is_neutron', return_value=True)
+ def test_get_neutron_event_not_first_boot(self, mock_is_neutron):
+ network_info = [{"active": False, "id": 1},
+ {"active": True, "id": 2},
+ {"active": False, "id": 3},
+ {"id": 4}]
+ power_on = True
+ first_boot = False
+ events = self.vmops._get_neutron_events(network_info,
+ power_on, first_boot)
+ self.assertEqual([], events)
+
class DestroyTestCase(VMOpsTestBase):
def setUp(self):
diff --git a/nova/tests/unit/virt/xenapi/test_xenapi.py b/nova/tests/unit/virt/xenapi/test_xenapi.py
index 28b50ac..4847cfc 100644
--- a/nova/tests/unit/virt/xenapi/test_xenapi.py
+++ b/nova/tests/unit/virt/xenapi/test_xenapi.py
@@ -329,6 +329,12 @@ class XenAPIVMTestCase(stubs.XenAPITestBase):
virtual_size)
self.stubs.Set(vm_utils, '_safe_copy_vdi', fake_safe_copy_vdi)
+ def fake_update_instance_with_power_on(self,
+ vm_ref, instance, power_on):
+ self._update_last_dom_id(vm_ref)
+ self.stubs.Set(vmops.VMOps, '_update_instance_with_power_on',
+ fake_update_instance_with_power_on)
+
def tearDown(self):
fake_image.FakeImageService_reset()
super(XenAPIVMTestCase, self).tearDown()
@@ -1675,6 +1681,19 @@ class XenAPIMigrateInstance(stubs.XenAPITestBase):
self.stubs.Set(vmops.VMOps, '_inject_instance_metadata',
fake_inject_instance_metadata)
+ def fake_update_instance_with_power_on(self,
+ vm_ref, instance, power_on):
+ pass
+ self.stubs.Set(vmops.VMOps, '_update_instance_with_power_on',
+ fake_update_instance_with_power_on)
+
+ def _create_instance(self, **kw):
+ values = self.instance_values.copy()
+ values.update(kw)
+ instance = objects.Instance(context=self.context, **values)
+ instance.create()
+ return instance
+
def test_migrate_disk_and_power_off(self):
instance = db.instance_create(self.context, self.instance_values)
xenapi_fake.create_vm(instance['name'], 'Running')
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 65a41a3..1fbc15a 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -22,6 +22,7 @@ import functools
import time
import zlib
+import eventlet
from eventlet import greenthread
import netaddr
from oslo_config import cfg
@@ -321,7 +322,8 @@ class VMOps(object):
rescue=False, power_on=power_on, resize=resize_instance,
completed_callback=completed_callback)
- def _start(self, instance, vm_ref=None, bad_volumes_callback=None):
+ def _start(self, instance, vm_ref=None, bad_volumes_callback=None,
+ start_pause=False):
"""Power on a VM instance."""
vm_ref = vm_ref or self._get_vm_opaque_ref(instance)
LOG.debug("Starting instance", instance=instance)
@@ -339,7 +341,7 @@ class VMOps(object):
self._session.call_xenapi('VM.start_on', vm_ref,
self._session.host_ref,
- False, False)
+ start_pause, False)
# Allow higher-layers a chance to detach bad-volumes as well (in order
# to cleanup BDM entries and detach in Cinder)
@@ -548,14 +550,13 @@ class VMOps(object):
self._prepare_instance_filter(instance, network_info)
@step
- def boot_instance_step(undo_mgr, vm_ref):
+ def start_paused_instance_step(undo_mgr, vm_ref):
if power_on:
- self._start(instance, vm_ref)
- self._wait_for_instance_to_start(instance, vm_ref)
- self._update_last_dom_id(vm_ref)
+ self._start(instance, vm_ref, start_pause=True)
@step
- def configure_booted_instance_step(undo_mgr, vm_ref):
+ def boot_and_configure_instance_step(undo_mgr, vm_ref):
+ self._update_instance_with_power_on(vm_ref, instance, power_on)
if first_boot:
self._configure_new_instance_with_agent(instance, vm_ref,
injected_files, admin_password)
@@ -583,22 +584,61 @@ class VMOps(object):
attach_devices_step(undo_mgr, vm_ref, vdis, disk_image_type)
inject_instance_data_step(undo_mgr, vm_ref, vdis)
- setup_network_step(undo_mgr, vm_ref)
- if rescue:
- attach_orig_disks_step(undo_mgr, vm_ref)
-
- boot_instance_step(undo_mgr, vm_ref)
+ # if use neutron, prepare waiting event from neutron
+ timeout = CONF.vif_plugging_timeout
+ events = self._get_neutron_events(network_info,
+ power_on, first_boot)
+ try:
+ with self._virtapi.wait_for_instance_event(
+ instance, events, deadline=timeout,
+ error_callback=self._neutron_failed_callback):
+ LOG.debug("wait for instance event:%s", events)
+ setup_network_step(undo_mgr, vm_ref)
+ if rescue:
+ attach_orig_disks_step(undo_mgr, vm_ref)
+ start_paused_instance_step(undo_mgr, vm_ref)
+ except eventlet.timeout.Timeout:
+ self._handle_neutron_event_timeout(instance, undo_mgr)
- configure_booted_instance_step(undo_mgr, vm_ref)
apply_security_group_filters_step(undo_mgr)
-
+ boot_and_configure_instance_step(undo_mgr, vm_ref)
if completed_callback:
completed_callback()
except Exception:
msg = _("Failed to spawn, rolling back")
undo_mgr.rollback_and_reraise(msg=msg, instance=instance)
+ def _handle_neutron_event_timeout(self, instance, undo_mgr):
+ # We didn't get callback from Neutron within given time
+ LOG.warn(_LW('Timeout waiting for vif plugging callback'),
+ instance=instance)
+ if CONF.vif_plugging_is_fatal:
+ raise exception.VirtualInterfaceCreateException()
+
+ def _update_instance_with_power_on(self, vm_ref, instance, power_on):
+ if power_on:
+ LOG.debug("Update instance when power on", instance=instance)
+ self._session.VM.unpause(vm_ref)
+ self._wait_for_instance_to_start(instance, vm_ref)
+ self._update_last_dom_id(vm_ref)
+
+ def _neutron_failed_callback(self, event_name, instance):
+ LOG.warn(_LW('Neutron Reported failure on event %(event)s'),
+ {'event': event_name}, instance=instance)
+ if CONF.vif_plugging_is_fatal:
+ raise exception.VirtualInterfaceCreateException()
+
+ def _get_neutron_events(self, network_info, power_on, first_boot):
+ # Only get network-vif-plugged events with VIF's status is not active.
+ # With VIF whose status is active, neutron may not notify such event.
+ timeout = CONF.vif_plugging_timeout
+ if (utils.is_neutron() and power_on and timeout and first_boot):
+ return [('network-vif-plugged', vif['id'])
+ for vif in network_info if vif.get('active', True) is False]
+ else:
+ return []
+
def _attach_orig_disks(self, instance, vm_ref):
orig_vm_ref = vm_utils.lookup(self._session, instance['name'])
orig_vdi_refs = self._find_vdi_refs(orig_vm_ref,