424 lines
16 KiB
Python
424 lines
16 KiB
Python
# Copyright (c) 2013 Rackspace Hosting
|
|
#
|
|
# 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 math
|
|
|
|
import mock
|
|
import os_resource_classes as orc
|
|
from oslo_utils.fixture import uuidsentinel as uuids
|
|
from oslo_utils import units
|
|
|
|
from nova import exception
|
|
from nova.objects import fields as obj_fields
|
|
from nova.tests.unit.virt.xenapi import stubs
|
|
from nova.virt import driver
|
|
from nova.virt import fake
|
|
from nova.virt import xenapi
|
|
from nova.virt.xenapi import driver as xenapi_driver
|
|
from nova.virt.xenapi import host
|
|
|
|
|
|
class XenAPIDriverTestCase(stubs.XenAPITestBaseNoDB):
|
|
"""Unit tests for Driver operations."""
|
|
|
|
def _get_driver(self):
|
|
stubs.stubout_session(self, stubs.FakeSessionForVMTests)
|
|
self.flags(connection_url='http://localhost',
|
|
connection_password='test_pass', group='xenserver')
|
|
return xenapi.XenAPIDriver(fake.FakeVirtAPI(), False)
|
|
|
|
def host_stats(self, refresh=True):
|
|
return {'host_memory_total': 3 * units.Mi,
|
|
'host_memory_free_computed': 2 * units.Mi,
|
|
'disk_total': 5 * units.Gi,
|
|
'disk_used': 2 * units.Gi,
|
|
'disk_allocated': 4 * units.Gi,
|
|
'host_hostname': 'somename',
|
|
'supported_instances': obj_fields.Architecture.X86_64,
|
|
'host_cpu_info': {'cpu_count': 50},
|
|
'cpu_model': {
|
|
'vendor': 'GenuineIntel',
|
|
'model': 'Intel(R) Xeon(R) CPU X3430 @ 2.40GHz',
|
|
'topology': {
|
|
'sockets': 1,
|
|
'cores': 4,
|
|
'threads': 1,
|
|
},
|
|
'features': [
|
|
'fpu', 'de', 'tsc', 'msr', 'pae', 'mce',
|
|
'cx8', 'apic', 'sep', 'mtrr', 'mca',
|
|
'cmov', 'pat', 'clflush', 'acpi', 'mmx',
|
|
'fxsr', 'sse', 'sse2', 'ss', 'ht',
|
|
'nx', 'constant_tsc', 'nonstop_tsc',
|
|
'aperfmperf', 'pni', 'vmx', 'est', 'ssse3',
|
|
'sse4_1', 'sse4_2', 'popcnt', 'hypervisor',
|
|
'ida', 'tpr_shadow', 'vnmi', 'flexpriority',
|
|
'ept', 'vpid',
|
|
],
|
|
},
|
|
'vcpus_used': 10,
|
|
'pci_passthrough_devices': '',
|
|
'host_other-config': {'iscsi_iqn': 'someiqn'},
|
|
'vgpu_stats': {
|
|
'c8328467-badf-43d8-8e28-0e096b0f88b1':
|
|
{'uuid': '6444c6ee-3a49-42f5-bebb-606b52175e67',
|
|
'type_name': 'Intel GVT-g',
|
|
'max_heads': 1,
|
|
'total': 7,
|
|
'remaining': 7,
|
|
},
|
|
}}
|
|
|
|
def test_available_resource(self):
|
|
driver = self._get_driver()
|
|
driver._session.product_version = (6, 8, 2)
|
|
|
|
with mock.patch.object(driver.host_state, 'get_host_stats',
|
|
side_effect=self.host_stats) as mock_get:
|
|
|
|
resources = driver.get_available_resource(None)
|
|
self.assertEqual(6008002, resources['hypervisor_version'])
|
|
self.assertEqual(50, resources['vcpus'])
|
|
self.assertEqual(3, resources['memory_mb'])
|
|
self.assertEqual(5, resources['local_gb'])
|
|
self.assertEqual(10, resources['vcpus_used'])
|
|
self.assertEqual(3 - 2, resources['memory_mb_used'])
|
|
self.assertEqual(2, resources['local_gb_used'])
|
|
self.assertEqual('XenServer', resources['hypervisor_type'])
|
|
self.assertEqual('somename', resources['hypervisor_hostname'])
|
|
self.assertEqual(1, resources['disk_available_least'])
|
|
mock_get.assert_called_once_with(refresh=True)
|
|
|
|
def test_overhead(self):
|
|
driver = self._get_driver()
|
|
instance = {'memory_mb': 30720, 'vcpus': 4}
|
|
|
|
# expected memory overhead per:
|
|
# https://wiki.openstack.org/wiki/XenServer/Overhead
|
|
expected = ((instance['memory_mb'] * xenapi_driver.OVERHEAD_PER_MB) +
|
|
(instance['vcpus'] * xenapi_driver.OVERHEAD_PER_VCPU) +
|
|
xenapi_driver.OVERHEAD_BASE)
|
|
expected = math.ceil(expected)
|
|
overhead = driver.estimate_instance_overhead(instance)
|
|
self.assertEqual(expected, overhead['memory_mb'])
|
|
|
|
def test_set_bootable(self):
|
|
driver = self._get_driver()
|
|
|
|
with mock.patch.object(driver._vmops,
|
|
'set_bootable') as mock_set_bootable:
|
|
driver.set_bootable('inst', True)
|
|
mock_set_bootable.assert_called_once_with('inst', True)
|
|
|
|
def test_post_interrupted_snapshot_cleanup(self):
|
|
driver = self._get_driver()
|
|
fake_vmops_cleanup = mock.Mock()
|
|
driver._vmops.post_interrupted_snapshot_cleanup = fake_vmops_cleanup
|
|
|
|
driver.post_interrupted_snapshot_cleanup("context", "instance")
|
|
|
|
fake_vmops_cleanup.assert_called_once_with("context", "instance")
|
|
|
|
def test_public_api_signatures(self):
|
|
inst = self._get_driver()
|
|
self.assertPublicAPISignatures(driver.ComputeDriver(None), inst)
|
|
|
|
def test_get_volume_connector(self):
|
|
ip = '123.123.123.123'
|
|
driver = self._get_driver()
|
|
self.flags(connection_url='http://%s' % ip,
|
|
connection_password='test_pass', group='xenserver')
|
|
with mock.patch.object(driver.host_state, 'get_host_stats',
|
|
side_effect=self.host_stats) as mock_get:
|
|
|
|
connector = driver.get_volume_connector({'uuid': 'fake'})
|
|
self.assertIn('ip', connector)
|
|
self.assertEqual(connector['ip'], ip)
|
|
self.assertIn('initiator', connector)
|
|
self.assertEqual(connector['initiator'], 'someiqn')
|
|
mock_get.assert_called_once_with(refresh=True)
|
|
|
|
def test_get_block_storage_ip(self):
|
|
my_ip = '123.123.123.123'
|
|
connection_ip = '124.124.124.124'
|
|
driver = self._get_driver()
|
|
self.flags(connection_url='http://%s' % connection_ip,
|
|
group='xenserver')
|
|
self.flags(my_ip=my_ip, my_block_storage_ip=my_ip)
|
|
|
|
ip = driver._get_block_storage_ip()
|
|
self.assertEqual(connection_ip, ip)
|
|
|
|
def test_get_block_storage_ip_conf(self):
|
|
driver = self._get_driver()
|
|
my_ip = '123.123.123.123'
|
|
my_block_storage_ip = '124.124.124.124'
|
|
self.flags(my_ip=my_ip, my_block_storage_ip=my_block_storage_ip)
|
|
|
|
ip = driver._get_block_storage_ip()
|
|
self.assertEqual(my_block_storage_ip, ip)
|
|
|
|
@mock.patch.object(xenapi_driver, 'invalid_option')
|
|
@mock.patch.object(xenapi_driver.vm_utils, 'ensure_correct_host')
|
|
def test_invalid_options(self, mock_ensure, mock_invalid):
|
|
driver = self._get_driver()
|
|
self.flags(independent_compute=True, group='xenserver')
|
|
self.flags(check_host=True, group='xenserver')
|
|
self.flags(flat_injected=True)
|
|
self.flags(default_ephemeral_format='vfat')
|
|
|
|
driver.init_host('host')
|
|
|
|
expected_calls = [
|
|
mock.call('CONF.xenserver.check_host', False),
|
|
mock.call('CONF.flat_injected', False),
|
|
mock.call('CONF.default_ephemeral_format', 'ext3')]
|
|
mock_invalid.assert_has_calls(expected_calls)
|
|
|
|
@mock.patch.object(xenapi_driver.vm_utils, 'cleanup_attached_vdis')
|
|
@mock.patch.object(xenapi_driver.vm_utils, 'ensure_correct_host')
|
|
def test_independent_compute_no_vdi_cleanup(self, mock_ensure,
|
|
mock_cleanup):
|
|
driver = self._get_driver()
|
|
self.flags(independent_compute=True, group='xenserver')
|
|
self.flags(check_host=False, group='xenserver')
|
|
self.flags(flat_injected=False)
|
|
|
|
driver.init_host('host')
|
|
|
|
self.assertFalse(mock_cleanup.called)
|
|
self.assertFalse(mock_ensure.called)
|
|
|
|
@mock.patch.object(xenapi_driver.vm_utils, 'cleanup_attached_vdis')
|
|
@mock.patch.object(xenapi_driver.vm_utils, 'ensure_correct_host')
|
|
def test_dependent_compute_vdi_cleanup(self, mock_ensure, mock_cleanup):
|
|
driver = self._get_driver()
|
|
self.assertFalse(mock_cleanup.called)
|
|
self.flags(independent_compute=False, group='xenserver')
|
|
self.flags(check_host=True, group='xenserver')
|
|
|
|
driver.init_host('host')
|
|
|
|
self.assertTrue(mock_cleanup.called)
|
|
self.assertTrue(mock_ensure.called)
|
|
|
|
@mock.patch.object(xenapi_driver.vmops.VMOps, 'attach_interface')
|
|
def test_attach_interface(self, mock_attach_interface):
|
|
driver = self._get_driver()
|
|
driver.attach_interface('fake_context', 'fake_instance',
|
|
'fake_image_meta', 'fake_vif')
|
|
mock_attach_interface.assert_called_once_with('fake_instance',
|
|
'fake_vif')
|
|
|
|
@mock.patch.object(xenapi_driver.vmops.VMOps, 'detach_interface')
|
|
def test_detach_interface(self, mock_detach_interface):
|
|
driver = self._get_driver()
|
|
driver.detach_interface('fake_context', 'fake_instance', 'fake_vif')
|
|
mock_detach_interface.assert_called_once_with('fake_instance',
|
|
'fake_vif')
|
|
|
|
@mock.patch.object(xenapi_driver.vmops.VMOps,
|
|
'post_live_migration_at_source')
|
|
def test_post_live_migration_at_source(self, mock_post_live_migration):
|
|
driver = self._get_driver()
|
|
driver.post_live_migration_at_source('fake_context', 'fake_instance',
|
|
'fake_network_info')
|
|
mock_post_live_migration.assert_called_once_with(
|
|
'fake_context', 'fake_instance', 'fake_network_info')
|
|
|
|
@mock.patch.object(xenapi_driver.vmops.VMOps,
|
|
'rollback_live_migration_at_destination')
|
|
def test_rollback_live_migration_at_destination(self, mock_rollback):
|
|
driver = self._get_driver()
|
|
driver.rollback_live_migration_at_destination(
|
|
'fake_context', 'fake_instance', 'fake_network_info',
|
|
'fake_block_device')
|
|
mock_rollback.assert_called_once_with('fake_instance',
|
|
'fake_network_info',
|
|
'fake_block_device')
|
|
|
|
@mock.patch.object(host.HostState, 'get_host_stats')
|
|
def test_get_inventory(self, mock_get_stats):
|
|
expected_inv = {
|
|
orc.VCPU: {
|
|
'total': 50,
|
|
'min_unit': 1,
|
|
'max_unit': 50,
|
|
'step_size': 1,
|
|
},
|
|
orc.MEMORY_MB: {
|
|
'total': 3,
|
|
'min_unit': 1,
|
|
'max_unit': 3,
|
|
'step_size': 1,
|
|
},
|
|
orc.DISK_GB: {
|
|
'total': 5,
|
|
'min_unit': 1,
|
|
'max_unit': 5,
|
|
'step_size': 1,
|
|
},
|
|
orc.VGPU: {
|
|
'total': 7,
|
|
'min_unit': 1,
|
|
'max_unit': 1,
|
|
'step_size': 1,
|
|
},
|
|
}
|
|
|
|
mock_get_stats.side_effect = self.host_stats
|
|
drv = self._get_driver()
|
|
inv = drv.get_inventory(mock.sentinel.nodename)
|
|
|
|
mock_get_stats.assert_called_once_with(refresh=True)
|
|
self.assertEqual(expected_inv, inv)
|
|
|
|
@mock.patch.object(host.HostState, 'get_host_stats')
|
|
def test_get_inventory_no_vgpu(self, mock_get_stats):
|
|
# Test when there are no vGPU resources in the inventory.
|
|
host_stats = self.host_stats()
|
|
host_stats.update(vgpu_stats={})
|
|
mock_get_stats.return_value = host_stats
|
|
|
|
drv = self._get_driver()
|
|
inv = drv.get_inventory(mock.sentinel.nodename)
|
|
|
|
# check if the inventory data does NOT contain VGPU.
|
|
self.assertNotIn(orc.VGPU, inv)
|
|
|
|
def test_get_vgpu_total_single_grp(self):
|
|
# Test when only one group included in the host_stats.
|
|
vgpu_stats = {
|
|
'grp_uuid_1': {
|
|
'total': 7
|
|
}
|
|
}
|
|
|
|
drv = self._get_driver()
|
|
vgpu_total = drv._get_vgpu_total(vgpu_stats)
|
|
|
|
self.assertEqual(7, vgpu_total)
|
|
|
|
def test_get_vgpu_total_multiple_grps(self):
|
|
# Test when multiple groups included in the host_stats.
|
|
vgpu_stats = {
|
|
'grp_uuid_1': {
|
|
'total': 7
|
|
},
|
|
'grp_uuid_2': {
|
|
'total': 4
|
|
}
|
|
}
|
|
|
|
drv = self._get_driver()
|
|
vgpu_total = drv._get_vgpu_total(vgpu_stats)
|
|
|
|
self.assertEqual(11, vgpu_total)
|
|
|
|
def test_get_vgpu_info_no_vgpu_alloc(self):
|
|
# no vgpu in allocation.
|
|
alloc = {
|
|
'rp1': {
|
|
'resources': {
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 512,
|
|
'DISK_GB': 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
drv = self._get_driver()
|
|
vgpu_info = drv._get_vgpu_info(alloc)
|
|
|
|
self.assertIsNone(vgpu_info)
|
|
|
|
@mock.patch.object(host.HostState, 'get_host_stats')
|
|
def test_get_vgpu_info_has_vgpu_alloc(self, mock_get_stats):
|
|
# Have vgpu in allocation.
|
|
alloc = {
|
|
'rp1': {
|
|
'resources': {
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 512,
|
|
'DISK_GB': 1,
|
|
'VGPU': 1,
|
|
}
|
|
}
|
|
}
|
|
# The following fake data assumes there are two GPU
|
|
# groups both of which supply the same type of vGPUs.
|
|
# If the 1st GPU group has no remaining available vGPUs;
|
|
# the 2nd GPU group still has remaining available vGPUs.
|
|
# it should return the uuid from the 2nd GPU group.
|
|
vgpu_stats = {
|
|
uuids.gpu_group_1: {
|
|
'uuid': uuids.vgpu_type,
|
|
'type_name': 'GRID K180Q',
|
|
'max_heads': 4,
|
|
'total': 2,
|
|
'remaining': 0,
|
|
},
|
|
uuids.gpu_group_2: {
|
|
'uuid': uuids.vgpu_type,
|
|
'type_name': 'GRID K180Q',
|
|
'max_heads': 4,
|
|
'total': 2,
|
|
'remaining': 2,
|
|
},
|
|
}
|
|
|
|
host_stats = self.host_stats()
|
|
host_stats.update(vgpu_stats=vgpu_stats)
|
|
mock_get_stats.return_value = host_stats
|
|
|
|
drv = self._get_driver()
|
|
vgpu_info = drv._get_vgpu_info(alloc)
|
|
|
|
expected_info = {'gpu_grp_uuid': uuids.gpu_group_2,
|
|
'vgpu_type_uuid': uuids.vgpu_type}
|
|
self.assertEqual(expected_info, vgpu_info)
|
|
|
|
@mock.patch.object(host.HostState, 'get_host_stats')
|
|
def test_get_vgpu_info_has_vgpu_alloc_except(self, mock_get_stats):
|
|
# Allocated vGPU but got exception due to no remaining vGPU.
|
|
alloc = {
|
|
'rp1': {
|
|
'resources': {
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 512,
|
|
'DISK_GB': 1,
|
|
'VGPU': 1,
|
|
}
|
|
}
|
|
}
|
|
vgpu_stats = {
|
|
uuids.gpu_group: {
|
|
'uuid': uuids.vgpu_type,
|
|
'type_name': 'Intel GVT-g',
|
|
'max_heads': 1,
|
|
'total': 7,
|
|
'remaining': 0,
|
|
},
|
|
}
|
|
|
|
host_stats = self.host_stats()
|
|
host_stats.update(vgpu_stats=vgpu_stats)
|
|
mock_get_stats.return_value = host_stats
|
|
|
|
drv = self._get_driver()
|
|
self.assertRaises(exception.ComputeResourcesUnavailable,
|
|
drv._get_vgpu_info,
|
|
alloc)
|