ffe092b112
CI tests frequently teardown by rolling back interface
attachments and then deleting the server, both of which
are asynchronous. If we're trying to detach an interface
on a guest that is gone, we don't need to log a traceback
exception in the logs, just let the InstanceNotFound
raise up to the ComputeManager which will handle the
error and log it appropriately.
Change-Id: I9428be0e6e5b640fdda00410817925001361fd2c
Closes-Bug: #1759979
(cherry picked from commit 5a42701f89
)
2126 lines
98 KiB
Python
2126 lines
98 KiB
Python
# Copyright 2013 OpenStack Foundation
|
|
# 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.
|
|
|
|
import base64
|
|
import uuid
|
|
import zlib
|
|
|
|
try:
|
|
import xmlrpclib
|
|
except ImportError:
|
|
import six.moves.xmlrpc_client as xmlrpclib
|
|
|
|
from eventlet import greenthread
|
|
import mock
|
|
from os_xenapi.client import host_xenstore
|
|
import six
|
|
|
|
from nova.compute import power_state
|
|
from nova.compute import task_states
|
|
from nova import context
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.objects import fields
|
|
from nova.pci import manager as pci_manager
|
|
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.tests import uuidsentinel as uuids
|
|
from nova import utils
|
|
from nova.virt import fake
|
|
from nova.virt.xenapi import agent as xenapi_agent
|
|
from nova.virt.xenapi import fake as xenapi_fake
|
|
from nova.virt.xenapi import vm_utils
|
|
from nova.virt.xenapi import vmops
|
|
from nova.virt.xenapi import volume_utils
|
|
from nova.virt.xenapi import volumeops
|
|
|
|
|
|
class VMOpsTestBase(stubs.XenAPITestBaseNoDB):
|
|
def setUp(self):
|
|
super(VMOpsTestBase, self).setUp()
|
|
self._setup_mock_vmops()
|
|
self.vms = []
|
|
|
|
def _setup_mock_vmops(self, product_brand=None, product_version=None):
|
|
stubs.stubout_session(self.stubs, xenapi_fake.SessionBase)
|
|
self._session = xenapi_fake.SessionBase(
|
|
'http://localhost', 'root', 'test_pass')
|
|
self.vmops = vmops.VMOps(self._session, fake.FakeVirtAPI())
|
|
|
|
def create_vm(self, name, state="Running"):
|
|
vm_ref = xenapi_fake.create_vm(name, state)
|
|
self.vms.append(vm_ref)
|
|
vm = xenapi_fake.get_record("VM", vm_ref)
|
|
return vm, vm_ref
|
|
|
|
def tearDown(self):
|
|
super(VMOpsTestBase, self).tearDown()
|
|
for vm in self.vms:
|
|
xenapi_fake.destroy_vm(vm)
|
|
|
|
|
|
class VMOpsTestCase(VMOpsTestBase):
|
|
def setUp(self):
|
|
super(VMOpsTestCase, self).setUp()
|
|
self._setup_mock_vmops()
|
|
self.context = context.RequestContext('user', 'project')
|
|
self.instance = fake_instance.fake_instance_obj(self.context)
|
|
|
|
def _setup_mock_vmops(self, product_brand=None, product_version=None):
|
|
self._session = self._get_mock_session(product_brand, product_version)
|
|
self._vmops = vmops.VMOps(self._session, fake.FakeVirtAPI())
|
|
|
|
def _get_mock_session(self, product_brand, product_version):
|
|
class Mock(object):
|
|
pass
|
|
|
|
mock_session = Mock()
|
|
mock_session.product_brand = product_brand
|
|
mock_session.product_version = product_version
|
|
return mock_session
|
|
|
|
def _test_finish_revert_migration_after_crash(self, backup_made, new_made,
|
|
vm_shutdown=True):
|
|
instance = {'name': 'foo',
|
|
'task_state': task_states.RESIZE_MIGRATING}
|
|
context = 'fake_context'
|
|
|
|
lookup_returns = [backup_made and 'foo' or None,
|
|
(not backup_made or new_made) and 'foo' or None]
|
|
|
|
@mock.patch.object(vm_utils, 'lookup',
|
|
side_effect=lookup_returns)
|
|
@mock.patch.object(self._vmops, '_destroy')
|
|
@mock.patch.object(vm_utils, 'set_vm_name_label')
|
|
@mock.patch.object(self._vmops, '_attach_mapped_block_devices')
|
|
@mock.patch.object(self._vmops, '_start')
|
|
@mock.patch.object(vm_utils, 'is_vm_shutdown',
|
|
return_value=vm_shutdown)
|
|
def test(mock_is_vm, mock_start, mock_attach_bdm, mock_set_vm_name,
|
|
mock_destroy, mock_lookup):
|
|
self._vmops.finish_revert_migration(context, instance, [])
|
|
|
|
mock_lookup.assert_has_calls([mock.call(self._session, 'foo-orig'),
|
|
mock.call(self._session, 'foo')])
|
|
if backup_made:
|
|
if new_made:
|
|
mock_destroy.assert_called_once_with(instance, 'foo')
|
|
mock_set_vm_name.assert_called_once_with(self._session, 'foo',
|
|
'foo')
|
|
mock_attach_bdm.assert_called_once_with(instance, [])
|
|
|
|
mock_is_vm.assert_called_once_with(self._session, 'foo')
|
|
if vm_shutdown:
|
|
mock_start.assert_called_once_with(instance, 'foo')
|
|
|
|
test()
|
|
|
|
def test_finish_revert_migration_after_crash(self):
|
|
self._test_finish_revert_migration_after_crash(True, True)
|
|
|
|
def test_finish_revert_migration_after_crash_before_new(self):
|
|
self._test_finish_revert_migration_after_crash(True, False)
|
|
|
|
def test_finish_revert_migration_after_crash_before_backup(self):
|
|
self._test_finish_revert_migration_after_crash(False, False)
|
|
|
|
@mock.patch.object(vm_utils, 'lookup', return_value=None)
|
|
def test_get_vm_opaque_ref_raises_instance_not_found(self, mock_lookup):
|
|
instance = {"name": "dummy"}
|
|
|
|
self.assertRaises(exception.InstanceNotFound,
|
|
self._vmops._get_vm_opaque_ref, instance)
|
|
mock_lookup.assert_called_once_with(self._session, instance['name'],
|
|
False)
|
|
|
|
@mock.patch.object(vm_utils, 'destroy_vm')
|
|
@mock.patch.object(vm_utils, 'clean_shutdown_vm')
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
def test_clean_shutdown_no_bdm_on_destroy(self, hard_shutdown_vm,
|
|
clean_shutdown_vm, destroy_vm):
|
|
vm_ref = 'vm_ref'
|
|
self._vmops._destroy(self.instance, vm_ref, destroy_disks=False)
|
|
hard_shutdown_vm.assert_called_once_with(self._vmops._session,
|
|
self.instance, vm_ref)
|
|
self.assertEqual(0, clean_shutdown_vm.call_count)
|
|
|
|
@mock.patch.object(vm_utils, 'destroy_vm')
|
|
@mock.patch.object(vm_utils, 'clean_shutdown_vm')
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
def test_clean_shutdown_with_bdm_on_destroy(self, hard_shutdown_vm,
|
|
clean_shutdown_vm, destroy_vm):
|
|
vm_ref = 'vm_ref'
|
|
block_device_info = {'block_device_mapping': ['fake']}
|
|
self._vmops._destroy(self.instance, vm_ref, destroy_disks=False,
|
|
block_device_info=block_device_info)
|
|
clean_shutdown_vm.assert_called_once_with(self._vmops._session,
|
|
self.instance, vm_ref)
|
|
self.assertEqual(0, hard_shutdown_vm.call_count)
|
|
|
|
@mock.patch.object(vm_utils, 'destroy_vm')
|
|
@mock.patch.object(vm_utils, 'clean_shutdown_vm', return_value=False)
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
def test_clean_shutdown_with_bdm_failed_on_destroy(self, hard_shutdown_vm,
|
|
clean_shutdown_vm, destroy_vm):
|
|
vm_ref = 'vm_ref'
|
|
block_device_info = {'block_device_mapping': ['fake']}
|
|
self._vmops._destroy(self.instance, vm_ref, destroy_disks=False,
|
|
block_device_info=block_device_info)
|
|
clean_shutdown_vm.assert_called_once_with(self._vmops._session,
|
|
self.instance, vm_ref)
|
|
hard_shutdown_vm.assert_called_once_with(self._vmops._session,
|
|
self.instance, vm_ref)
|
|
|
|
@mock.patch.object(vm_utils, 'try_auto_configure_disk')
|
|
@mock.patch.object(vm_utils, 'create_vbd',
|
|
side_effect=test.TestingException)
|
|
def test_attach_disks_rescue_auto_disk_config_false(self, create_vbd,
|
|
try_auto_config):
|
|
ctxt = context.RequestContext('user', 'project')
|
|
instance = fake_instance.fake_instance_obj(ctxt)
|
|
image_meta = objects.ImageMeta.from_dict(
|
|
{'properties': {'auto_disk_config': 'false'}})
|
|
vdis = {'root': {'ref': 'fake-ref'}}
|
|
self.assertRaises(test.TestingException, self._vmops._attach_disks,
|
|
ctxt, instance, image_meta=image_meta, vm_ref=None,
|
|
name_label=None, vdis=vdis, disk_image_type='fake',
|
|
network_info=[], rescue=True)
|
|
self.assertFalse(try_auto_config.called)
|
|
|
|
@mock.patch.object(vm_utils, 'try_auto_configure_disk')
|
|
@mock.patch.object(vm_utils, 'create_vbd',
|
|
side_effect=test.TestingException)
|
|
def test_attach_disks_rescue_auto_disk_config_true(self, create_vbd,
|
|
try_auto_config):
|
|
ctxt = context.RequestContext('user', 'project')
|
|
instance = fake_instance.fake_instance_obj(ctxt)
|
|
image_meta = objects.ImageMeta.from_dict(
|
|
{'properties': {'auto_disk_config': 'true'}})
|
|
vdis = {'root': {'ref': 'fake-ref'}}
|
|
self.assertRaises(test.TestingException, self._vmops._attach_disks,
|
|
ctxt, instance, image_meta=image_meta, vm_ref=None,
|
|
name_label=None, vdis=vdis, disk_image_type='fake',
|
|
network_info=[], rescue=True)
|
|
try_auto_config.assert_called_once_with(self._vmops._session,
|
|
'fake-ref', instance.flavor.root_gb)
|
|
|
|
|
|
class InjectAutoDiskConfigTestCase(VMOpsTestBase):
|
|
def test_inject_auto_disk_config_when_present(self):
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
instance = {"name": "dummy", "uuid": "1234", "auto_disk_config": True}
|
|
self.vmops._inject_auto_disk_config(instance, vm_ref)
|
|
xenstore_data = vm['xenstore_data']
|
|
self.assertEqual(xenstore_data['vm-data/auto-disk-config'], 'True')
|
|
|
|
def test_inject_auto_disk_config_none_as_false(self):
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
instance = {"name": "dummy", "uuid": "1234", "auto_disk_config": None}
|
|
self.vmops._inject_auto_disk_config(instance, vm_ref)
|
|
xenstore_data = vm['xenstore_data']
|
|
self.assertEqual(xenstore_data['vm-data/auto-disk-config'], 'False')
|
|
|
|
|
|
class GetConsoleOutputTestCase(VMOpsTestBase):
|
|
def _mock_console_log(self, session, domid):
|
|
if domid == 0:
|
|
raise session.XenAPI.Failure('No console')
|
|
return base64.b64encode(zlib.compress(six.b('dom_id: %s' % domid)))
|
|
|
|
@mock.patch.object(vmops.vm_management, 'get_console_log')
|
|
def test_get_console_output_works(self, mock_console_log):
|
|
ctxt = context.RequestContext('user', 'project')
|
|
mock_console_log.side_effect = self._mock_console_log
|
|
instance = fake_instance.fake_instance_obj(ctxt)
|
|
with mock.patch.object(self.vmops, '_get_last_dom_id',
|
|
return_value=42) as mock_last_dom:
|
|
self.assertEqual(b"dom_id: 42",
|
|
self.vmops.get_console_output(instance))
|
|
mock_last_dom.assert_called_once_with(instance, check_rescue=True)
|
|
|
|
@mock.patch.object(vmops.vm_management, 'get_console_log')
|
|
def test_get_console_output_not_available(self, mock_console_log):
|
|
mock_console_log.side_effect = self._mock_console_log
|
|
ctxt = context.RequestContext('user', 'project')
|
|
instance = fake_instance.fake_instance_obj(ctxt)
|
|
# dom_id=0 used to trigger exception in fake XenAPI
|
|
with mock.patch.object(self.vmops, '_get_last_dom_id',
|
|
return_value=0) as mock_last_dom:
|
|
self.assertRaises(exception.ConsoleNotAvailable,
|
|
self.vmops.get_console_output, instance)
|
|
mock_last_dom.assert_called_once_with(instance, check_rescue=True)
|
|
|
|
def test_get_dom_id_works(self):
|
|
instance = {"name": "dummy"}
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
self.assertEqual(vm["domid"], self.vmops._get_dom_id(instance))
|
|
|
|
def test_get_dom_id_works_with_rescue_vm(self):
|
|
instance = {"name": "dummy"}
|
|
vm, vm_ref = self.create_vm("dummy-rescue")
|
|
self.assertEqual(vm["domid"],
|
|
self.vmops._get_dom_id(instance, check_rescue=True))
|
|
|
|
def test_get_dom_id_raises_not_found(self):
|
|
instance = {"name": "dummy"}
|
|
self.create_vm("not-dummy")
|
|
self.assertRaises(exception.InstanceNotFound,
|
|
self.vmops._get_dom_id, instance)
|
|
|
|
def test_get_dom_id_works_with_vmref(self):
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
instance = {'name': 'dummy'}
|
|
self.assertEqual(vm["domid"],
|
|
self.vmops._get_dom_id(instance, vm_ref=vm_ref))
|
|
|
|
def test_get_dom_id_fails_if_shutdown(self):
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
instance = {'name': 'dummy'}
|
|
self._session.VM.hard_shutdown(vm_ref)
|
|
self.assertRaises(exception.InstanceNotFound,
|
|
self.vmops._get_dom_id, instance, vm_ref=vm_ref)
|
|
|
|
|
|
class SpawnTestCase(VMOpsTestBase):
|
|
def _stub_out_common(self):
|
|
self.mox.StubOutWithMock(self.vmops, '_ensure_instance_name_unique')
|
|
self.mox.StubOutWithMock(self.vmops, '_ensure_enough_free_mem')
|
|
self.mox.StubOutWithMock(self.vmops, '_update_instance_progress')
|
|
self.mox.StubOutWithMock(vm_utils, 'determine_disk_image_type')
|
|
self.mox.StubOutWithMock(self.vmops, '_get_vdis_for_instance')
|
|
self.mox.StubOutWithMock(vm_utils, 'safe_destroy_vdis')
|
|
self.mox.StubOutWithMock(self.vmops._volumeops,
|
|
'safe_cleanup_from_vdis')
|
|
self.mox.StubOutWithMock(self.vmops, '_resize_up_vdis')
|
|
self.mox.StubOutWithMock(vm_utils,
|
|
'create_kernel_and_ramdisk')
|
|
self.mox.StubOutWithMock(vm_utils, 'destroy_kernel_ramdisk')
|
|
self.mox.StubOutWithMock(self.vmops, '_create_vm_record')
|
|
self.mox.StubOutWithMock(self.vmops, '_destroy')
|
|
self.mox.StubOutWithMock(self.vmops, '_attach_disks')
|
|
self.mox.StubOutWithMock(self.vmops, '_save_device_metadata')
|
|
self.mox.StubOutWithMock(self.vmops, '_prepare_disk_metadata')
|
|
self.mox.StubOutWithMock(pci_manager, 'get_instance_pci_devs')
|
|
self.mox.StubOutWithMock(vm_utils, 'set_other_config_pci')
|
|
self.mox.StubOutWithMock(self.vmops, '_attach_orig_disks')
|
|
self.mox.StubOutWithMock(self.vmops, 'inject_network_info')
|
|
self.mox.StubOutWithMock(self.vmops, '_inject_hostname')
|
|
self.mox.StubOutWithMock(self.vmops, '_inject_instance_metadata')
|
|
self.mox.StubOutWithMock(self.vmops, '_inject_auto_disk_config')
|
|
self.mox.StubOutWithMock(self.vmops, '_file_inject_vm_settings')
|
|
self.mox.StubOutWithMock(self.vmops, '_create_vifs')
|
|
self.mox.StubOutWithMock(self.vmops.firewall_driver,
|
|
'setup_basic_filtering')
|
|
self.mox.StubOutWithMock(self.vmops.firewall_driver,
|
|
'prepare_instance_filter')
|
|
self.mox.StubOutWithMock(self.vmops, '_start')
|
|
self.mox.StubOutWithMock(self.vmops, '_wait_for_instance_to_start')
|
|
self.mox.StubOutWithMock(self.vmops,
|
|
'_configure_new_instance_with_agent')
|
|
self.mox.StubOutWithMock(self.vmops, '_remove_hostname')
|
|
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')
|
|
self.mox.StubOutWithMock(self.vmops, '_attach_vgpu')
|
|
|
|
@staticmethod
|
|
def _new_instance(obj):
|
|
class _Instance(dict):
|
|
__getattr__ = dict.__getitem__
|
|
__setattr__ = dict.__setitem__
|
|
return _Instance(**obj)
|
|
|
|
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, neutron_exception=False,
|
|
network_info=None, vgpu_info=None):
|
|
self._stub_out_common()
|
|
|
|
instance = self._new_instance({"name": "dummy", "uuid": "fake_uuid",
|
|
"device_metadata": None})
|
|
|
|
name_label = name_label_param
|
|
if name_label is None:
|
|
name_label = "dummy"
|
|
image_meta = objects.ImageMeta.from_dict({"id": uuids.image_id})
|
|
context = "context"
|
|
session = self.vmops._session
|
|
injected_files = "fake_files"
|
|
admin_password = "password"
|
|
if network_info is None:
|
|
network_info = []
|
|
steps = 10
|
|
if rescue:
|
|
steps += 1
|
|
|
|
block_device_info = block_device_info_param
|
|
if block_device_info and not block_device_info['root_device_name']:
|
|
block_device_info = dict(block_device_info_param)
|
|
block_device_info['root_device_name'] = \
|
|
self.vmops.default_root_dev
|
|
|
|
di_type = "di_type"
|
|
vm_utils.determine_disk_image_type(image_meta).AndReturn(di_type)
|
|
step = 1
|
|
self.vmops._update_instance_progress(context, instance, step, steps)
|
|
|
|
vdis = {"other": {"ref": "fake_ref_2", "osvol": True}}
|
|
if include_root_vdi:
|
|
vdis["root"] = {"ref": "fake_ref"}
|
|
self.vmops._get_vdis_for_instance(context, instance,
|
|
name_label, image_meta, di_type,
|
|
block_device_info).AndReturn(vdis)
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
step += 1
|
|
self.vmops._update_instance_progress(context, instance, step, steps)
|
|
|
|
kernel_file = "kernel"
|
|
ramdisk_file = "ramdisk"
|
|
vm_utils.create_kernel_and_ramdisk(context, session,
|
|
instance, name_label).AndReturn((kernel_file, ramdisk_file))
|
|
step += 1
|
|
self.vmops._update_instance_progress(context, instance, step, steps)
|
|
|
|
vm_ref = "fake_vm_ref"
|
|
self.vmops._ensure_instance_name_unique(name_label)
|
|
self.vmops._ensure_enough_free_mem(instance)
|
|
self.vmops._create_vm_record(context, instance, name_label,
|
|
di_type, kernel_file,
|
|
ramdisk_file, image_meta, rescue).AndReturn(vm_ref)
|
|
step += 1
|
|
self.vmops._update_instance_progress(context, instance, step, steps)
|
|
|
|
self.vmops._save_device_metadata(context, instance, block_device_info)
|
|
self.vmops._attach_disks(context, instance, image_meta, vm_ref,
|
|
name_label, vdis, di_type, network_info, rescue,
|
|
admin_password, injected_files)
|
|
if attach_pci_dev:
|
|
fake_dev = {
|
|
'created_at': None,
|
|
'updated_at': None,
|
|
'deleted_at': None,
|
|
'deleted': None,
|
|
'id': 1,
|
|
'compute_node_id': 1,
|
|
'address': '00:00.0',
|
|
'vendor_id': '1234',
|
|
'product_id': 'abcd',
|
|
'dev_type': fields.PciDeviceType.STANDARD,
|
|
'status': 'available',
|
|
'dev_id': 'devid',
|
|
'label': 'label',
|
|
'instance_uuid': None,
|
|
'extra_info': '{}',
|
|
}
|
|
pci_manager.get_instance_pci_devs(instance).AndReturn([fake_dev])
|
|
vm_utils.set_other_config_pci(self.vmops._session,
|
|
vm_ref,
|
|
"0/0000:00:00.0")
|
|
else:
|
|
pci_manager.get_instance_pci_devs(instance).AndReturn([])
|
|
|
|
self.vmops._attach_vgpu(vm_ref, vgpu_info, instance)
|
|
|
|
step += 1
|
|
self.vmops._update_instance_progress(context, instance, step, steps)
|
|
|
|
self.vmops._inject_instance_metadata(instance, vm_ref)
|
|
self.vmops._inject_auto_disk_config(instance, vm_ref)
|
|
self.vmops._inject_hostname(instance, vm_ref, rescue)
|
|
self.vmops._file_inject_vm_settings(instance, vm_ref, vdis,
|
|
network_info)
|
|
self.vmops.inject_network_info(instance, network_info, vm_ref)
|
|
step += 1
|
|
self.vmops._update_instance_progress(context, instance, step, steps)
|
|
|
|
if neutron_exception:
|
|
events = [('network-vif-plugged', 1)]
|
|
self.vmops._get_neutron_events(network_info,
|
|
True, True, False).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)
|
|
|
|
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)
|
|
|
|
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)
|
|
vm_utils.safe_destroy_vdis(self.vmops._session, ["fake_ref"])
|
|
self.vmops._volumeops.safe_cleanup_from_vdis(["fake_ref_2"])
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops.spawn(context, instance, image_meta, injected_files,
|
|
admin_password, network_info, block_device_info_param,
|
|
vgpu_info, name_label_param, rescue)
|
|
|
|
def test_spawn(self):
|
|
self._test_spawn()
|
|
|
|
def test_spawn_with_alternate_options(self):
|
|
self._test_spawn(include_root_vdi=False, rescue=True,
|
|
name_label_param="bob",
|
|
block_device_info_param={"root_device_name": ""})
|
|
|
|
def test_spawn_with_pci_available_on_the_host(self):
|
|
self._test_spawn(attach_pci_dev=True)
|
|
|
|
def test_spawn_with_vgpu(self):
|
|
vgpu_info = {'grp_uuid': uuids.gpu_group_1,
|
|
'vgpu_type_uuid': uuids.vgpu_type_1}
|
|
self._test_spawn(vgpu_info=vgpu_info)
|
|
|
|
def test_spawn_performs_rollback_and_throws_exception(self):
|
|
self.assertRaises(test.TestingException, self._test_spawn,
|
|
throw_exception=test.TestingException())
|
|
|
|
def test_spawn_with_neutron(self):
|
|
self.flags(use_neutron=True)
|
|
self.mox.StubOutWithMock(self.vmops, '_get_neutron_events')
|
|
events = [('network-vif-plugged', 1)]
|
|
network_info = [{'id': 1, 'active': True}]
|
|
self.vmops._get_neutron_events(network_info,
|
|
True, True, False).AndReturn(events)
|
|
self.mox.StubOutWithMock(self.vmops,
|
|
'_neutron_failed_callback')
|
|
self._test_spawn(network_info=network_info)
|
|
|
|
@staticmethod
|
|
def _dev_mock(obj):
|
|
dev = mock.MagicMock(**obj)
|
|
dev.__contains__.side_effect = (
|
|
lambda attr: getattr(dev, attr, None) is not None)
|
|
return dev
|
|
|
|
@mock.patch.object(objects, 'XenDeviceBus')
|
|
@mock.patch.object(objects, 'IDEDeviceBus')
|
|
@mock.patch.object(objects, 'DiskMetadata')
|
|
def test_prepare_disk_metadata(self, mock_DiskMetadata,
|
|
mock_IDEDeviceBus, mock_XenDeviceBus):
|
|
mock_IDEDeviceBus.side_effect = \
|
|
lambda **kw: \
|
|
self._dev_mock({"address": kw.get("address"), "bus": "ide"})
|
|
mock_XenDeviceBus.side_effect = \
|
|
lambda **kw: \
|
|
self._dev_mock({"address": kw.get("address"), "bus": "xen"})
|
|
mock_DiskMetadata.side_effect = \
|
|
lambda **kw: self._dev_mock(dict(**kw))
|
|
|
|
bdm = self._dev_mock({"device_name": "/dev/xvda", "tag": "disk_a"})
|
|
disk_metadata = self.vmops._prepare_disk_metadata(bdm)
|
|
|
|
self.assertEqual(disk_metadata[0].tags, ["disk_a"])
|
|
self.assertEqual(disk_metadata[0].bus.bus, "ide")
|
|
self.assertEqual(disk_metadata[0].bus.address, "0:0")
|
|
self.assertEqual(disk_metadata[1].tags, ["disk_a"])
|
|
self.assertEqual(disk_metadata[1].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[1].bus.address, "000000")
|
|
self.assertEqual(disk_metadata[2].tags, ["disk_a"])
|
|
self.assertEqual(disk_metadata[2].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[2].bus.address, "51712")
|
|
self.assertEqual(disk_metadata[3].tags, ["disk_a"])
|
|
self.assertEqual(disk_metadata[3].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[3].bus.address, "768")
|
|
|
|
bdm = self._dev_mock({"device_name": "/dev/xvdc", "tag": "disk_c"})
|
|
disk_metadata = self.vmops._prepare_disk_metadata(bdm)
|
|
|
|
self.assertEqual(disk_metadata[0].tags, ["disk_c"])
|
|
self.assertEqual(disk_metadata[0].bus.bus, "ide")
|
|
self.assertEqual(disk_metadata[0].bus.address, "1:0")
|
|
self.assertEqual(disk_metadata[1].tags, ["disk_c"])
|
|
self.assertEqual(disk_metadata[1].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[1].bus.address, "000200")
|
|
self.assertEqual(disk_metadata[2].tags, ["disk_c"])
|
|
self.assertEqual(disk_metadata[2].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[2].bus.address, "51744")
|
|
self.assertEqual(disk_metadata[3].tags, ["disk_c"])
|
|
self.assertEqual(disk_metadata[3].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[3].bus.address, "5632")
|
|
|
|
bdm = self._dev_mock({"device_name": "/dev/xvde", "tag": "disk_e"})
|
|
disk_metadata = self.vmops._prepare_disk_metadata(bdm)
|
|
|
|
self.assertEqual(disk_metadata[0].tags, ["disk_e"])
|
|
self.assertEqual(disk_metadata[0].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[0].bus.address, "000400")
|
|
self.assertEqual(disk_metadata[1].tags, ["disk_e"])
|
|
self.assertEqual(disk_metadata[1].bus.bus, "xen")
|
|
self.assertEqual(disk_metadata[1].bus.address, "51776")
|
|
|
|
@mock.patch.object(objects.VirtualInterfaceList, 'get_by_instance_uuid')
|
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
|
@mock.patch.object(objects, 'NetworkInterfaceMetadata')
|
|
@mock.patch.object(objects, 'InstanceDeviceMetadata')
|
|
@mock.patch.object(objects, 'PCIDeviceBus')
|
|
@mock.patch.object(vmops.VMOps, '_prepare_disk_metadata')
|
|
def test_save_device_metadata(self, mock_prepare_disk_metadata,
|
|
mock_PCIDeviceBus, mock_InstanceDeviceMetadata,
|
|
mock_NetworkInterfaceMetadata, mock_get_bdms, mock_get_vifs):
|
|
context = {}
|
|
instance = {"uuid": "fake_uuid"}
|
|
vif = self._dev_mock({"address": "fake_address", "tag": "vif_tag"})
|
|
bdm = self._dev_mock({"device_name": "/dev/xvdx", "tag": "bdm_tag"})
|
|
block_device_info = {'block_device_mapping': [bdm]}
|
|
|
|
mock_get_vifs.return_value = [vif]
|
|
mock_get_bdms.return_value = [bdm]
|
|
mock_InstanceDeviceMetadata.side_effect = \
|
|
lambda **kw: {"devices": kw.get("devices")}
|
|
mock_NetworkInterfaceMetadata.return_value = mock.sentinel.vif_metadata
|
|
mock_prepare_disk_metadata.return_value = [mock.sentinel.bdm_metadata]
|
|
|
|
dev_meta = self.vmops._save_device_metadata(context, instance,
|
|
block_device_info)
|
|
|
|
mock_get_vifs.assert_called_once_with(context, instance["uuid"])
|
|
|
|
mock_NetworkInterfaceMetadata.assert_called_once_with(mac=vif.address,
|
|
bus=mock_PCIDeviceBus.return_value,
|
|
tags=[vif.tag])
|
|
mock_prepare_disk_metadata.assert_called_once_with(bdm)
|
|
self.assertEqual(dev_meta["devices"],
|
|
[mock.sentinel.vif_metadata, mock.sentinel.bdm_metadata])
|
|
|
|
@mock.patch.object(objects.VirtualInterfaceList, 'get_by_instance_uuid')
|
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
|
@mock.patch.object(vmops.VMOps, '_prepare_disk_metadata')
|
|
def test_save_device_metadata_no_vifs_no_bdms(
|
|
self, mock_prepare_disk_metadata, mock_get_bdms, mock_get_vifs):
|
|
"""Tests that we don't save any device metadata when there are no
|
|
VIFs or BDMs.
|
|
"""
|
|
ctxt = context.RequestContext('fake-user', 'fake-project')
|
|
instance = objects.Instance(uuid=uuids.instance_uuid)
|
|
block_device_info = {'block_device_mapping': []}
|
|
|
|
mock_get_vifs.return_value = objects.VirtualInterfaceList()
|
|
|
|
dev_meta = self.vmops._save_device_metadata(
|
|
ctxt, instance, block_device_info)
|
|
self.assertIsNone(dev_meta)
|
|
|
|
mock_get_vifs.assert_called_once_with(ctxt, uuids.instance_uuid)
|
|
mock_get_bdms.assert_not_called()
|
|
mock_prepare_disk_metadata.assert_not_called()
|
|
|
|
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,
|
|
vgpu_info=None):
|
|
self._stub_out_common()
|
|
self.mox.StubOutWithMock(volumeops.VolumeOps, "connect_volume")
|
|
self.mox.StubOutWithMock(vm_utils, "import_all_migrated_disks")
|
|
self.mox.StubOutWithMock(self.vmops, "_attach_mapped_block_devices")
|
|
|
|
context = "context"
|
|
migration = {}
|
|
name_label = "dummy"
|
|
instance = self._new_instance({"name": name_label, "uuid": "fake_uuid",
|
|
"root_device_name": "/dev/xvda", "device_metadata": None})
|
|
disk_info = "disk_info"
|
|
network_info = "net_info"
|
|
image_meta = objects.ImageMeta.from_dict({"id": uuids.image_id})
|
|
block_device_info = {}
|
|
import_root = True
|
|
if booted_from_volume:
|
|
block_device_info = {'block_device_mapping': [
|
|
{'mount_device': '/dev/xvda',
|
|
'connection_info': {'data': 'fake-data'}}]}
|
|
import_root = False
|
|
volumeops.VolumeOps.connect_volume(
|
|
{'data': 'fake-data'}).AndReturn(('sr', 'vol-vdi-uuid'))
|
|
self.vmops._session.call_xenapi('VDI.get_by_uuid',
|
|
'vol-vdi-uuid').AndReturn('vol-vdi-ref')
|
|
session = self.vmops._session
|
|
|
|
self.vmops._ensure_instance_name_unique(name_label)
|
|
self.vmops._ensure_enough_free_mem(instance)
|
|
|
|
di_type = "di_type"
|
|
vm_utils.determine_disk_image_type(image_meta).AndReturn(di_type)
|
|
|
|
root_vdi = {"ref": "fake_ref"}
|
|
ephemeral_vdi = {"ref": "fake_ref_e"}
|
|
vdis = {"root": root_vdi, "ephemerals": {4: ephemeral_vdi}}
|
|
vm_utils.import_all_migrated_disks(self.vmops._session, instance,
|
|
import_root=import_root).AndReturn(vdis)
|
|
|
|
kernel_file = "kernel"
|
|
ramdisk_file = "ramdisk"
|
|
vm_utils.create_kernel_and_ramdisk(context, session,
|
|
instance, name_label).AndReturn((kernel_file, ramdisk_file))
|
|
|
|
vm_ref = "fake_vm_ref"
|
|
rescue = False
|
|
self.vmops._create_vm_record(context, instance, name_label,
|
|
di_type, kernel_file,
|
|
ramdisk_file, image_meta, rescue).AndReturn(vm_ref)
|
|
|
|
if resize_instance:
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
self.vmops._save_device_metadata(context, instance, block_device_info)
|
|
self.vmops._attach_disks(context, instance, image_meta, vm_ref,
|
|
name_label, vdis, di_type, network_info, False,
|
|
None, None)
|
|
self.vmops._attach_mapped_block_devices(instance, block_device_info)
|
|
pci_manager.get_instance_pci_devs(instance).AndReturn([])
|
|
|
|
self.vmops._attach_vgpu(vm_ref, vgpu_info, instance)
|
|
|
|
self.vmops._inject_instance_metadata(instance, vm_ref)
|
|
self.vmops._inject_auto_disk_config(instance, vm_ref)
|
|
self.vmops._file_inject_vm_settings(instance, vm_ref, vdis,
|
|
network_info)
|
|
self.vmops.inject_network_info(instance, network_info, vm_ref)
|
|
|
|
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)
|
|
|
|
if power_on:
|
|
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)
|
|
if throw_exception:
|
|
last_call.AndRaise(throw_exception)
|
|
self.vmops._destroy(instance, vm_ref, network_info=network_info)
|
|
vm_utils.destroy_kernel_ramdisk(self.vmops._session, instance,
|
|
kernel_file, ramdisk_file)
|
|
vm_utils.safe_destroy_vdis(self.vmops._session,
|
|
["fake_ref_e", "fake_ref"])
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops.finish_migration(context, migration, instance, disk_info,
|
|
network_info, image_meta, resize_instance,
|
|
block_device_info, power_on)
|
|
|
|
def test_finish_migration(self):
|
|
self._test_finish_migration()
|
|
|
|
def test_finish_migration_no_power_on(self):
|
|
self._test_finish_migration(power_on=False, resize_instance=False)
|
|
|
|
def test_finish_migration_booted_from_volume(self):
|
|
self._test_finish_migration(booted_from_volume=True)
|
|
|
|
def test_finish_migrate_performs_rollback_on_error(self):
|
|
self.assertRaises(test.TestingException, self._test_finish_migration,
|
|
power_on=False, resize_instance=False,
|
|
throw_exception=test.TestingException())
|
|
|
|
def test_remove_hostname(self):
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
instance = {"name": "dummy", "uuid": "1234", "auto_disk_config": None}
|
|
self.mox.StubOutWithMock(self._session, 'call_xenapi')
|
|
self._session.call_xenapi("VM.remove_from_xenstore_data", vm_ref,
|
|
"vm-data/hostname")
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops._remove_hostname(instance, vm_ref)
|
|
self.mox.VerifyAll()
|
|
|
|
def test_reset_network(self):
|
|
class mock_agent(object):
|
|
def __init__(self):
|
|
self.called = False
|
|
|
|
def resetnetwork(self):
|
|
self.called = True
|
|
|
|
vm, vm_ref = self.create_vm("dummy")
|
|
instance = {"name": "dummy", "uuid": "1234", "auto_disk_config": None}
|
|
agent = mock_agent()
|
|
|
|
self.mox.StubOutWithMock(self.vmops, 'agent_enabled')
|
|
self.mox.StubOutWithMock(self.vmops, '_get_agent')
|
|
self.mox.StubOutWithMock(self.vmops, '_inject_hostname')
|
|
self.mox.StubOutWithMock(self.vmops, '_remove_hostname')
|
|
|
|
self.vmops.agent_enabled(instance).AndReturn(True)
|
|
self.vmops._get_agent(instance, vm_ref).AndReturn(agent)
|
|
self.vmops._inject_hostname(instance, vm_ref, False)
|
|
self.vmops._remove_hostname(instance, vm_ref)
|
|
self.mox.ReplayAll()
|
|
self.vmops.reset_network(instance)
|
|
self.assertTrue(agent.called)
|
|
self.mox.VerifyAll()
|
|
|
|
def test_inject_hostname(self):
|
|
instance = {"hostname": "dummy", "os_type": "fake", "uuid": "uuid"}
|
|
vm_ref = "vm_ref"
|
|
|
|
self.mox.StubOutWithMock(self.vmops, '_add_to_param_xenstore')
|
|
self.vmops._add_to_param_xenstore(vm_ref, 'vm-data/hostname', 'dummy')
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops._inject_hostname(instance, vm_ref, rescue=False)
|
|
|
|
def test_inject_hostname_with_rescue_prefix(self):
|
|
instance = {"hostname": "dummy", "os_type": "fake", "uuid": "uuid"}
|
|
vm_ref = "vm_ref"
|
|
|
|
self.mox.StubOutWithMock(self.vmops, '_add_to_param_xenstore')
|
|
self.vmops._add_to_param_xenstore(vm_ref, 'vm-data/hostname',
|
|
'RESCUE-dummy')
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops._inject_hostname(instance, vm_ref, rescue=True)
|
|
|
|
def test_inject_hostname_with_windows_name_truncation(self):
|
|
instance = {"hostname": "dummydummydummydummydummy",
|
|
"os_type": "windows", "uuid": "uuid"}
|
|
vm_ref = "vm_ref"
|
|
|
|
self.mox.StubOutWithMock(self.vmops, '_add_to_param_xenstore')
|
|
self.vmops._add_to_param_xenstore(vm_ref, 'vm-data/hostname',
|
|
'RESCUE-dummydum')
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops._inject_hostname(instance, vm_ref, rescue=True)
|
|
|
|
def test_wait_for_instance_to_start(self):
|
|
instance = {"uuid": "uuid"}
|
|
vm_ref = "vm_ref"
|
|
|
|
self.mox.StubOutWithMock(vm_utils, 'get_power_state')
|
|
self.mox.StubOutWithMock(greenthread, 'sleep')
|
|
vm_utils.get_power_state(self._session, vm_ref).AndReturn(
|
|
power_state.SHUTDOWN)
|
|
greenthread.sleep(0.5)
|
|
vm_utils.get_power_state(self._session, vm_ref).AndReturn(
|
|
power_state.RUNNING)
|
|
|
|
self.mox.ReplayAll()
|
|
self.vmops._wait_for_instance_to_start(instance, vm_ref)
|
|
|
|
@mock.patch.object(vm_utils, 'lookup', return_value='ref')
|
|
@mock.patch.object(vm_utils, 'create_vbd')
|
|
def test_attach_orig_disks(self, mock_create_vbd, mock_lookup):
|
|
instance = {"name": "dummy"}
|
|
vm_ref = "vm_ref"
|
|
vbd_refs = {vmops.DEVICE_ROOT: "vdi_ref"}
|
|
|
|
with mock.patch.object(self.vmops, '_find_vdi_refs',
|
|
return_value=vbd_refs) as mock_find_vdi:
|
|
self.vmops._attach_orig_disks(instance, vm_ref)
|
|
mock_lookup.assert_called_once_with(self.vmops._session, 'dummy')
|
|
mock_find_vdi.assert_called_once_with('ref', exclude_volumes=True)
|
|
mock_create_vbd.assert_called_once_with(
|
|
self.vmops._session, vm_ref, 'vdi_ref', vmops.DEVICE_RESCUE,
|
|
bootable=False)
|
|
|
|
def test_agent_update_setup(self):
|
|
# agent updates need to occur after networking is configured
|
|
instance = {'name': 'betelgeuse',
|
|
'uuid': '1-2-3-4-5-6'}
|
|
vm_ref = 'vm_ref'
|
|
agent = xenapi_agent.XenAPIBasedAgent(self.vmops._session,
|
|
self.vmops._virtapi, instance, vm_ref)
|
|
|
|
self.mox.StubOutWithMock(xenapi_agent, 'should_use_agent')
|
|
self.mox.StubOutWithMock(self.vmops, '_get_agent')
|
|
self.mox.StubOutWithMock(agent, 'get_version')
|
|
self.mox.StubOutWithMock(agent, 'resetnetwork')
|
|
self.mox.StubOutWithMock(agent, 'update_if_needed')
|
|
|
|
xenapi_agent.should_use_agent(instance).AndReturn(True)
|
|
self.vmops._get_agent(instance, vm_ref).AndReturn(agent)
|
|
agent.get_version().AndReturn('1.2.3')
|
|
agent.resetnetwork()
|
|
agent.update_if_needed('1.2.3')
|
|
|
|
self.mox.ReplayAll()
|
|
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
|
|
rescue = False
|
|
events = self.vmops._get_neutron_events(network_info,
|
|
power_on, first_boot, rescue)
|
|
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
|
|
rescue = False
|
|
events = self.vmops._get_neutron_events(network_info,
|
|
power_on, first_boot, rescue)
|
|
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
|
|
rescue = False
|
|
events = self.vmops._get_neutron_events(network_info,
|
|
power_on, first_boot, rescue)
|
|
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
|
|
rescue = False
|
|
events = self.vmops._get_neutron_events(network_info,
|
|
power_on, first_boot, rescue)
|
|
self.assertEqual([], events)
|
|
|
|
@mock.patch.object(utils, 'is_neutron', return_value=True)
|
|
def test_get_neutron_event_rescue(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
|
|
rescue = True
|
|
events = self.vmops._get_neutron_events(network_info,
|
|
power_on, first_boot, rescue)
|
|
self.assertEqual([], events)
|
|
|
|
|
|
class DestroyTestCase(VMOpsTestBase):
|
|
def setUp(self):
|
|
super(DestroyTestCase, self).setUp()
|
|
self.context = context.RequestContext(user_id=None, project_id=None)
|
|
self.instance = fake_instance.fake_instance_obj(self.context)
|
|
|
|
@mock.patch.object(vm_utils, 'lookup', side_effect=[None, None])
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
@mock.patch.object(volume_utils, 'find_sr_by_uuid')
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_no_vm_no_bdm(self, forget_sr, find_sr_by_uuid, hard_shutdown_vm,
|
|
lookup):
|
|
self.vmops.destroy(self.instance, 'network_info',
|
|
{'block_device_mapping': []})
|
|
self.assertEqual(0, find_sr_by_uuid.call_count)
|
|
self.assertEqual(0, forget_sr.call_count)
|
|
self.assertEqual(0, hard_shutdown_vm.call_count)
|
|
|
|
@mock.patch.object(vm_utils, 'lookup', side_effect=[None, None])
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
@mock.patch.object(volume_utils, 'find_sr_by_uuid', return_value=None)
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_no_vm_orphaned_volume_no_sr(self, forget_sr, find_sr_by_uuid,
|
|
hard_shutdown_vm, lookup):
|
|
self.vmops.destroy(self.instance, 'network_info',
|
|
{'block_device_mapping': [{'connection_info':
|
|
{'data': {'volume_id': 'fake-uuid'}}}]})
|
|
find_sr_by_uuid.assert_called_once_with(self.vmops._session,
|
|
'FA15E-D15C-fake-uuid')
|
|
self.assertEqual(0, forget_sr.call_count)
|
|
self.assertEqual(0, hard_shutdown_vm.call_count)
|
|
|
|
@mock.patch.object(vm_utils, 'lookup', side_effect=[None, None])
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
@mock.patch.object(volume_utils, 'find_sr_by_uuid', return_value='sr_ref')
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_no_vm_orphaned_volume_old_sr(self, forget_sr, find_sr_by_uuid,
|
|
hard_shutdown_vm, lookup):
|
|
self.vmops.destroy(self.instance, 'network_info',
|
|
{'block_device_mapping': [{'connection_info':
|
|
{'data': {'volume_id': 'fake-uuid'}}}]})
|
|
find_sr_by_uuid.assert_called_once_with(self.vmops._session,
|
|
'FA15E-D15C-fake-uuid')
|
|
forget_sr.assert_called_once_with(self.vmops._session, 'sr_ref')
|
|
self.assertEqual(0, hard_shutdown_vm.call_count)
|
|
|
|
@mock.patch.object(vm_utils, 'lookup', side_effect=[None, None])
|
|
@mock.patch.object(vm_utils, 'hard_shutdown_vm')
|
|
@mock.patch.object(volume_utils, 'find_sr_by_uuid',
|
|
side_effect=[None, 'sr_ref'])
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
@mock.patch.object(uuid, 'uuid5', return_value='fake-uuid')
|
|
def test_no_vm_orphaned_volume(self, uuid5, forget_sr,
|
|
find_sr_by_uuid, hard_shutdown_vm, lookup):
|
|
fake_data = {'volume_id': 'fake-uuid',
|
|
'target_portal': 'host:port',
|
|
'target_iqn': 'iqn'}
|
|
self.vmops.destroy(self.instance, 'network_info',
|
|
{'block_device_mapping': [{'connection_info':
|
|
{'data': fake_data}}]})
|
|
call1 = mock.call(self.vmops._session, 'FA15E-D15C-fake-uuid')
|
|
call2 = mock.call(self.vmops._session, 'fake-uuid')
|
|
uuid5.assert_called_once_with(volume_utils.SR_NAMESPACE,
|
|
'host/port/iqn')
|
|
find_sr_by_uuid.assert_has_calls([call1, call2])
|
|
forget_sr.assert_called_once_with(self.vmops._session, 'sr_ref')
|
|
self.assertEqual(0, hard_shutdown_vm.call_count)
|
|
|
|
|
|
@mock.patch.object(vmops.VMOps, '_update_instance_progress')
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
@mock.patch.object(vm_utils, 'get_sr_path')
|
|
@mock.patch.object(vmops.VMOps, '_detach_block_devices_from_orig_vm')
|
|
@mock.patch.object(vmops.VMOps, '_migrate_disk_resizing_down')
|
|
@mock.patch.object(vmops.VMOps, '_migrate_disk_resizing_up')
|
|
class MigrateDiskAndPowerOffTestCase(VMOpsTestBase):
|
|
def setUp(self):
|
|
super(MigrateDiskAndPowerOffTestCase, self).setUp()
|
|
self.context = context.RequestContext('user', 'project')
|
|
|
|
def test_migrate_disk_and_power_off_works_down(self,
|
|
migrate_up, migrate_down, *mocks):
|
|
instance = objects.Instance(
|
|
flavor=objects.Flavor(root_gb=2, ephemeral_gb=0),
|
|
uuid=uuids.instance)
|
|
flavor = fake_flavor.fake_flavor_obj(self.context, root_gb=1,
|
|
ephemeral_gb=0)
|
|
|
|
self.vmops.migrate_disk_and_power_off(None, instance, None,
|
|
flavor, None)
|
|
|
|
self.assertFalse(migrate_up.called)
|
|
self.assertTrue(migrate_down.called)
|
|
|
|
def test_migrate_disk_and_power_off_works_up(self,
|
|
migrate_up, migrate_down, *mocks):
|
|
instance = objects.Instance(
|
|
flavor=objects.Flavor(root_gb=1,
|
|
ephemeral_gb=1),
|
|
uuid=uuids.instance)
|
|
flavor = fake_flavor.fake_flavor_obj(self.context, root_gb=2,
|
|
ephemeral_gb=2)
|
|
|
|
self.vmops.migrate_disk_and_power_off(None, instance, None,
|
|
flavor, None)
|
|
|
|
self.assertFalse(migrate_down.called)
|
|
self.assertTrue(migrate_up.called)
|
|
|
|
def test_migrate_disk_and_power_off_resize_down_ephemeral_fails(self,
|
|
migrate_up, migrate_down, *mocks):
|
|
instance = objects.Instance(flavor=objects.Flavor(ephemeral_gb=2))
|
|
flavor = fake_flavor.fake_flavor_obj(self.context, ephemeral_gb=1)
|
|
|
|
self.assertRaises(exception.ResizeError,
|
|
self.vmops.migrate_disk_and_power_off,
|
|
None, instance, None, flavor, None)
|
|
|
|
|
|
@mock.patch.object(vm_utils, 'get_vdi_for_vm_safely')
|
|
@mock.patch.object(vm_utils, 'migrate_vhd')
|
|
@mock.patch.object(vmops.VMOps, '_resize_ensure_vm_is_shutdown')
|
|
@mock.patch.object(vm_utils, 'get_all_vdi_uuids_for_vm')
|
|
@mock.patch.object(vmops.VMOps, '_update_instance_progress')
|
|
@mock.patch.object(vmops.VMOps, '_apply_orig_vm_name_label')
|
|
class MigrateDiskResizingUpTestCase(VMOpsTestBase):
|
|
def _fake_snapshot_attached_here(self, session, instance, vm_ref, label,
|
|
userdevice, post_snapshot_callback):
|
|
self.assertIsInstance(instance, dict)
|
|
if userdevice == '0':
|
|
self.assertEqual("vm_ref", vm_ref)
|
|
self.assertEqual("fake-snapshot", label)
|
|
yield ["leaf", "parent", "grandp"]
|
|
else:
|
|
leaf = userdevice + "-leaf"
|
|
parent = userdevice + "-parent"
|
|
yield [leaf, parent]
|
|
|
|
@mock.patch.object(volume_utils, 'is_booted_from_volume',
|
|
return_value=False)
|
|
def test_migrate_disk_resizing_up_works_no_ephemeral(self,
|
|
mock_is_booted_from_volume,
|
|
mock_apply_orig, mock_update_progress, mock_get_all_vdi_uuids,
|
|
mock_shutdown, mock_migrate_vhd, mock_get_vdi_for_vm):
|
|
context = "ctxt"
|
|
instance = {"name": "fake", "uuid": "uuid"}
|
|
dest = "dest"
|
|
vm_ref = "vm_ref"
|
|
sr_path = "sr_path"
|
|
|
|
mock_get_all_vdi_uuids.return_value = None
|
|
mock_get_vdi_for_vm.return_value = ({}, {"uuid": "root"})
|
|
|
|
with mock.patch.object(vm_utils, '_snapshot_attached_here_impl',
|
|
self._fake_snapshot_attached_here):
|
|
self.vmops._migrate_disk_resizing_up(context, instance, dest,
|
|
vm_ref, sr_path)
|
|
|
|
mock_get_all_vdi_uuids.assert_called_once_with(self.vmops._session,
|
|
vm_ref, min_userdevice=4)
|
|
mock_apply_orig.assert_called_once_with(instance, vm_ref)
|
|
mock_shutdown.assert_called_once_with(instance, vm_ref)
|
|
|
|
m_vhd_expected = [mock.call(self.vmops._session, instance, "parent",
|
|
dest, sr_path, 1),
|
|
mock.call(self.vmops._session, instance, "grandp",
|
|
dest, sr_path, 2),
|
|
mock.call(self.vmops._session, instance, "root",
|
|
dest, sr_path, 0)]
|
|
self.assertEqual(m_vhd_expected, mock_migrate_vhd.call_args_list)
|
|
|
|
prog_expected = [
|
|
mock.call(context, instance, 1, 5),
|
|
mock.call(context, instance, 2, 5),
|
|
mock.call(context, instance, 3, 5),
|
|
mock.call(context, instance, 4, 5)
|
|
# 5/5: step to be executed by finish migration.
|
|
]
|
|
self.assertEqual(prog_expected, mock_update_progress.call_args_list)
|
|
|
|
@mock.patch.object(volume_utils, 'is_booted_from_volume',
|
|
return_value=False)
|
|
def test_migrate_disk_resizing_up_works_with_two_ephemeral(self,
|
|
mock_is_booted_from_volume,
|
|
mock_apply_orig, mock_update_progress, mock_get_all_vdi_uuids,
|
|
mock_shutdown, mock_migrate_vhd, mock_get_vdi_for_vm):
|
|
context = "ctxt"
|
|
instance = {"name": "fake", "uuid": "uuid"}
|
|
dest = "dest"
|
|
vm_ref = "vm_ref"
|
|
sr_path = "sr_path"
|
|
|
|
mock_get_all_vdi_uuids.return_value = ["vdi-eph1", "vdi-eph2"]
|
|
mock_get_vdi_for_vm.side_effect = [({}, {"uuid": "root"}),
|
|
({}, {"uuid": "4-root"}),
|
|
({}, {"uuid": "5-root"})]
|
|
|
|
with mock.patch.object(vm_utils, '_snapshot_attached_here_impl',
|
|
self._fake_snapshot_attached_here):
|
|
self.vmops._migrate_disk_resizing_up(context, instance, dest,
|
|
vm_ref, sr_path)
|
|
|
|
mock_get_all_vdi_uuids.assert_called_once_with(self.vmops._session,
|
|
vm_ref, min_userdevice=4)
|
|
mock_apply_orig.assert_called_once_with(instance, vm_ref)
|
|
mock_shutdown.assert_called_once_with(instance, vm_ref)
|
|
|
|
m_vhd_expected = [mock.call(self.vmops._session, instance,
|
|
"parent", dest, sr_path, 1),
|
|
mock.call(self.vmops._session, instance,
|
|
"grandp", dest, sr_path, 2),
|
|
mock.call(self.vmops._session, instance,
|
|
"4-parent", dest, sr_path, 1, 1),
|
|
mock.call(self.vmops._session, instance,
|
|
"5-parent", dest, sr_path, 1, 2),
|
|
mock.call(self.vmops._session, instance,
|
|
"root", dest, sr_path, 0),
|
|
mock.call(self.vmops._session, instance,
|
|
"4-root", dest, sr_path, 0, 1),
|
|
mock.call(self.vmops._session, instance,
|
|
"5-root", dest, sr_path, 0, 2)]
|
|
self.assertEqual(m_vhd_expected, mock_migrate_vhd.call_args_list)
|
|
|
|
prog_expected = [
|
|
mock.call(context, instance, 1, 5),
|
|
mock.call(context, instance, 2, 5),
|
|
mock.call(context, instance, 3, 5),
|
|
mock.call(context, instance, 4, 5)
|
|
# 5/5: step to be executed by finish migration.
|
|
]
|
|
self.assertEqual(prog_expected, mock_update_progress.call_args_list)
|
|
|
|
@mock.patch.object(volume_utils, 'is_booted_from_volume',
|
|
return_value=True)
|
|
def test_migrate_disk_resizing_up_booted_from_volume(self,
|
|
mock_is_booted_from_volume,
|
|
mock_apply_orig, mock_update_progress, mock_get_all_vdi_uuids,
|
|
mock_shutdown, mock_migrate_vhd, mock_get_vdi_for_vm):
|
|
context = "ctxt"
|
|
instance = {"name": "fake", "uuid": "uuid"}
|
|
dest = "dest"
|
|
vm_ref = "vm_ref"
|
|
sr_path = "sr_path"
|
|
|
|
mock_get_all_vdi_uuids.return_value = ["vdi-eph1", "vdi-eph2"]
|
|
mock_get_vdi_for_vm.side_effect = [({}, {"uuid": "4-root"}),
|
|
({}, {"uuid": "5-root"})]
|
|
|
|
with mock.patch.object(vm_utils, '_snapshot_attached_here_impl',
|
|
self._fake_snapshot_attached_here):
|
|
self.vmops._migrate_disk_resizing_up(context, instance, dest,
|
|
vm_ref, sr_path)
|
|
|
|
mock_get_all_vdi_uuids.assert_called_once_with(self.vmops._session,
|
|
vm_ref, min_userdevice=4)
|
|
mock_apply_orig.assert_called_once_with(instance, vm_ref)
|
|
mock_shutdown.assert_called_once_with(instance, vm_ref)
|
|
|
|
m_vhd_expected = [mock.call(self.vmops._session, instance,
|
|
"4-parent", dest, sr_path, 1, 1),
|
|
mock.call(self.vmops._session, instance,
|
|
"5-parent", dest, sr_path, 1, 2),
|
|
mock.call(self.vmops._session, instance,
|
|
"4-root", dest, sr_path, 0, 1),
|
|
mock.call(self.vmops._session, instance,
|
|
"5-root", dest, sr_path, 0, 2)]
|
|
self.assertEqual(m_vhd_expected, mock_migrate_vhd.call_args_list)
|
|
|
|
prog_expected = [
|
|
mock.call(context, instance, 1, 5),
|
|
mock.call(context, instance, 2, 5),
|
|
mock.call(context, instance, 3, 5),
|
|
mock.call(context, instance, 4, 5)
|
|
# 5/5: step to be executed by finish migration.
|
|
]
|
|
self.assertEqual(prog_expected, mock_update_progress.call_args_list)
|
|
|
|
@mock.patch.object(vmops.VMOps, '_restore_orig_vm_and_cleanup_orphan')
|
|
@mock.patch.object(volume_utils, 'is_booted_from_volume',
|
|
return_value=False)
|
|
def test_migrate_disk_resizing_up_rollback(self,
|
|
mock_is_booted_from_volume,
|
|
mock_restore,
|
|
mock_apply_orig, mock_update_progress, mock_get_all_vdi_uuids,
|
|
mock_shutdown, mock_migrate_vhd, mock_get_vdi_for_vm):
|
|
context = "ctxt"
|
|
instance = {"name": "fake", "uuid": "fake"}
|
|
dest = "dest"
|
|
vm_ref = "vm_ref"
|
|
sr_path = "sr_path"
|
|
|
|
mock_migrate_vhd.side_effect = test.TestingException
|
|
mock_restore.side_effect = test.TestingException
|
|
|
|
with mock.patch.object(vm_utils, '_snapshot_attached_here_impl',
|
|
self._fake_snapshot_attached_here):
|
|
self.assertRaises(exception.InstanceFaultRollback,
|
|
self.vmops._migrate_disk_resizing_up,
|
|
context, instance, dest, vm_ref, sr_path)
|
|
|
|
mock_apply_orig.assert_called_once_with(instance, vm_ref)
|
|
mock_restore.assert_called_once_with(instance)
|
|
mock_migrate_vhd.assert_called_once_with(self.vmops._session,
|
|
instance, "parent", dest, sr_path, 1)
|
|
|
|
|
|
class CreateVMRecordTestCase(VMOpsTestBase):
|
|
@mock.patch.object(vm_utils, 'determine_vm_mode')
|
|
@mock.patch.object(vm_utils, 'get_vm_device_id')
|
|
@mock.patch.object(vm_utils, 'create_vm')
|
|
def test_create_vm_record_with_vm_device_id(self, mock_create_vm,
|
|
mock_get_vm_device_id, mock_determine_vm_mode):
|
|
|
|
context = "context"
|
|
instance = objects.Instance(vm_mode="vm_mode", uuid=uuids.instance)
|
|
name_label = "dummy"
|
|
disk_image_type = "vhd"
|
|
kernel_file = "kernel"
|
|
ramdisk_file = "ram"
|
|
device_id = "0002"
|
|
image_properties = {"xenapi_device_id": device_id}
|
|
image_meta = objects.ImageMeta.from_dict(
|
|
{"properties": image_properties})
|
|
rescue = False
|
|
session = "session"
|
|
self.vmops._session = session
|
|
mock_get_vm_device_id.return_value = device_id
|
|
mock_determine_vm_mode.return_value = "vm_mode"
|
|
|
|
self.vmops._create_vm_record(context, instance, name_label,
|
|
disk_image_type, kernel_file, ramdisk_file, image_meta, rescue)
|
|
|
|
mock_get_vm_device_id.assert_called_with(session, image_meta)
|
|
mock_create_vm.assert_called_with(session, instance, name_label,
|
|
kernel_file, ramdisk_file, False, device_id)
|
|
|
|
|
|
class BootableTestCase(VMOpsTestBase):
|
|
|
|
def setUp(self):
|
|
super(BootableTestCase, self).setUp()
|
|
|
|
self.instance = {"name": "test", "uuid": "fake"}
|
|
vm_rec, self.vm_ref = self.create_vm('test')
|
|
|
|
# sanity check bootlock is initially disabled:
|
|
self.assertEqual({}, vm_rec['blocked_operations'])
|
|
|
|
def _get_blocked(self):
|
|
vm_rec = self._session.call_xenapi("VM.get_record", self.vm_ref)
|
|
return vm_rec['blocked_operations']
|
|
|
|
def test_acquire_bootlock(self):
|
|
self.vmops._acquire_bootlock(self.vm_ref)
|
|
blocked = self._get_blocked()
|
|
self.assertIn('start', blocked)
|
|
|
|
def test_release_bootlock(self):
|
|
self.vmops._acquire_bootlock(self.vm_ref)
|
|
self.vmops._release_bootlock(self.vm_ref)
|
|
blocked = self._get_blocked()
|
|
self.assertNotIn('start', blocked)
|
|
|
|
def test_set_bootable(self):
|
|
self.vmops.set_bootable(self.instance, True)
|
|
blocked = self._get_blocked()
|
|
self.assertNotIn('start', blocked)
|
|
|
|
def test_set_not_bootable(self):
|
|
self.vmops.set_bootable(self.instance, False)
|
|
blocked = self._get_blocked()
|
|
self.assertIn('start', blocked)
|
|
|
|
|
|
@mock.patch.object(vm_utils, 'update_vdi_virtual_size', autospec=True)
|
|
class ResizeVdisTestCase(VMOpsTestBase):
|
|
def test_dont_resize_root_volumes_osvol_false(self, mock_resize):
|
|
instance = fake_instance.fake_instance_obj(
|
|
None, flavor=objects.Flavor(root_gb=20))
|
|
vdis = {'root': {'osvol': False, 'ref': 'vdi_ref'}}
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
self.assertTrue(mock_resize.called)
|
|
|
|
def test_dont_resize_root_volumes_osvol_true(self, mock_resize):
|
|
instance = fake_instance.fake_instance_obj(
|
|
None, flavor=objects.Flavor(root_gb=20))
|
|
vdis = {'root': {'osvol': True}}
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
self.assertFalse(mock_resize.called)
|
|
|
|
def test_dont_resize_root_volumes_no_osvol(self, mock_resize):
|
|
instance = fake_instance.fake_instance_obj(
|
|
None, flavor=objects.Flavor(root_gb=20))
|
|
vdis = {'root': {}}
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
self.assertFalse(mock_resize.called)
|
|
|
|
@mock.patch.object(vm_utils, 'get_ephemeral_disk_sizes')
|
|
def test_ensure_ephemeral_resize_with_root_volume(self, mock_sizes,
|
|
mock_resize):
|
|
mock_sizes.return_value = [2000, 1000]
|
|
instance = fake_instance.fake_instance_obj(
|
|
None, flavor=objects.Flavor(root_gb=20, ephemeral_gb=20))
|
|
ephemerals = {"4": {"ref": 4}, "5": {"ref": 5}}
|
|
vdis = {'root': {'osvol': True, 'ref': 'vdi_ref'},
|
|
'ephemerals': ephemerals}
|
|
with mock.patch.object(vm_utils, 'generate_single_ephemeral',
|
|
autospec=True) as g:
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
self.assertEqual([mock.call(self.vmops._session, instance, 4,
|
|
2000),
|
|
mock.call(self.vmops._session, instance, 5,
|
|
1000)],
|
|
mock_resize.call_args_list)
|
|
self.assertFalse(g.called)
|
|
|
|
def test_resize_up_vdis_root(self, mock_resize):
|
|
instance = objects.Instance(flavor=objects.Flavor(root_gb=20,
|
|
ephemeral_gb=0))
|
|
self.vmops._resize_up_vdis(instance, {"root": {"ref": "vdi_ref"}})
|
|
mock_resize.assert_called_once_with(self.vmops._session, instance,
|
|
"vdi_ref", 20)
|
|
|
|
def test_resize_up_vdis_zero_disks(self, mock_resize):
|
|
instance = objects.Instance(flavor=objects.Flavor(root_gb=0,
|
|
ephemeral_gb=0))
|
|
self.vmops._resize_up_vdis(instance, {"root": {}})
|
|
self.assertFalse(mock_resize.called)
|
|
|
|
def test_resize_up_vdis_no_vdis_like_initial_spawn(self, mock_resize):
|
|
instance = objects.Instance(flavor=objects.Flavor(root_gb=0,
|
|
ephemeral_gb=3000))
|
|
vdis = {}
|
|
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
|
|
self.assertFalse(mock_resize.called)
|
|
|
|
@mock.patch.object(vm_utils, 'get_ephemeral_disk_sizes')
|
|
def test_resize_up_vdis_ephemeral(self, mock_sizes, mock_resize):
|
|
mock_sizes.return_value = [2000, 1000]
|
|
instance = objects.Instance(flavor=objects.Flavor(root_gb=0,
|
|
ephemeral_gb=3000))
|
|
ephemerals = {"4": {"ref": 4}, "5": {"ref": 5}}
|
|
vdis = {"ephemerals": ephemerals}
|
|
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
|
|
mock_sizes.assert_called_once_with(3000)
|
|
expected = [mock.call(self.vmops._session, instance, 4, 2000),
|
|
mock.call(self.vmops._session, instance, 5, 1000)]
|
|
self.assertEqual(expected, mock_resize.call_args_list)
|
|
|
|
@mock.patch.object(vm_utils, 'generate_single_ephemeral')
|
|
@mock.patch.object(vm_utils, 'get_ephemeral_disk_sizes')
|
|
def test_resize_up_vdis_ephemeral_with_generate(self, mock_sizes,
|
|
mock_generate,
|
|
mock_resize):
|
|
mock_sizes.return_value = [2000, 1000]
|
|
instance = objects.Instance(uuid=uuids.instance,
|
|
flavor=objects.Flavor(root_gb=0,
|
|
ephemeral_gb=3000))
|
|
ephemerals = {"4": {"ref": 4}}
|
|
vdis = {"ephemerals": ephemerals}
|
|
|
|
self.vmops._resize_up_vdis(instance, vdis)
|
|
|
|
mock_sizes.assert_called_once_with(3000)
|
|
mock_resize.assert_called_once_with(self.vmops._session, instance,
|
|
4, 2000)
|
|
mock_generate.assert_called_once_with(self.vmops._session, instance,
|
|
None, 5, 1000)
|
|
|
|
|
|
@mock.patch.object(vm_utils, 'remove_old_snapshots')
|
|
class CleanupFailedSnapshotTestCase(VMOpsTestBase):
|
|
def test_post_interrupted_snapshot_cleanup(self, mock_remove):
|
|
self.vmops._get_vm_opaque_ref = mock.Mock()
|
|
self.vmops._get_vm_opaque_ref.return_value = "vm_ref"
|
|
|
|
self.vmops.post_interrupted_snapshot_cleanup("context", "instance")
|
|
|
|
mock_remove.assert_called_once_with(self.vmops._session,
|
|
"instance", "vm_ref")
|
|
|
|
|
|
class XenstoreCallsTestCase(VMOpsTestBase):
|
|
"""Test cases for Read/Write/Delete/Update xenstore calls
|
|
from vmops.
|
|
"""
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_dom_id')
|
|
@mock.patch.object(host_xenstore, 'read_record')
|
|
def test_read_from_xenstore(self, mock_read_record, mock_dom_id):
|
|
mock_read_record.return_value = "fake_xapi_return"
|
|
mock_dom_id.return_value = "fake_dom_id"
|
|
fake_instance = {"name": "fake_instance"}
|
|
path = "attr/PVAddons/MajorVersion"
|
|
self.assertEqual("fake_xapi_return",
|
|
self.vmops._read_from_xenstore(fake_instance, path,
|
|
vm_ref="vm_ref"))
|
|
mock_dom_id.assert_called_once_with(fake_instance, "vm_ref")
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_dom_id')
|
|
@mock.patch.object(host_xenstore, 'read_record')
|
|
def test_read_from_xenstore_ignore_missing_path(self, mock_read_record,
|
|
mock_dom_id):
|
|
mock_read_record.return_value = "fake_xapi_return"
|
|
mock_dom_id.return_value = "fake_dom_id"
|
|
fake_instance = {"name": "fake_instance"}
|
|
path = "attr/PVAddons/MajorVersion"
|
|
self.vmops._read_from_xenstore(fake_instance, path, vm_ref="vm_ref")
|
|
mock_read_record.assert_called_once_with(
|
|
self._session, "fake_dom_id", path, ignore_missing_path=True)
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_dom_id')
|
|
@mock.patch.object(host_xenstore, 'read_record')
|
|
def test_read_from_xenstore_missing_path(self, mock_read_record,
|
|
mock_dom_id):
|
|
mock_read_record.return_value = "fake_xapi_return"
|
|
mock_dom_id.return_value = "fake_dom_id"
|
|
fake_instance = {"name": "fake_instance"}
|
|
path = "attr/PVAddons/MajorVersion"
|
|
self.vmops._read_from_xenstore(fake_instance, path, vm_ref="vm_ref",
|
|
ignore_missing_path=False)
|
|
mock_read_record.assert_called_once_with(self._session, "fake_dom_id",
|
|
path,
|
|
ignore_missing_path=False)
|
|
|
|
|
|
class LiveMigrateTestCase(VMOpsTestBase):
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_network_ref')
|
|
@mock.patch.object(vmops.VMOps, '_ensure_host_in_aggregate')
|
|
def _test_check_can_live_migrate_destination_shared_storage(
|
|
self,
|
|
shared,
|
|
mock_ensure_host,
|
|
mock_net_ref):
|
|
fake_instance = {"name": "fake_instance", "host": "fake_host"}
|
|
block_migration = None
|
|
disk_over_commit = False
|
|
ctxt = 'ctxt'
|
|
mock_net_ref.return_value = 'fake_net_ref'
|
|
|
|
with mock.patch.object(self._session, 'get_rec') as fake_sr_rec:
|
|
fake_sr_rec.return_value = {'shared': shared}
|
|
migrate_data_ret = self.vmops.check_can_live_migrate_destination(
|
|
ctxt, fake_instance, block_migration, disk_over_commit)
|
|
|
|
if shared:
|
|
self.assertFalse(migrate_data_ret.block_migration)
|
|
else:
|
|
self.assertTrue(migrate_data_ret.block_migration)
|
|
self.assertEqual({'': 'fake_net_ref'},
|
|
migrate_data_ret.vif_uuid_map)
|
|
|
|
def test_check_can_live_migrate_destination_shared_storage(self):
|
|
self._test_check_can_live_migrate_destination_shared_storage(True)
|
|
|
|
def test_check_can_live_migrate_destination_shared_storage_false(self):
|
|
self._test_check_can_live_migrate_destination_shared_storage(False)
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_network_ref')
|
|
@mock.patch.object(vmops.VMOps, '_ensure_host_in_aggregate',
|
|
side_effect=exception.MigrationPreCheckError(reason=""))
|
|
def test_check_can_live_migrate_destination_block_migration(
|
|
self,
|
|
mock_ensure_host,
|
|
mock_net_ref):
|
|
fake_instance = {"name": "fake_instance", "host": "fake_host"}
|
|
block_migration = None
|
|
disk_over_commit = False
|
|
ctxt = 'ctxt'
|
|
mock_net_ref.return_value = 'fake_net_ref'
|
|
|
|
migrate_data_ret = self.vmops.check_can_live_migrate_destination(
|
|
ctxt, fake_instance, block_migration, disk_over_commit)
|
|
|
|
self.assertTrue(migrate_data_ret.block_migration)
|
|
self.assertEqual(vm_utils.safe_find_sr(self._session),
|
|
migrate_data_ret.destination_sr_ref)
|
|
self.assertEqual({'value': 'fake_migrate_data'},
|
|
migrate_data_ret.migrate_send_data)
|
|
self.assertEqual({'': 'fake_net_ref'},
|
|
migrate_data_ret.vif_uuid_map)
|
|
|
|
@mock.patch.object(vmops.objects.AggregateList, 'get_by_host')
|
|
def test_get_host_uuid_from_aggregate_no_aggr(self, mock_get_by_host):
|
|
mock_get_by_host.return_value = objects.AggregateList(objects=[])
|
|
context = "ctx"
|
|
hostname = "other_host"
|
|
self.assertRaises(exception.MigrationPreCheckError,
|
|
self.vmops._get_host_uuid_from_aggregate,
|
|
context, hostname)
|
|
|
|
@mock.patch.object(vmops.objects.AggregateList, 'get_by_host')
|
|
def test_get_host_uuid_from_aggregate_bad_aggr(self, mock_get_by_host):
|
|
context = "ctx"
|
|
hostname = "other_host"
|
|
fake_aggregate_obj = objects.Aggregate(hosts=['fake'],
|
|
metadata={'this': 'that'})
|
|
fake_aggr_list = objects.AggregateList(objects=[fake_aggregate_obj])
|
|
mock_get_by_host.return_value = fake_aggr_list
|
|
|
|
self.assertRaises(exception.MigrationPreCheckError,
|
|
self.vmops._get_host_uuid_from_aggregate,
|
|
context, hostname)
|
|
|
|
@mock.patch.object(vmops.VMOps, 'create_interim_networks')
|
|
@mock.patch.object(vmops.VMOps, 'connect_block_device_volumes')
|
|
def test_pre_live_migration(self, mock_connect, mock_create):
|
|
migrate_data = objects.XenapiLiveMigrateData()
|
|
migrate_data.block_migration = True
|
|
sr_uuid_map = {"sr_uuid": "sr_ref"}
|
|
vif_uuid_map = {"neutron_vif_uuid": "dest_network_ref"}
|
|
mock_connect.return_value = {"sr_uuid": "sr_ref"}
|
|
mock_create.return_value = {"neutron_vif_uuid": "dest_network_ref"}
|
|
result = self.vmops.pre_live_migration(
|
|
None, None, "bdi", "fake_network_info", None, migrate_data)
|
|
|
|
self.assertTrue(result.block_migration)
|
|
self.assertEqual(result.sr_uuid_map, sr_uuid_map)
|
|
self.assertEqual(result.vif_uuid_map, vif_uuid_map)
|
|
mock_connect.assert_called_once_with("bdi")
|
|
mock_create.assert_called_once_with("fake_network_info")
|
|
|
|
@mock.patch.object(vmops.VMOps, '_delete_networks_and_bridges')
|
|
def test_post_live_migration_at_source(self, mock_delete):
|
|
self.vmops.post_live_migration_at_source('fake_context',
|
|
'fake_instance',
|
|
'fake_network_info')
|
|
mock_delete.assert_called_once_with('fake_instance',
|
|
'fake_network_info')
|
|
|
|
|
|
class LiveMigrateFakeVersionTestCase(VMOpsTestBase):
|
|
@mock.patch.object(vmops.VMOps, '_pv_device_reported')
|
|
@mock.patch.object(vmops.VMOps, '_pv_driver_version_reported')
|
|
@mock.patch.object(vmops.VMOps, '_write_fake_pv_version')
|
|
def test_ensure_pv_driver_info_for_live_migration(
|
|
self,
|
|
mock_write_fake_pv_version,
|
|
mock_pv_driver_version_reported,
|
|
mock_pv_device_reported):
|
|
|
|
mock_pv_device_reported.return_value = True
|
|
mock_pv_driver_version_reported.return_value = False
|
|
fake_instance = {"name": "fake_instance"}
|
|
self.vmops._ensure_pv_driver_info_for_live_migration(fake_instance,
|
|
"vm_rec")
|
|
|
|
mock_write_fake_pv_version.assert_called_once_with(fake_instance,
|
|
"vm_rec")
|
|
|
|
@mock.patch.object(vmops.VMOps, '_read_from_xenstore')
|
|
def test_pv_driver_version_reported_None(self, fake_read_from_xenstore):
|
|
fake_read_from_xenstore.return_value = '"None"'
|
|
fake_instance = {"name": "fake_instance"}
|
|
self.assertFalse(self.vmops._pv_driver_version_reported(fake_instance,
|
|
"vm_ref"))
|
|
|
|
@mock.patch.object(vmops.VMOps, '_read_from_xenstore')
|
|
def test_pv_driver_version_reported(self, fake_read_from_xenstore):
|
|
fake_read_from_xenstore.return_value = '6.2.0'
|
|
fake_instance = {"name": "fake_instance"}
|
|
self.assertTrue(self.vmops._pv_driver_version_reported(fake_instance,
|
|
"vm_ref"))
|
|
|
|
@mock.patch.object(vmops.VMOps, '_read_from_xenstore')
|
|
def test_pv_device_reported(self, fake_read_from_xenstore):
|
|
with mock.patch.object(self._session.VM, 'get_record') as fake_vm_rec:
|
|
fake_vm_rec.return_value = {'VIFs': 'fake-vif-object'}
|
|
with mock.patch.object(self._session, 'call_xenapi') as fake_call:
|
|
fake_call.return_value = {'device': '0'}
|
|
fake_read_from_xenstore.return_value = '4'
|
|
fake_instance = {"name": "fake_instance"}
|
|
self.assertTrue(self.vmops._pv_device_reported(fake_instance,
|
|
"vm_ref"))
|
|
|
|
@mock.patch.object(vmops.VMOps, '_read_from_xenstore')
|
|
def test_pv_device_not_reported(self, fake_read_from_xenstore):
|
|
with mock.patch.object(self._session.VM, 'get_record') as fake_vm_rec:
|
|
fake_vm_rec.return_value = {'VIFs': 'fake-vif-object'}
|
|
with mock.patch.object(self._session, 'call_xenapi') as fake_call:
|
|
fake_call.return_value = {'device': '0'}
|
|
fake_read_from_xenstore.return_value = '0'
|
|
fake_instance = {"name": "fake_instance"}
|
|
self.assertFalse(self.vmops._pv_device_reported(fake_instance,
|
|
"vm_ref"))
|
|
|
|
@mock.patch.object(vmops.VMOps, '_read_from_xenstore')
|
|
def test_pv_device_None_reported(self, fake_read_from_xenstore):
|
|
with mock.patch.object(self._session.VM, 'get_record') as fake_vm_rec:
|
|
fake_vm_rec.return_value = {'VIFs': 'fake-vif-object'}
|
|
with mock.patch.object(self._session, 'call_xenapi') as fake_call:
|
|
fake_call.return_value = {'device': '0'}
|
|
fake_read_from_xenstore.return_value = '"None"'
|
|
fake_instance = {"name": "fake_instance"}
|
|
self.assertFalse(self.vmops._pv_device_reported(fake_instance,
|
|
"vm_ref"))
|
|
|
|
@mock.patch.object(vmops.VMOps, '_write_to_xenstore')
|
|
def test_write_fake_pv_version(self, fake_write_to_xenstore):
|
|
fake_write_to_xenstore.return_value = 'fake_return'
|
|
fake_instance = {"name": "fake_instance"}
|
|
with mock.patch.object(self._session, 'product_version') as version:
|
|
version.return_value = ('6', '2', '0')
|
|
self.assertIsNone(self.vmops._write_fake_pv_version(fake_instance,
|
|
"vm_ref"))
|
|
|
|
|
|
class LiveMigrateHelperTestCase(VMOpsTestBase):
|
|
def test_connect_block_device_volumes_none(self):
|
|
self.assertEqual({}, self.vmops.connect_block_device_volumes(None))
|
|
|
|
@mock.patch.object(volumeops.VolumeOps, "connect_volume")
|
|
def test_connect_block_device_volumes_calls_connect(self, mock_connect):
|
|
with mock.patch.object(self.vmops._session,
|
|
"call_xenapi") as mock_session:
|
|
mock_connect.return_value = ("sr_uuid", None)
|
|
mock_session.return_value = "sr_ref"
|
|
bdm = {"connection_info": "c_info"}
|
|
bdi = {"block_device_mapping": [bdm]}
|
|
result = self.vmops.connect_block_device_volumes(bdi)
|
|
|
|
self.assertEqual({'sr_uuid': 'sr_ref'}, result)
|
|
|
|
mock_connect.assert_called_once_with("c_info")
|
|
mock_session.assert_called_once_with("SR.get_by_uuid",
|
|
"sr_uuid")
|
|
|
|
@mock.patch.object(volumeops.VolumeOps, "connect_volume")
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_connect_block_device_volumes_calls_forget_sr(self, mock_forget,
|
|
mock_connect):
|
|
bdms = [{'connection_info': 'info1'},
|
|
{'connection_info': 'info2'}]
|
|
|
|
def fake_connect(connection_info):
|
|
expected = bdms[mock_connect.call_count - 1]['connection_info']
|
|
self.assertEqual(expected, connection_info)
|
|
|
|
if mock_connect.call_count == 2:
|
|
raise exception.VolumeDriverNotFound(driver_type='123')
|
|
|
|
return ('sr_uuid_1', None)
|
|
|
|
def fake_call_xenapi(method, uuid):
|
|
self.assertEqual('sr_uuid_1', uuid)
|
|
return 'sr_ref_1'
|
|
|
|
mock_connect.side_effect = fake_connect
|
|
|
|
with mock.patch.object(self.vmops._session, "call_xenapi",
|
|
side_effect=fake_call_xenapi):
|
|
self.assertRaises(exception.VolumeDriverNotFound,
|
|
self.vmops.connect_block_device_volumes,
|
|
{'block_device_mapping': bdms})
|
|
mock_forget.assert_called_once_with(self.vmops._session,
|
|
'sr_ref_1')
|
|
|
|
def _call_live_migrate_command_with_migrate_send_data(self, migrate_data):
|
|
command_name = 'test_command'
|
|
vm_ref = "vm_ref"
|
|
|
|
def side_effect(method, *args):
|
|
if method == "SR.get_by_uuid":
|
|
return "sr_ref_new"
|
|
xmlrpclib.dumps(args, method, allow_none=1)
|
|
|
|
with mock.patch.object(self.vmops,
|
|
"_generate_vdi_map") as mock_gen_vdi_map, \
|
|
mock.patch.object(self.vmops._session,
|
|
'call_xenapi') as mock_call_xenapi, \
|
|
mock.patch.object(self.vmops,
|
|
"_generate_vif_network_map") as mock_vif_map:
|
|
mock_call_xenapi.side_effect = side_effect
|
|
mock_gen_vdi_map.side_effect = [
|
|
{"vdi": "sr_ref"}, {"vdi": "sr_ref_2"}]
|
|
mock_vif_map.return_value = {"vif_ref1": "dest_net_ref"}
|
|
|
|
self.vmops._call_live_migrate_command(command_name,
|
|
vm_ref, migrate_data)
|
|
|
|
expect_vif_map = {}
|
|
if 'vif_uuid_map' in migrate_data:
|
|
expect_vif_map.update({"vif_ref1": "dest_net_ref"})
|
|
expected_vdi_map = {'vdi': 'sr_ref'}
|
|
if 'sr_uuid_map' in migrate_data:
|
|
expected_vdi_map = {'vdi': 'sr_ref_2'}
|
|
self.assertEqual(mock_call_xenapi.call_args_list[-1],
|
|
mock.call(command_name, vm_ref,
|
|
migrate_data.migrate_send_data, True,
|
|
expected_vdi_map, expect_vif_map, {}))
|
|
|
|
self.assertEqual(mock_gen_vdi_map.call_args_list[0],
|
|
mock.call(migrate_data.destination_sr_ref, vm_ref))
|
|
if 'sr_uuid_map' in migrate_data:
|
|
self.assertEqual(mock_gen_vdi_map.call_args_list[1],
|
|
mock.call(migrate_data.sr_uuid_map["sr_uuid2"], vm_ref,
|
|
"sr_ref_new"))
|
|
|
|
def test_call_live_migrate_command_with_full_data(self):
|
|
migrate_data = objects.XenapiLiveMigrateData()
|
|
migrate_data.migrate_send_data = {"foo": "bar"}
|
|
migrate_data.destination_sr_ref = "sr_ref"
|
|
migrate_data.sr_uuid_map = {"sr_uuid2": "sr_ref_3"}
|
|
migrate_data.vif_uuid_map = {"vif_id": "dest_net_ref"}
|
|
self._call_live_migrate_command_with_migrate_send_data(migrate_data)
|
|
|
|
def test_call_live_migrate_command_with_no_sr_uuid_map(self):
|
|
migrate_data = objects.XenapiLiveMigrateData()
|
|
migrate_data.migrate_send_data = {"foo": "baz"}
|
|
migrate_data.destination_sr_ref = "sr_ref"
|
|
self._call_live_migrate_command_with_migrate_send_data(migrate_data)
|
|
|
|
def test_call_live_migrate_command_with_no_migrate_send_data(self):
|
|
migrate_data = objects.XenapiLiveMigrateData()
|
|
self.assertRaises(exception.InvalidParameterValue,
|
|
self._call_live_migrate_command_with_migrate_send_data,
|
|
migrate_data)
|
|
|
|
def test_generate_vif_network_map(self):
|
|
with mock.patch.object(self._session.VIF,
|
|
'get_other_config') as mock_other_config, \
|
|
mock.patch.object(self._session.VM,
|
|
'get_VIFs') as mock_get_vif:
|
|
mock_other_config.side_effect = [{'neutron-port-id': 'vif_id_a'},
|
|
{'neutron-port-id': 'vif_id_b'}]
|
|
mock_get_vif.return_value = ['vif_ref1', 'vif_ref2']
|
|
vif_uuid_map = {'vif_id_b': 'dest_net_ref2',
|
|
'vif_id_a': 'dest_net_ref1'}
|
|
vif_map = self.vmops._generate_vif_network_map('vm_ref',
|
|
vif_uuid_map)
|
|
expected = {'vif_ref1': 'dest_net_ref1',
|
|
'vif_ref2': 'dest_net_ref2'}
|
|
self.assertEqual(vif_map, expected)
|
|
|
|
def test_generate_vif_network_map_default_net(self):
|
|
with mock.patch.object(self._session.VIF,
|
|
'get_other_config') as mock_other_config, \
|
|
mock.patch.object(self._session.VM,
|
|
'get_VIFs') as mock_get_vif:
|
|
mock_other_config.side_effect = [{'neutron-port-id': 'vif_id_a'},
|
|
{'neutron-port-id': 'vif_id_b'}]
|
|
mock_get_vif.return_value = ['vif_ref1']
|
|
vif_uuid_map = {'': 'default_net_ref'}
|
|
vif_map = self.vmops._generate_vif_network_map('vm_ref',
|
|
vif_uuid_map)
|
|
expected = {'vif_ref1': 'default_net_ref'}
|
|
self.assertEqual(vif_map, expected)
|
|
|
|
def test_generate_vif_network_map_exception(self):
|
|
with mock.patch.object(self._session.VIF,
|
|
'get_other_config') as mock_other_config, \
|
|
mock.patch.object(self._session.VM,
|
|
'get_VIFs') as mock_get_vif:
|
|
mock_other_config.side_effect = [{'neutron-port-id': 'vif_id_a'},
|
|
{'neutron-port-id': 'vif_id_b'}]
|
|
mock_get_vif.return_value = ['vif_ref1', 'vif_ref2']
|
|
vif_uuid_map = {'vif_id_c': 'dest_net_ref2',
|
|
'vif_id_d': 'dest_net_ref1'}
|
|
self.assertRaises(exception.MigrationError,
|
|
self.vmops._generate_vif_network_map,
|
|
'vm_ref', vif_uuid_map)
|
|
|
|
def test_generate_vif_network_map_exception_no_iface(self):
|
|
with mock.patch.object(self._session.VIF,
|
|
'get_other_config') as mock_other_config, \
|
|
mock.patch.object(self._session.VM,
|
|
'get_VIFs') as mock_get_vif:
|
|
mock_other_config.return_value = {}
|
|
mock_get_vif.return_value = ['vif_ref1']
|
|
vif_uuid_map = {}
|
|
self.assertRaises(exception.MigrationError,
|
|
self.vmops._generate_vif_network_map,
|
|
'vm_ref', vif_uuid_map)
|
|
|
|
def test_delete_networks_and_bridges(self):
|
|
self.vmops.vif_driver = mock.Mock()
|
|
network_info = ['fake_vif']
|
|
self.vmops._delete_networks_and_bridges('fake_instance', network_info)
|
|
self.vmops.vif_driver.delete_network_and_bridge.\
|
|
assert_called_once_with('fake_instance', 'fake_vif')
|
|
|
|
def test_create_interim_networks(self):
|
|
class FakeVifDriver(object):
|
|
def create_vif_interim_network(self, vif):
|
|
if vif['id'] == "vif_1":
|
|
return "network_ref_1"
|
|
if vif['id'] == "vif_2":
|
|
return "network_ref_2"
|
|
|
|
network_info = [{'id': "vif_1"}, {'id': 'vif_2'}]
|
|
self.vmops.vif_driver = FakeVifDriver()
|
|
vif_map = self.vmops.create_interim_networks(network_info)
|
|
self.assertEqual(vif_map, {'vif_1': 'network_ref_1',
|
|
'vif_2': 'network_ref_2'})
|
|
|
|
|
|
class RollbackLiveMigrateDestinationTestCase(VMOpsTestBase):
|
|
@mock.patch.object(vmops.VMOps, '_delete_networks_and_bridges')
|
|
@mock.patch.object(volume_utils, 'find_sr_by_uuid', return_value='sr_ref')
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_rollback_dest_calls_sr_forget(self, forget_sr, sr_ref,
|
|
delete_networks_bridges):
|
|
block_device_info = {'block_device_mapping': [{'connection_info':
|
|
{'data': {'volume_id': 'fake-uuid',
|
|
'target_iqn': 'fake-iqn',
|
|
'target_portal': 'fake-portal'}}}]}
|
|
network_info = [{'id': 'vif1'}]
|
|
self.vmops.rollback_live_migration_at_destination('instance',
|
|
network_info,
|
|
block_device_info)
|
|
forget_sr.assert_called_once_with(self.vmops._session, 'sr_ref')
|
|
delete_networks_bridges.assert_called_once_with(
|
|
'instance', [{'id': 'vif1'}])
|
|
|
|
@mock.patch.object(vmops.VMOps, '_delete_networks_and_bridges')
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
@mock.patch.object(volume_utils, 'find_sr_by_uuid',
|
|
side_effect=test.TestingException)
|
|
def test_rollback_dest_handles_exception(self, find_sr_ref, forget_sr,
|
|
delete_networks_bridges):
|
|
block_device_info = {'block_device_mapping': [{'connection_info':
|
|
{'data': {'volume_id': 'fake-uuid',
|
|
'target_iqn': 'fake-iqn',
|
|
'target_portal': 'fake-portal'}}}]}
|
|
network_info = [{'id': 'vif1'}]
|
|
self.vmops.rollback_live_migration_at_destination('instance',
|
|
network_info,
|
|
block_device_info)
|
|
self.assertFalse(forget_sr.called)
|
|
delete_networks_bridges.assert_called_once_with(
|
|
'instance', [{'id': 'vif1'}])
|
|
|
|
|
|
@mock.patch.object(vmops.VMOps, '_resize_ensure_vm_is_shutdown')
|
|
@mock.patch.object(vmops.VMOps, '_apply_orig_vm_name_label')
|
|
@mock.patch.object(vmops.VMOps, '_update_instance_progress')
|
|
@mock.patch.object(vm_utils, 'get_vdi_for_vm_safely')
|
|
@mock.patch.object(vm_utils, 'resize_disk')
|
|
@mock.patch.object(vm_utils, 'migrate_vhd')
|
|
@mock.patch.object(vm_utils, 'destroy_vdi')
|
|
class MigrateDiskResizingDownTestCase(VMOpsTestBase):
|
|
def test_migrate_disk_resizing_down_works_no_ephemeral(
|
|
self,
|
|
mock_destroy_vdi,
|
|
mock_migrate_vhd,
|
|
mock_resize_disk,
|
|
mock_get_vdi_for_vm_safely,
|
|
mock_update_instance_progress,
|
|
mock_apply_orig_vm_name_label,
|
|
mock_resize_ensure_vm_is_shutdown):
|
|
|
|
context = "ctx"
|
|
instance = {"name": "fake", "uuid": "uuid"}
|
|
dest = "dest"
|
|
vm_ref = "vm_ref"
|
|
sr_path = "sr_path"
|
|
instance_type = dict(root_gb=1)
|
|
old_vdi_ref = "old_ref"
|
|
new_vdi_ref = "new_ref"
|
|
new_vdi_uuid = "new_uuid"
|
|
|
|
mock_get_vdi_for_vm_safely.return_value = (old_vdi_ref, None)
|
|
mock_resize_disk.return_value = (new_vdi_ref, new_vdi_uuid)
|
|
|
|
self.vmops._migrate_disk_resizing_down(context, instance, dest,
|
|
instance_type, vm_ref, sr_path)
|
|
|
|
mock_get_vdi_for_vm_safely.assert_called_once_with(
|
|
self.vmops._session,
|
|
vm_ref)
|
|
mock_resize_ensure_vm_is_shutdown.assert_called_once_with(
|
|
instance, vm_ref)
|
|
mock_apply_orig_vm_name_label.assert_called_once_with(
|
|
instance, vm_ref)
|
|
mock_resize_disk.assert_called_once_with(
|
|
self.vmops._session,
|
|
instance,
|
|
old_vdi_ref,
|
|
instance_type)
|
|
mock_migrate_vhd.assert_called_once_with(
|
|
self.vmops._session,
|
|
instance,
|
|
new_vdi_uuid,
|
|
dest,
|
|
sr_path, 0)
|
|
mock_destroy_vdi.assert_called_once_with(
|
|
self.vmops._session,
|
|
new_vdi_ref)
|
|
|
|
prog_expected = [
|
|
mock.call(context, instance, 1, 5),
|
|
mock.call(context, instance, 2, 5),
|
|
mock.call(context, instance, 3, 5),
|
|
mock.call(context, instance, 4, 5)
|
|
# 5/5: step to be executed by finish migration.
|
|
]
|
|
self.assertEqual(prog_expected,
|
|
mock_update_instance_progress.call_args_list)
|
|
|
|
|
|
class GetVdisForInstanceTestCase(VMOpsTestBase):
|
|
"""Tests get_vdis_for_instance utility method."""
|
|
def setUp(self):
|
|
super(GetVdisForInstanceTestCase, self).setUp()
|
|
self.context = context.get_admin_context()
|
|
self.context.auth_token = 'auth_token'
|
|
self.session = mock.Mock()
|
|
self.vmops._session = self.session
|
|
self.instance = fake_instance.fake_instance_obj(self.context)
|
|
self.name_label = 'name'
|
|
self.image = 'fake_image_id'
|
|
|
|
@mock.patch.object(volumeops.VolumeOps, "connect_volume",
|
|
return_value=("sr", "vdi_uuid"))
|
|
def test_vdis_for_instance_bdi_password_scrubbed(self, get_uuid_mock):
|
|
# setup fake data
|
|
data = {'name_label': self.name_label,
|
|
'sr_uuid': 'fake',
|
|
'auth_password': 'scrubme'}
|
|
bdm = [{'mount_device': '/dev/vda',
|
|
'connection_info': {'data': data}}]
|
|
bdi = {'root_device_name': 'vda',
|
|
'block_device_mapping': bdm}
|
|
|
|
# Tests that the parameters to the to_xml method are sanitized for
|
|
# passwords when logged.
|
|
def fake_debug(*args, **kwargs):
|
|
if 'auth_password' in args[0]:
|
|
self.assertNotIn('scrubme', args[0])
|
|
fake_debug.matched = True
|
|
|
|
fake_debug.matched = False
|
|
|
|
with mock.patch.object(vmops.LOG, 'debug',
|
|
side_effect=fake_debug) as debug_mock:
|
|
vdis = self.vmops._get_vdis_for_instance(self.context,
|
|
self.instance, self.name_label, self.image,
|
|
image_type=4, block_device_info=bdi)
|
|
self.assertEqual(1, len(vdis))
|
|
get_uuid_mock.assert_called_once_with({"data": data})
|
|
# we don't care what the log message is, we just want to make sure
|
|
# our stub method is called which asserts the password is scrubbed
|
|
self.assertTrue(debug_mock.called)
|
|
self.assertTrue(fake_debug.matched)
|
|
|
|
|
|
class AttachInterfaceTestCase(VMOpsTestBase):
|
|
"""Test VIF hot plug/unplug"""
|
|
def setUp(self):
|
|
super(AttachInterfaceTestCase, self).setUp()
|
|
self.vmops.vif_driver = mock.Mock()
|
|
self.fake_vif = {'id': '12345'}
|
|
self.fake_instance = mock.Mock()
|
|
self.fake_instance.uuid = '6478'
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
def test_attach_interface(self, mock_get_vm_opaque_ref):
|
|
mock_get_vm_opaque_ref.return_value = 'fake_vm_ref'
|
|
with mock.patch.object(self._session.VM, 'get_allowed_VIF_devices')\
|
|
as fake_devices:
|
|
fake_devices.return_value = [2, 3, 4]
|
|
self.vmops.attach_interface(self.fake_instance, self.fake_vif)
|
|
fake_devices.assert_called_once_with('fake_vm_ref')
|
|
mock_get_vm_opaque_ref.assert_called_once_with(self.fake_instance)
|
|
self.vmops.vif_driver.plug.assert_called_once_with(
|
|
self.fake_instance, self.fake_vif, vm_ref='fake_vm_ref',
|
|
device=2)
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
def test_attach_interface_no_devices(self, mock_get_vm_opaque_ref):
|
|
mock_get_vm_opaque_ref.return_value = 'fake_vm_ref'
|
|
with mock.patch.object(self._session.VM, 'get_allowed_VIF_devices')\
|
|
as fake_devices:
|
|
fake_devices.return_value = []
|
|
self.assertRaises(exception.InterfaceAttachFailed,
|
|
self.vmops.attach_interface,
|
|
self.fake_instance, self.fake_vif)
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
def test_attach_interface_plug_failed(self, mock_get_vm_opaque_ref):
|
|
mock_get_vm_opaque_ref.return_value = 'fake_vm_ref'
|
|
with mock.patch.object(self._session.VM, 'get_allowed_VIF_devices')\
|
|
as fake_devices:
|
|
fake_devices.return_value = [2, 3, 4]
|
|
self.vmops.vif_driver.plug.side_effect =\
|
|
exception.VirtualInterfacePlugException('Failed to plug VIF')
|
|
self.assertRaises(exception.VirtualInterfacePlugException,
|
|
self.vmops.attach_interface,
|
|
self.fake_instance, self.fake_vif)
|
|
self.vmops.vif_driver.plug.assert_called_once_with(
|
|
self.fake_instance, self.fake_vif, vm_ref='fake_vm_ref',
|
|
device=2)
|
|
self.vmops.vif_driver.unplug.assert_called_once_with(
|
|
self.fake_instance, self.fake_vif, 'fake_vm_ref')
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
def test_attach_interface_reraise_exception(self, mock_get_vm_opaque_ref):
|
|
mock_get_vm_opaque_ref.return_value = 'fake_vm_ref'
|
|
with mock.patch.object(self._session.VM, 'get_allowed_VIF_devices')\
|
|
as fake_devices:
|
|
fake_devices.return_value = [2, 3, 4]
|
|
self.vmops.vif_driver.plug.side_effect =\
|
|
exception.VirtualInterfacePlugException('Failed to plug VIF')
|
|
self.vmops.vif_driver.unplug.side_effect =\
|
|
exception.VirtualInterfaceUnplugException(
|
|
'Failed to unplug VIF')
|
|
ex = self.assertRaises(exception.VirtualInterfacePlugException,
|
|
self.vmops.attach_interface,
|
|
self.fake_instance, self.fake_vif)
|
|
self.assertEqual('Failed to plug VIF', six.text_type(ex))
|
|
self.vmops.vif_driver.plug.assert_called_once_with(
|
|
self.fake_instance, self.fake_vif, vm_ref='fake_vm_ref',
|
|
device=2)
|
|
self.vmops.vif_driver.unplug.assert_called_once_with(
|
|
self.fake_instance, self.fake_vif, 'fake_vm_ref')
|
|
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
def test_detach_interface(self, mock_get_vm_opaque_ref):
|
|
mock_get_vm_opaque_ref.return_value = 'fake_vm_ref'
|
|
self.vmops.detach_interface(self.fake_instance, self.fake_vif)
|
|
mock_get_vm_opaque_ref.assert_called_once_with(self.fake_instance)
|
|
self.vmops.vif_driver.unplug.assert_called_once_with(
|
|
self.fake_instance, self.fake_vif, 'fake_vm_ref')
|
|
|
|
@mock.patch('nova.virt.xenapi.vmops.LOG.exception')
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref')
|
|
def test_detach_interface_exception(self, mock_get_vm_opaque_ref,
|
|
mock_log_exception):
|
|
mock_get_vm_opaque_ref.return_value = 'fake_vm_ref'
|
|
self.vmops.vif_driver.unplug.side_effect =\
|
|
exception.VirtualInterfaceUnplugException('Failed to unplug VIF')
|
|
|
|
self.assertRaises(exception.VirtualInterfaceUnplugException,
|
|
self.vmops.detach_interface,
|
|
self.fake_instance, self.fake_vif)
|
|
mock_log_exception.assert_called()
|
|
|
|
@mock.patch('nova.virt.xenapi.vmops.LOG.exception',
|
|
new_callable=mock.NonCallableMock)
|
|
@mock.patch.object(vmops.VMOps, '_get_vm_opaque_ref',
|
|
side_effect=exception.InstanceNotFound(
|
|
instance_id='fake_vm_ref'))
|
|
def test_detach_interface_instance_not_found(
|
|
self, mock_get_vm_opaque_ref, mock_log_exception):
|
|
self.assertRaises(exception.InstanceNotFound,
|
|
self.vmops.detach_interface,
|
|
self.fake_instance, self.fake_vif)
|