1975 lines
77 KiB
Python
1975 lines
77 KiB
Python
# Copyright 2010 OpenStack Foundation
|
|
# Copyright 2012 University Of Minho
|
|
# Copyright 2014 Red Hat, Inc
|
|
#
|
|
# 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 os
|
|
|
|
import eventlet
|
|
from eventlet import greenthread
|
|
from eventlet import tpool
|
|
import mock
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils.fixture import uuidsentinel as uuids
|
|
from oslo_utils import uuidutils
|
|
import testtools
|
|
|
|
from nova.compute import vm_states
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.objects import fields as obj_fields
|
|
from nova.pci import utils as pci_utils
|
|
from nova import test
|
|
from nova.tests import fixtures as nova_fixtures
|
|
from nova.tests.fixtures import libvirt as fakelibvirt
|
|
from nova.tests.fixtures import libvirt_data as fake_libvirt_data
|
|
from nova.virt import event
|
|
from nova.virt.libvirt import config as vconfig
|
|
from nova.virt.libvirt import event as libvirtevent
|
|
from nova.virt.libvirt import guest as libvirt_guest
|
|
from nova.virt.libvirt import host
|
|
|
|
|
|
class StringMatcher(object):
|
|
def __eq__(self, other):
|
|
return isinstance(other, str)
|
|
|
|
|
|
class FakeVirtDomain(object):
|
|
|
|
def __init__(self, id=-1, name=None):
|
|
self._id = id
|
|
self._name = name
|
|
self._uuid = uuidutils.generate_uuid()
|
|
|
|
def name(self):
|
|
return self._name
|
|
|
|
def ID(self):
|
|
return self._id
|
|
|
|
def UUIDString(self):
|
|
return self._uuid
|
|
|
|
|
|
class HostTestCase(test.NoDBTestCase):
|
|
|
|
def setUp(self):
|
|
super(HostTestCase, self).setUp()
|
|
|
|
self.useFixture(nova_fixtures.LibvirtFixture())
|
|
self.host = host.Host("qemu:///system")
|
|
|
|
@mock.patch("nova.virt.libvirt.host.Host._init_events")
|
|
def test_repeat_initialization(self, mock_init_events):
|
|
for i in range(3):
|
|
self.host.initialize()
|
|
mock_init_events.assert_called_once_with()
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "registerCloseCallback")
|
|
def test_close_callback(self, mock_close):
|
|
self.close_callback = None
|
|
|
|
def set_close_callback(cb, opaque):
|
|
self.close_callback = cb
|
|
|
|
mock_close.side_effect = set_close_callback
|
|
# verify that the driver registers for the close callback
|
|
self.host.get_connection()
|
|
self.assertTrue(self.close_callback)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "getLibVersion")
|
|
def test_broken_connection(self, mock_ver):
|
|
for (error, domain) in (
|
|
(fakelibvirt.VIR_ERR_SYSTEM_ERROR,
|
|
fakelibvirt.VIR_FROM_REMOTE),
|
|
(fakelibvirt.VIR_ERR_SYSTEM_ERROR,
|
|
fakelibvirt.VIR_FROM_RPC),
|
|
(fakelibvirt.VIR_ERR_INTERNAL_ERROR,
|
|
fakelibvirt.VIR_FROM_RPC)):
|
|
|
|
conn = self.host._connect("qemu:///system", False)
|
|
mock_ver.side_effect = fakelibvirt.make_libvirtError(
|
|
fakelibvirt.libvirtError,
|
|
"Connection broken",
|
|
error_code=error,
|
|
error_domain=domain)
|
|
self.assertFalse(self.host._test_connection(conn))
|
|
|
|
@mock.patch.object(host, 'LOG')
|
|
def test_connect_auth_cb_exception(self, log_mock):
|
|
creds = dict(authname='nova', password='verybadpass')
|
|
self.assertRaises(exception.NovaException,
|
|
self.host._connect_auth_cb, creds, False)
|
|
self.assertEqual(0, len(log_mock.method_calls),
|
|
'LOG should not be used in _connect_auth_cb.')
|
|
|
|
@mock.patch.object(greenthread, 'spawn_after')
|
|
def test_event_dispatch(self, mock_spawn_after):
|
|
# Validate that the libvirt self-pipe for forwarding
|
|
# events between threads is working sanely
|
|
def handler(event):
|
|
got_events.append(event)
|
|
|
|
hostimpl = host.Host("qemu:///system",
|
|
lifecycle_event_handler=handler)
|
|
got_events = []
|
|
|
|
hostimpl._init_events_pipe()
|
|
|
|
event1 = event.LifecycleEvent(
|
|
"cef19ce0-0ca2-11df-855d-b19fbce37686",
|
|
event.EVENT_LIFECYCLE_STARTED)
|
|
event2 = event.LifecycleEvent(
|
|
"cef19ce0-0ca2-11df-855d-b19fbce37686",
|
|
event.EVENT_LIFECYCLE_PAUSED)
|
|
hostimpl._queue_event(event1)
|
|
hostimpl._queue_event(event2)
|
|
hostimpl._dispatch_events()
|
|
|
|
want_events = [event1, event2]
|
|
self.assertEqual(want_events, got_events)
|
|
|
|
event3 = event.LifecycleEvent(
|
|
"cef19ce0-0ca2-11df-855d-b19fbce37686",
|
|
event.EVENT_LIFECYCLE_RESUMED)
|
|
event4 = event.LifecycleEvent(
|
|
"cef19ce0-0ca2-11df-855d-b19fbce37686",
|
|
event.EVENT_LIFECYCLE_STOPPED)
|
|
|
|
hostimpl._queue_event(event3)
|
|
hostimpl._queue_event(event4)
|
|
hostimpl._dispatch_events()
|
|
|
|
want_events = [event1, event2, event3]
|
|
self.assertEqual(want_events, got_events)
|
|
|
|
# STOPPED is delayed so it's handled separately
|
|
mock_spawn_after.assert_called_once_with(
|
|
hostimpl._lifecycle_delay, hostimpl._event_emit, event4)
|
|
|
|
def test_event_lifecycle(self):
|
|
got_events = []
|
|
|
|
# Validate that libvirt events are correctly translated
|
|
# to Nova events
|
|
def spawn_after(seconds, func, *args, **kwargs):
|
|
got_events.append(args[0])
|
|
return mock.Mock(spec=greenthread.GreenThread)
|
|
|
|
greenthread.spawn_after = mock.Mock(side_effect=spawn_after)
|
|
hostimpl = host.Host("qemu:///system",
|
|
lifecycle_event_handler=lambda e: None)
|
|
conn = hostimpl.get_connection()
|
|
|
|
hostimpl._init_events_pipe()
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<devices>
|
|
<disk type='file'>
|
|
<source file='filename'/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
dom = fakelibvirt.Domain(conn,
|
|
fake_dom_xml,
|
|
False)
|
|
|
|
hostimpl._event_lifecycle_callback(
|
|
conn, dom, fakelibvirt.VIR_DOMAIN_EVENT_STOPPED, 0, hostimpl)
|
|
hostimpl._dispatch_events()
|
|
self.assertEqual(len(got_events), 1)
|
|
self.assertIsInstance(got_events[0], event.LifecycleEvent)
|
|
self.assertEqual(got_events[0].uuid,
|
|
"cef19ce0-0ca2-11df-855d-b19fbce37686")
|
|
self.assertEqual(got_events[0].transition,
|
|
event.EVENT_LIFECYCLE_STOPPED)
|
|
|
|
def test_event_lifecycle_callback_suspended_postcopy(self):
|
|
"""Tests the suspended lifecycle event with libvirt with post-copy"""
|
|
hostimpl = mock.MagicMock()
|
|
conn = mock.MagicMock()
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
</domain>
|
|
"""
|
|
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
|
host.Host._event_lifecycle_callback(
|
|
conn, dom, fakelibvirt.VIR_DOMAIN_EVENT_SUSPENDED,
|
|
detail=fakelibvirt.VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY,
|
|
opaque=hostimpl)
|
|
expected_event = hostimpl._queue_event.call_args[0][0]
|
|
self.assertEqual(event.EVENT_LIFECYCLE_POSTCOPY_STARTED,
|
|
expected_event.transition)
|
|
|
|
@mock.patch('nova.virt.libvirt.guest.Guest.get_job_info')
|
|
def test_event_lifecycle_callback_suspended_migrated(self, get_job_info):
|
|
"""Tests the suspended lifecycle event with libvirt with migrated"""
|
|
hostimpl = mock.MagicMock()
|
|
conn = mock.MagicMock()
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
</domain>
|
|
"""
|
|
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
|
jobinfo = libvirt_guest.JobInfo(
|
|
type=fakelibvirt.VIR_DOMAIN_JOB_COMPLETED)
|
|
get_job_info.return_value = jobinfo
|
|
host.Host._event_lifecycle_callback(
|
|
conn, dom, fakelibvirt.VIR_DOMAIN_EVENT_SUSPENDED,
|
|
detail=fakelibvirt.VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED,
|
|
opaque=hostimpl)
|
|
expected_event = hostimpl._queue_event.call_args[0][0]
|
|
self.assertEqual(event.EVENT_LIFECYCLE_MIGRATION_COMPLETED,
|
|
expected_event.transition)
|
|
get_job_info.assert_called_once_with()
|
|
|
|
@mock.patch('nova.virt.libvirt.guest.Guest.get_job_info')
|
|
@mock.patch('nova.virt.libvirt.migration.find_job_type')
|
|
def test_event_lifecycle_callback_suspended_migrated_job_failed(
|
|
self, find_job_type, get_job_info):
|
|
"""Tests the suspended lifecycle event with libvirt with migrated"""
|
|
hostimpl = mock.MagicMock()
|
|
conn = mock.MagicMock()
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
</domain>
|
|
"""
|
|
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
|
jobinfo = libvirt_guest.JobInfo(type=fakelibvirt.VIR_DOMAIN_JOB_NONE)
|
|
get_job_info.return_value = jobinfo
|
|
# If the job type is VIR_DOMAIN_JOB_NONE we'll attempt to figure out
|
|
# the actual job status, so in this case we mock it to be a failure.
|
|
find_job_type.return_value = fakelibvirt.VIR_DOMAIN_JOB_FAILED
|
|
host.Host._event_lifecycle_callback(
|
|
conn, dom, fakelibvirt.VIR_DOMAIN_EVENT_SUSPENDED,
|
|
detail=fakelibvirt.VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED,
|
|
opaque=hostimpl)
|
|
expected_event = hostimpl._queue_event.call_args[0][0]
|
|
self.assertEqual(event.EVENT_LIFECYCLE_PAUSED,
|
|
expected_event.transition)
|
|
get_job_info.assert_called_once_with()
|
|
find_job_type.assert_called_once_with(
|
|
test.MatchType(libvirt_guest.Guest), instance=None,
|
|
logging_ok=False)
|
|
|
|
def test_event_emit_delayed_call_delayed(self):
|
|
ev = event.LifecycleEvent(
|
|
"cef19ce0-0ca2-11df-855d-b19fbce37686",
|
|
event.EVENT_LIFECYCLE_STOPPED)
|
|
spawn_after_mock = mock.Mock()
|
|
greenthread.spawn_after = spawn_after_mock
|
|
hostimpl = host.Host(
|
|
'qemu:///system', lifecycle_event_handler=lambda e: None)
|
|
hostimpl._event_emit_delayed(ev)
|
|
spawn_after_mock.assert_called_once_with(
|
|
15, hostimpl._event_emit, ev)
|
|
|
|
@mock.patch.object(greenthread, 'spawn_after')
|
|
def test_event_emit_delayed_call_delayed_pending(self, spawn_after_mock):
|
|
hostimpl = host.Host(
|
|
'qemu:///system', lifecycle_event_handler=lambda e: None)
|
|
uuid = "cef19ce0-0ca2-11df-855d-b19fbce37686"
|
|
gt_mock = mock.Mock()
|
|
hostimpl._events_delayed[uuid] = gt_mock
|
|
ev = event.LifecycleEvent(
|
|
uuid, event.EVENT_LIFECYCLE_STOPPED)
|
|
hostimpl._event_emit_delayed(ev)
|
|
gt_mock.cancel.assert_called_once_with()
|
|
self.assertTrue(spawn_after_mock.called)
|
|
|
|
def test_event_delayed_cleanup(self):
|
|
hostimpl = host.Host(
|
|
'qemu:///system', lifecycle_event_handler=lambda e: None)
|
|
uuid = "cef19ce0-0ca2-11df-855d-b19fbce37686"
|
|
ev = event.LifecycleEvent(
|
|
uuid, event.EVENT_LIFECYCLE_STARTED)
|
|
gt_mock = mock.Mock()
|
|
hostimpl._events_delayed[uuid] = gt_mock
|
|
hostimpl._event_emit_delayed(ev)
|
|
gt_mock.cancel.assert_called_once_with()
|
|
self.assertNotIn(uuid, hostimpl._events_delayed.keys())
|
|
|
|
def test_device_removed_event(self):
|
|
hostimpl = mock.MagicMock()
|
|
conn = mock.MagicMock()
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
</domain>
|
|
"""
|
|
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
|
host.Host._event_device_removed_callback(
|
|
conn, dom, dev='virtio-1', opaque=hostimpl)
|
|
expected_event = hostimpl._queue_event.call_args[0][0]
|
|
self.assertEqual(
|
|
libvirtevent.DeviceRemovedEvent, type(expected_event))
|
|
self.assertEqual(
|
|
'cef19ce0-0ca2-11df-855d-b19fbce37686', expected_event.uuid)
|
|
self.assertEqual('virtio-1', expected_event.dev)
|
|
|
|
def test_device_removal_failed(self):
|
|
hostimpl = mock.MagicMock()
|
|
conn = mock.MagicMock()
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
</domain>
|
|
"""
|
|
dom = fakelibvirt.Domain(conn, fake_dom_xml, running=True)
|
|
host.Host._event_device_removal_failed_callback(
|
|
conn, dom, dev='virtio-1', opaque=hostimpl)
|
|
expected_event = hostimpl._queue_event.call_args[0][0]
|
|
self.assertEqual(
|
|
libvirtevent.DeviceRemovalFailedEvent, type(expected_event))
|
|
self.assertEqual(
|
|
'cef19ce0-0ca2-11df-855d-b19fbce37686', expected_event.uuid)
|
|
self.assertEqual('virtio-1', expected_event.dev)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "domainEventRegisterAny")
|
|
@mock.patch.object(host.Host, "_connect")
|
|
def test_get_connection_serial(self, mock_conn, mock_event):
|
|
def get_conn_currency(host):
|
|
host.get_connection().getLibVersion()
|
|
|
|
def connect_with_block(*a, **k):
|
|
# enough to allow another connect to run
|
|
eventlet.sleep(0)
|
|
self.connect_calls += 1
|
|
return fakelibvirt.openAuth("qemu:///system",
|
|
[[], lambda: 1, None], 0)
|
|
|
|
def fake_register(*a, **k):
|
|
self.register_calls += 1
|
|
|
|
self.connect_calls = 0
|
|
self.register_calls = 0
|
|
|
|
mock_conn.side_effect = connect_with_block
|
|
mock_event.side_effect = fake_register
|
|
|
|
# call serially
|
|
get_conn_currency(self.host)
|
|
get_conn_currency(self.host)
|
|
self.assertEqual(self.connect_calls, 1)
|
|
self.assertEqual(self.register_calls, 3)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "domainEventRegisterAny")
|
|
@mock.patch.object(host.Host, "_connect")
|
|
def test_get_connection_concurrency(self, mock_conn, mock_event):
|
|
def get_conn_currency(host):
|
|
host.get_connection().getLibVersion()
|
|
|
|
def connect_with_block(*a, **k):
|
|
# enough to allow another connect to run
|
|
eventlet.sleep(0)
|
|
self.connect_calls += 1
|
|
return fakelibvirt.openAuth("qemu:///system",
|
|
[[], lambda: 1, None], 0)
|
|
|
|
def fake_register(*a, **k):
|
|
self.register_calls += 1
|
|
|
|
self.connect_calls = 0
|
|
self.register_calls = 0
|
|
|
|
mock_conn.side_effect = connect_with_block
|
|
mock_event.side_effect = fake_register
|
|
|
|
# call concurrently
|
|
thr1 = eventlet.spawn(get_conn_currency, self.host)
|
|
thr2 = eventlet.spawn(get_conn_currency, self.host)
|
|
|
|
# let threads run
|
|
eventlet.sleep(0)
|
|
|
|
thr1.wait()
|
|
thr2.wait()
|
|
self.assertEqual(self.connect_calls, 1)
|
|
self.assertEqual(self.register_calls, 3)
|
|
|
|
@mock.patch.object(host.Host, "_connect")
|
|
def test_conn_event(self, mock_conn):
|
|
handler = mock.MagicMock()
|
|
h = host.Host("qemu:///system", conn_event_handler=handler)
|
|
|
|
h.get_connection()
|
|
h._dispatch_conn_event()
|
|
|
|
handler.assert_called_once_with(True, None)
|
|
|
|
@mock.patch.object(host.Host, "_connect")
|
|
def test_conn_event_fail(self, mock_conn):
|
|
handler = mock.MagicMock()
|
|
h = host.Host("qemu:///system", conn_event_handler=handler)
|
|
mock_conn.side_effect = fakelibvirt.libvirtError('test')
|
|
|
|
self.assertRaises(exception.HypervisorUnavailable, h.get_connection)
|
|
h._dispatch_conn_event()
|
|
|
|
handler.assert_called_once_with(False, StringMatcher())
|
|
|
|
# Attempt to get a second connection, and assert that we don't add
|
|
# queue a second callback. Note that we can't call
|
|
# _dispatch_conn_event() and assert no additional call to the handler
|
|
# here as above. This is because we haven't added an event, so it would
|
|
# block. We mock the helper method which queues an event for callback
|
|
# instead.
|
|
with mock.patch.object(h, '_queue_conn_event_handler') as mock_queue:
|
|
self.assertRaises(exception.HypervisorUnavailable,
|
|
h.get_connection)
|
|
mock_queue.assert_not_called()
|
|
|
|
@mock.patch.object(host.Host, "_test_connection")
|
|
@mock.patch.object(host.Host, "_connect")
|
|
def test_conn_event_up_down(self, mock_conn, mock_test_conn):
|
|
handler = mock.MagicMock()
|
|
h = host.Host("qemu:///system", conn_event_handler=handler)
|
|
mock_conn.side_effect = (mock.MagicMock(),
|
|
fakelibvirt.libvirtError('test'))
|
|
mock_test_conn.return_value = False
|
|
|
|
h.get_connection()
|
|
self.assertRaises(exception.HypervisorUnavailable, h.get_connection)
|
|
h._dispatch_conn_event()
|
|
h._dispatch_conn_event()
|
|
|
|
handler.assert_has_calls([
|
|
mock.call(True, None),
|
|
mock.call(False, StringMatcher())
|
|
])
|
|
|
|
@mock.patch.object(host.Host, "_connect")
|
|
def test_conn_event_thread(self, mock_conn):
|
|
event = eventlet.event.Event()
|
|
h = host.Host("qemu:///system", conn_event_handler=event.send)
|
|
h.initialize()
|
|
|
|
h.get_connection()
|
|
event.wait()
|
|
# This test will timeout if it fails. Success is implicit in a
|
|
# timely return from wait(), indicating that the connection event
|
|
# handler was called.
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "getLibVersion")
|
|
@mock.patch.object(fakelibvirt.virConnect, "getVersion")
|
|
@mock.patch.object(fakelibvirt.virConnect, "getType")
|
|
def test_has_min_version(self, fake_hv_type, fake_hv_ver, fake_lv_ver):
|
|
fake_lv_ver.return_value = 1002003
|
|
fake_hv_ver.return_value = 4005006
|
|
fake_hv_type.return_value = 'xyz'
|
|
|
|
lv_ver = (1, 2, 3)
|
|
hv_ver = (4, 5, 6)
|
|
hv_type = 'xyz'
|
|
self.assertTrue(self.host.has_min_version(lv_ver, hv_ver, hv_type))
|
|
|
|
self.assertFalse(self.host.has_min_version(lv_ver, hv_ver, 'abc'))
|
|
self.assertFalse(self.host.has_min_version(lv_ver, (4, 5, 7), hv_type))
|
|
self.assertFalse(self.host.has_min_version((1, 3, 3), hv_ver, hv_type))
|
|
|
|
self.assertTrue(self.host.has_min_version(lv_ver, hv_ver, None))
|
|
self.assertTrue(self.host.has_min_version(lv_ver, None, hv_type))
|
|
self.assertTrue(self.host.has_min_version(None, hv_ver, hv_type))
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "getLibVersion")
|
|
@mock.patch.object(fakelibvirt.virConnect, "getVersion")
|
|
@mock.patch.object(fakelibvirt.virConnect, "getType")
|
|
def test_has_version(self, fake_hv_type, fake_hv_ver, fake_lv_ver):
|
|
fake_lv_ver.return_value = 1002003
|
|
fake_hv_ver.return_value = 4005006
|
|
fake_hv_type.return_value = 'xyz'
|
|
|
|
lv_ver = (1, 2, 3)
|
|
hv_ver = (4, 5, 6)
|
|
hv_type = 'xyz'
|
|
self.assertTrue(self.host.has_version(lv_ver, hv_ver, hv_type))
|
|
|
|
for lv_ver_ in [(1, 2, 2), (1, 2, 4)]:
|
|
self.assertFalse(self.host.has_version(lv_ver_, hv_ver, hv_type))
|
|
for hv_ver_ in [(4, 4, 6), (4, 6, 6)]:
|
|
self.assertFalse(self.host.has_version(lv_ver, hv_ver_, hv_type))
|
|
self.assertFalse(self.host.has_version(lv_ver, hv_ver, 'abc'))
|
|
|
|
self.assertTrue(self.host.has_version(lv_ver, hv_ver, None))
|
|
self.assertTrue(self.host.has_version(lv_ver, None, hv_type))
|
|
self.assertTrue(self.host.has_version(None, hv_ver, hv_type))
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "lookupByUUIDString")
|
|
def test_get_domain(self, fake_lookup):
|
|
uuid = uuidutils.generate_uuid()
|
|
dom = fakelibvirt.virDomain(self.host.get_connection(),
|
|
"<domain id='7'/>")
|
|
instance = objects.Instance(id="124", uuid=uuid)
|
|
fake_lookup.return_value = dom
|
|
|
|
self.assertEqual(dom, self.host._get_domain(instance))
|
|
fake_lookup.assert_called_once_with(uuid)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "lookupByUUIDString")
|
|
def test_get_domain_raises(self, fake_lookup):
|
|
instance = objects.Instance(uuid=uuids.instance,
|
|
vm_state=vm_states.ACTIVE)
|
|
fake_lookup.side_effect = fakelibvirt.make_libvirtError(
|
|
fakelibvirt.libvirtError,
|
|
'Domain not found: no domain with matching name',
|
|
error_code=fakelibvirt.VIR_ERR_NO_DOMAIN,
|
|
error_domain=fakelibvirt.VIR_FROM_QEMU)
|
|
|
|
with testtools.ExpectedException(exception.InstanceNotFound):
|
|
self.host._get_domain(instance)
|
|
|
|
fake_lookup.assert_called_once_with(uuids.instance)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "lookupByUUIDString")
|
|
def test_get_guest(self, fake_lookup):
|
|
uuid = uuidutils.generate_uuid()
|
|
dom = fakelibvirt.virDomain(self.host.get_connection(),
|
|
"<domain id='7'/>")
|
|
|
|
fake_lookup.return_value = dom
|
|
instance = objects.Instance(id="124", uuid=uuid)
|
|
|
|
guest = self.host.get_guest(instance)
|
|
self.assertEqual(dom, guest._domain)
|
|
self.assertIsInstance(guest, libvirt_guest.Guest)
|
|
|
|
fake_lookup.assert_called_once_with(uuid)
|
|
|
|
@mock.patch.object(fakelibvirt.Connection, "listAllDomains")
|
|
def test_list_instance_domains(self, mock_list_all):
|
|
vm1 = FakeVirtDomain(id=3, name="instance00000001")
|
|
vm2 = FakeVirtDomain(id=17, name="instance00000002")
|
|
vm3 = FakeVirtDomain(name="instance00000003")
|
|
vm4 = FakeVirtDomain(name="instance00000004")
|
|
|
|
def fake_list_all(flags):
|
|
vms = []
|
|
if flags & fakelibvirt.VIR_CONNECT_LIST_DOMAINS_ACTIVE:
|
|
vms.extend([vm1, vm2])
|
|
if flags & fakelibvirt.VIR_CONNECT_LIST_DOMAINS_INACTIVE:
|
|
vms.extend([vm3, vm4])
|
|
return vms
|
|
|
|
mock_list_all.side_effect = fake_list_all
|
|
|
|
doms = self.host.list_instance_domains()
|
|
|
|
mock_list_all.assert_called_once_with(
|
|
fakelibvirt.VIR_CONNECT_LIST_DOMAINS_ACTIVE)
|
|
mock_list_all.reset_mock()
|
|
|
|
self.assertEqual(len(doms), 2)
|
|
self.assertEqual(doms[0].name(), vm1.name())
|
|
self.assertEqual(doms[1].name(), vm2.name())
|
|
|
|
doms = self.host.list_instance_domains(only_running=False)
|
|
|
|
mock_list_all.assert_called_once_with(
|
|
fakelibvirt.VIR_CONNECT_LIST_DOMAINS_ACTIVE |
|
|
fakelibvirt.VIR_CONNECT_LIST_DOMAINS_INACTIVE)
|
|
mock_list_all.reset_mock()
|
|
|
|
self.assertEqual(len(doms), 4)
|
|
self.assertEqual(doms[0].name(), vm1.name())
|
|
self.assertEqual(doms[1].name(), vm2.name())
|
|
self.assertEqual(doms[2].name(), vm3.name())
|
|
self.assertEqual(doms[3].name(), vm4.name())
|
|
|
|
@mock.patch.object(host.Host, "list_instance_domains")
|
|
def test_list_guests(self, mock_list_domains):
|
|
dom0 = mock.Mock(spec=fakelibvirt.virDomain)
|
|
dom1 = mock.Mock(spec=fakelibvirt.virDomain)
|
|
mock_list_domains.return_value = [dom0, dom1]
|
|
result = self.host.list_guests(True)
|
|
mock_list_domains.assert_called_once_with(only_running=True)
|
|
self.assertEqual(dom0, result[0]._domain)
|
|
self.assertEqual(dom1, result[1]._domain)
|
|
|
|
def test_cpu_features_bug_1217630(self):
|
|
self.host.get_connection()
|
|
|
|
# Test old version of libvirt, it shouldn't see the `aes' feature
|
|
with mock.patch('nova.virt.libvirt.host.libvirt') as mock_libvirt:
|
|
del mock_libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES
|
|
caps = self.host.get_capabilities()
|
|
self.assertNotIn('aes', [x.name for x in caps.host.cpu.features])
|
|
|
|
# Cleanup the capabilities cache firstly
|
|
self.host._caps = None
|
|
|
|
# Test new version of libvirt, should find the `aes' feature
|
|
with mock.patch('nova.virt.libvirt.host.libvirt') as mock_libvirt:
|
|
mock_libvirt['VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES'] = 1
|
|
caps = self.host.get_capabilities()
|
|
self.assertIn('aes', [x.name for x in caps.host.cpu.features])
|
|
|
|
def test_cpu_features_are_not_duplicated(self):
|
|
self.host.get_connection()
|
|
|
|
# Test old version of libvirt. Should return single 'hypervisor'
|
|
with mock.patch('nova.virt.libvirt.host.libvirt') as mock_libvirt:
|
|
del mock_libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES
|
|
caps = self.host.get_capabilities()
|
|
cnt = [x.name for x in caps.host.cpu.features].count('xtpr')
|
|
self.assertEqual(1, cnt)
|
|
|
|
# Cleanup the capabilities cache firstly
|
|
self.host._caps = None
|
|
|
|
# Test new version of libvirt. Should still return single 'hypervisor'
|
|
with mock.patch('nova.virt.libvirt.host.libvirt') as mock_libvirt:
|
|
mock_libvirt['VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES'] = 1
|
|
caps = self.host.get_capabilities()
|
|
cnt = [x.name for x in caps.host.cpu.features].count('xtpr')
|
|
self.assertEqual(1, cnt)
|
|
|
|
def test_baseline_cpu_not_supported(self):
|
|
# Handle just the NO_SUPPORT error
|
|
not_supported_exc = fakelibvirt.make_libvirtError(
|
|
fakelibvirt.libvirtError,
|
|
'this function is not supported by the connection driver:'
|
|
' virConnectBaselineCPU',
|
|
error_code=fakelibvirt.VIR_ERR_NO_SUPPORT)
|
|
|
|
with mock.patch.object(fakelibvirt.virConnect, 'baselineCPU',
|
|
side_effect=not_supported_exc):
|
|
caps = self.host.get_capabilities()
|
|
self.assertEqual(vconfig.LibvirtConfigCaps, type(caps))
|
|
self.assertNotIn('aes', [x.name for x in caps.host.cpu.features])
|
|
|
|
# Clear cached result so we can test again...
|
|
self.host._caps = None
|
|
|
|
# Other errors should not be caught
|
|
other_exc = fakelibvirt.make_libvirtError(
|
|
fakelibvirt.libvirtError,
|
|
'other exc',
|
|
error_code=fakelibvirt.VIR_ERR_NO_DOMAIN)
|
|
|
|
with mock.patch.object(fakelibvirt.virConnect, 'baselineCPU',
|
|
side_effect=other_exc):
|
|
self.assertRaises(fakelibvirt.libvirtError,
|
|
self.host.get_capabilities)
|
|
|
|
def test_get_capabilities_no_host_cpu_model(self):
|
|
"""Tests that cpu features are not retrieved when the host cpu model
|
|
is not in the capabilities.
|
|
"""
|
|
fake_caps_xml = '''
|
|
<capabilities>
|
|
<host>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<vendor>Intel</vendor>
|
|
</cpu>
|
|
</host>
|
|
</capabilities>'''
|
|
with mock.patch.object(fakelibvirt.virConnect, 'getCapabilities',
|
|
return_value=fake_caps_xml):
|
|
caps = self.host.get_capabilities()
|
|
self.assertEqual(vconfig.LibvirtConfigCaps, type(caps))
|
|
self.assertIsNone(caps.host.cpu.model)
|
|
self.assertEqual(0, len(caps.host.cpu.features))
|
|
|
|
def test__get_machine_types(self):
|
|
expected = [
|
|
# NOTE(aspiers): in the real world, i686 would probably
|
|
# have q35 too, but our fixtures are manipulated to
|
|
# exclude it to allow more thorough testing the our
|
|
# canonical machine types logic is correct.
|
|
('i686', 'qemu', ['pc']),
|
|
('i686', 'kvm', ['pc']),
|
|
('x86_64', 'qemu', ['pc', 'q35']),
|
|
('x86_64', 'kvm', ['pc', 'q35']),
|
|
('armv7l', 'qemu', ['virt']),
|
|
# NOTE(aspiers): we're currently missing default machine
|
|
# types for the other architectures for which we have fake
|
|
# capabilities.
|
|
]
|
|
for arch, domain, expected_mach_types in expected:
|
|
guest_xml = fake_libvirt_data.CAPABILITIES_GUEST[arch]
|
|
guest = vconfig.LibvirtConfigCapsGuest()
|
|
guest.parse_str(guest_xml)
|
|
domain = guest.domains[domain]
|
|
self.assertEqual(set(expected_mach_types),
|
|
self.host._get_machine_types(arch, domain),
|
|
"for arch %s domain %s" %
|
|
(arch, domain.domtype))
|
|
|
|
def _test_get_domain_capabilities(self):
|
|
caps = self.host.get_domain_capabilities()
|
|
for arch, mtypes in caps.items():
|
|
for mtype, dom_cap in mtypes.items():
|
|
self.assertIsInstance(dom_cap, vconfig.LibvirtConfigDomainCaps)
|
|
# NOTE(sean-k-mooney): this should always be true since we are
|
|
# mapping from an arch and machine_type to a domain cap object
|
|
# for that pair. We use 'in' to allow libvirt to expand the
|
|
# unversioned alias such as 'pc' or 'q35' to its versioned
|
|
# form e.g. pc-i440fx-2.11
|
|
self.assertIn(mtype, dom_cap.machine_type)
|
|
self.assertIn(dom_cap.machine_type_alias, mtype)
|
|
|
|
# We assume we are testing with x86_64 in other parts of the code
|
|
# so we just assert it's in the test data and return it.
|
|
expected = [
|
|
('i686', ['pc', 'pc-i440fx-2.11']),
|
|
('x86_64', ['pc', 'pc-i440fx-2.11', 'q35', 'pc-q35-2.11']),
|
|
]
|
|
for arch, expected_mtypes in expected:
|
|
self.assertIn(arch, caps)
|
|
for mach_type in expected_mtypes:
|
|
self.assertIn(mach_type, caps[arch], "for arch %s" % arch)
|
|
|
|
return caps['x86_64']['pc']
|
|
|
|
def test_get_domain_capabilities(self):
|
|
caps = self._test_get_domain_capabilities()
|
|
self.assertEqual(vconfig.LibvirtConfigDomainCaps, type(caps))
|
|
# There is a <gic supported='no'/> feature in the fixture but
|
|
# we don't parse that because nothing currently cares about it.
|
|
self.assertEqual(0, len(caps.features))
|
|
|
|
def test_get_domain_capabilities_non_native_kvm(self):
|
|
# This test assumes that we are on a x86 host and the
|
|
# virt-type is set to kvm. In that case we would expect
|
|
# libvirt to raise an error if you try to get the domain
|
|
# capabilities for non-native archs specifying the kvm virt
|
|
# type.
|
|
archs = {
|
|
'sparc': 'SS-5',
|
|
'mips': 'malta',
|
|
'mipsel': 'malta',
|
|
'ppc': 'g3beige',
|
|
'armv7l': 'virt-2.11',
|
|
}
|
|
|
|
# Because we are mocking out the libvirt connection and
|
|
# supplying fake data, no exception will be raised, so we
|
|
# first store a reference to the original
|
|
# _get_domain_capabilities function
|
|
local__get_domain_caps = self.host._get_domain_capabilities
|
|
|
|
# We then define our own version that will raise for
|
|
# non-native archs and otherwise delegates to the private
|
|
# function.
|
|
def _get_domain_capabilities(**kwargs):
|
|
arch = kwargs['arch']
|
|
if arch not in archs:
|
|
return local__get_domain_caps(**kwargs)
|
|
else:
|
|
exc = fakelibvirt.make_libvirtError(
|
|
fakelibvirt.libvirtError,
|
|
"invalid argument: KVM is not supported by "
|
|
"'/usr/bin/qemu-system-%s' on this host" % arch,
|
|
error_code=fakelibvirt.VIR_ERR_INVALID_ARG)
|
|
raise exc
|
|
|
|
# Finally we patch to use our own version
|
|
with test.nested(
|
|
mock.patch.object(host.LOG, 'debug'),
|
|
mock.patch.object(self.host, "_get_domain_capabilities"),
|
|
) as (mock_log, mock_caps):
|
|
mock_caps.side_effect = _get_domain_capabilities
|
|
self.flags(virt_type='kvm', group='libvirt')
|
|
# and call self.host.get_domain_capabilities() directly as
|
|
# the exception should be caught internally
|
|
caps = self.host.get_domain_capabilities()
|
|
# We don't really care what mock_caps is called with,
|
|
# as we assert the behavior we expect below. However we
|
|
# can at least check for the expected debug messages.
|
|
mock_caps.assert_called()
|
|
warnings = []
|
|
for call in mock_log.mock_calls:
|
|
name, args, kwargs = call
|
|
if "Error from libvirt when retrieving domain capabilities" \
|
|
in args[0]:
|
|
warnings.append(call)
|
|
self.assertTrue(len(warnings) > 0)
|
|
|
|
# The resulting capabilities object should be non-empty
|
|
# as the x86 archs won't raise a libvirtError exception
|
|
self.assertTrue(len(caps) > 0)
|
|
# but all of the archs we mocked out should be skipped and
|
|
# not included in the result set
|
|
for arch in archs:
|
|
self.assertNotIn(arch, caps)
|
|
|
|
def test_get_domain_capabilities_other_archs(self):
|
|
# NOTE(aspiers): only architectures which are returned by
|
|
# fakelibvirt's getCapabilities() can be tested here, since
|
|
# Host.get_domain_capabilities() iterates over those
|
|
# architectures.
|
|
archs = {
|
|
'sparc': 'SS-5',
|
|
'mips': 'malta',
|
|
'mipsel': 'malta',
|
|
'ppc': 'g3beige',
|
|
'armv7l': 'virt-2.11',
|
|
}
|
|
|
|
caps = self.host.get_domain_capabilities()
|
|
|
|
for arch, mtype in archs.items():
|
|
self.assertIn(arch, caps)
|
|
self.assertNotIn('pc', caps[arch])
|
|
self.assertIn(mtype, caps[arch])
|
|
self.assertEqual(mtype, caps[arch][mtype].machine_type)
|
|
|
|
def test_get_domain_capabilities_with_versioned_machine_type(self):
|
|
caps = self.host.get_domain_capabilities()
|
|
|
|
# i686 supports both an unversioned pc alias and
|
|
# a versioned form.
|
|
i686 = caps['i686']
|
|
self.assertIn('pc', i686)
|
|
self.assertIn('pc-i440fx-2.11', i686)
|
|
# both the versioned and unversioned forms
|
|
# have the unversioned name available as a machine_type_alias
|
|
unversioned_caps = i686['pc']
|
|
self.assertEqual('pc', unversioned_caps.machine_type_alias)
|
|
versioned_caps = i686['pc-i440fx-2.11']
|
|
self.assertEqual('pc', versioned_caps.machine_type_alias)
|
|
# the unversioned_caps and versioned_caps
|
|
# are equal and are actually the same object.
|
|
self.assertEqual(unversioned_caps, versioned_caps)
|
|
self.assertIs(unversioned_caps, versioned_caps)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, '_domain_capability_features',
|
|
new='')
|
|
def test_get_domain_capabilities_no_features(self):
|
|
caps = self._test_get_domain_capabilities()
|
|
self.assertEqual(vconfig.LibvirtConfigDomainCaps, type(caps))
|
|
features = caps.features
|
|
self.assertEqual([], features)
|
|
|
|
def _test_get_domain_capabilities_sev(self, supported):
|
|
caps = self._test_get_domain_capabilities()
|
|
self.assertEqual(vconfig.LibvirtConfigDomainCaps, type(caps))
|
|
features = caps.features
|
|
self.assertEqual(1, len(features))
|
|
sev = features[0]
|
|
self.assertEqual(vconfig.LibvirtConfigDomainCapsFeatureSev, type(sev))
|
|
self.assertEqual(supported, sev.supported)
|
|
if supported:
|
|
self.assertEqual(47, sev.cbitpos)
|
|
self.assertEqual(1, sev.reduced_phys_bits)
|
|
|
|
@mock.patch.object(
|
|
fakelibvirt.virConnect, '_domain_capability_features', new=
|
|
fakelibvirt.virConnect._domain_capability_features_with_SEV_unsupported
|
|
)
|
|
def test_get_domain_capabilities_sev_unsupported(self):
|
|
self._test_get_domain_capabilities_sev(False)
|
|
|
|
@mock.patch.object(
|
|
fakelibvirt.virConnect, '_domain_capability_features',
|
|
new=fakelibvirt.virConnect._domain_capability_features_with_SEV)
|
|
def test_get_domain_capabilities_sev_supported(self):
|
|
self._test_get_domain_capabilities_sev(True)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "getHostname")
|
|
def test_get_hostname_caching(self, mock_hostname):
|
|
mock_hostname.return_value = "foo"
|
|
self.assertEqual('foo', self.host.get_hostname())
|
|
mock_hostname.assert_called_with()
|
|
|
|
mock_hostname.reset_mock()
|
|
|
|
mock_hostname.return_value = "bar"
|
|
self.assertEqual('foo', self.host.get_hostname())
|
|
mock_hostname.assert_called_with()
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "getType")
|
|
def test_get_driver_type(self, mock_type):
|
|
mock_type.return_value = "qemu"
|
|
self.assertEqual("qemu", self.host.get_driver_type())
|
|
mock_type.assert_called_once_with()
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "getVersion")
|
|
def test_get_version(self, mock_version):
|
|
mock_version.return_value = 1005001
|
|
self.assertEqual(1005001, self.host.get_version())
|
|
mock_version.assert_called_once_with()
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "secretLookupByUsage")
|
|
def test_find_secret(self, mock_sec):
|
|
"""finding secrets with various usage_type."""
|
|
expected = [
|
|
mock.call(fakelibvirt.VIR_SECRET_USAGE_TYPE_CEPH, 'rbdvol'),
|
|
mock.call(fakelibvirt.VIR_SECRET_USAGE_TYPE_CEPH, 'cephvol'),
|
|
mock.call(fakelibvirt.VIR_SECRET_USAGE_TYPE_ISCSI, 'iscsivol'),
|
|
mock.call(fakelibvirt.VIR_SECRET_USAGE_TYPE_VOLUME, 'vol')]
|
|
|
|
self.host.find_secret('rbd', 'rbdvol')
|
|
self.host.find_secret('ceph', 'cephvol')
|
|
self.host.find_secret('iscsi', 'iscsivol')
|
|
self.host.find_secret('volume', 'vol')
|
|
self.assertEqual(expected, mock_sec.mock_calls)
|
|
self.assertRaises(exception.NovaException,
|
|
self.host.find_secret, "foo", "foovol")
|
|
|
|
mock_sec.side_effect = fakelibvirt.libvirtError("")
|
|
mock_sec.side_effect.err = (66, )
|
|
self.assertIsNone(self.host.find_secret('rbd', 'rbdvol'))
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "secretDefineXML")
|
|
def test_create_secret(self, mock_sec):
|
|
"""creating secrets with various usage_type."""
|
|
self.host.create_secret('rbd', 'rbdvol')
|
|
self.host.create_secret('ceph', 'cephvol')
|
|
self.host.create_secret('iscsi', 'iscsivol')
|
|
self.host.create_secret('volume', 'vol')
|
|
self.host.create_secret('vtpm', 'vtpmdev')
|
|
self.assertRaises(exception.NovaException,
|
|
self.host.create_secret, "foo", "foovol")
|
|
|
|
secret = mock.MagicMock()
|
|
mock_sec.return_value = secret
|
|
self.host.create_secret('iscsi', 'iscsivol', password="foo")
|
|
secret.setValue.assert_called_once_with("foo")
|
|
|
|
@mock.patch('nova.virt.libvirt.host.Host.find_secret')
|
|
def test_delete_secret(self, mock_find_secret):
|
|
"""deleting secret."""
|
|
secret = mock.MagicMock()
|
|
mock_find_secret.return_value = secret
|
|
expected = [mock.call('rbd', 'rbdvol'),
|
|
mock.call().undefine()]
|
|
self.host.delete_secret('rbd', 'rbdvol')
|
|
self.assertEqual(expected, mock_find_secret.mock_calls)
|
|
|
|
mock_find_secret.return_value = None
|
|
self.host.delete_secret("rbd", "rbdvol")
|
|
|
|
def test_get_memory_total(self):
|
|
with mock.patch.object(host.Host, "get_connection") as mock_conn:
|
|
mock_conn().getInfo.return_value = ['zero', 'one', 'two']
|
|
self.assertEqual('one', self.host.get_memory_mb_total())
|
|
|
|
def test_get_memory_total_file_backed(self):
|
|
self.flags(file_backed_memory=1048576, group="libvirt")
|
|
self.assertEqual(1048576, self.host.get_memory_mb_total())
|
|
|
|
def test_get_memory_used(self):
|
|
m = mock.mock_open(read_data="""
|
|
MemTotal: 16194180 kB
|
|
MemFree: 233092 kB
|
|
MemAvailable: 8892356 kB
|
|
Buffers: 567708 kB
|
|
Cached: 8362404 kB
|
|
SwapCached: 0 kB
|
|
Active: 8381604 kB
|
|
""")
|
|
with test.nested(
|
|
mock.patch('builtins.open', m, create=True),
|
|
mock.patch.object(host.Host, "get_connection"),
|
|
) as (mock_file, mock_conn):
|
|
mock_conn().getInfo.return_value = [
|
|
obj_fields.Architecture.X86_64, 15814, 8, 1208, 1, 1, 4, 2]
|
|
|
|
self.assertEqual(6866, self.host.get_memory_mb_used())
|
|
|
|
def test_sum_domain_memory_mb_file_backed(self):
|
|
class DiagFakeDomain(object):
|
|
def __init__(self, id, memmb):
|
|
self.id = id
|
|
self.memmb = memmb
|
|
|
|
def info(self):
|
|
return [0, 0, self.memmb * 1024]
|
|
|
|
def ID(self):
|
|
return self.id
|
|
|
|
def name(self):
|
|
return "instance000001"
|
|
|
|
def UUIDString(self):
|
|
return uuids.fake
|
|
|
|
with mock.patch.object(host.Host, 'list_guests') as mock_list:
|
|
mock_list.return_value = [
|
|
libvirt_guest.Guest(DiagFakeDomain(0, 4096)),
|
|
libvirt_guest.Guest(DiagFakeDomain(1, 2048)),
|
|
libvirt_guest.Guest(DiagFakeDomain(2, 1024)),
|
|
libvirt_guest.Guest(DiagFakeDomain(3, 1024))]
|
|
|
|
self.assertEqual(8192, self.host._sum_domain_memory_mb())
|
|
|
|
def test_get_memory_used_file_backed(self):
|
|
self.flags(file_backed_memory=1048576,
|
|
group='libvirt')
|
|
|
|
with mock.patch.object(
|
|
self.host, "_sum_domain_memory_mb"
|
|
) as mock_sumDomainMemory:
|
|
mock_sumDomainMemory.return_value = 8192
|
|
self.assertEqual(8192, self.host.get_memory_mb_used())
|
|
mock_sumDomainMemory.assert_called_once_with()
|
|
|
|
def test_get_cpu_stats(self):
|
|
stats = self.host.get_cpu_stats()
|
|
self.assertEqual(
|
|
{'kernel': 5664160000000,
|
|
'idle': 1592705190000000,
|
|
'frequency': 800,
|
|
'user': 26728850000000,
|
|
'iowait': 6121490000000},
|
|
stats)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "defineXML")
|
|
def test_write_instance_config(self, mock_defineXML):
|
|
fake_dom_xml = """
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<devices>
|
|
<disk type='file'>
|
|
<source file='filename'/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
conn = self.host.get_connection()
|
|
dom = fakelibvirt.Domain(conn,
|
|
fake_dom_xml,
|
|
False)
|
|
mock_defineXML.return_value = dom
|
|
guest = self.host.write_instance_config(fake_dom_xml)
|
|
mock_defineXML.assert_called_once_with(fake_dom_xml)
|
|
self.assertIsInstance(guest, libvirt_guest.Guest)
|
|
|
|
def test_write_instance_config_unicode(self):
|
|
fake_dom_xml = u"""
|
|
<domain type='kvm'>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<devices>
|
|
<disk type='file'>
|
|
<source file='\u4e2d\u6587'/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
|
|
def emulate_defineXML(xml):
|
|
conn = self.host.get_connection()
|
|
dom = fakelibvirt.Domain(conn, xml, False)
|
|
return dom
|
|
with mock.patch.object(fakelibvirt.virConnect, "defineXML"
|
|
) as mock_defineXML:
|
|
mock_defineXML.side_effect = emulate_defineXML
|
|
guest = self.host.write_instance_config(fake_dom_xml)
|
|
self.assertIsInstance(guest, libvirt_guest.Guest)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "nodeDeviceLookupByName")
|
|
def test_device_lookup_by_name(self, mock_nodeDeviceLookupByName):
|
|
self.host.device_lookup_by_name("foo")
|
|
mock_nodeDeviceLookupByName.assert_called_once_with("foo")
|
|
|
|
def test_get_pcinet_info(self):
|
|
dev_name = "net_enp2s2_02_9a_a1_37_be_54"
|
|
parent_address = "pci_0000_04_11_7"
|
|
net_dev = fakelibvirt.NodeDevice(
|
|
self.host._get_connection(),
|
|
xml=fake_libvirt_data._fake_NodeDevXml[dev_name])
|
|
pci_dev = fakelibvirt.NodeDevice(
|
|
self.host._get_connection(),
|
|
xml=fake_libvirt_data._fake_NodeDevXml[parent_address])
|
|
actualvf = self.host._get_pcinet_info(pci_dev, [net_dev])
|
|
expect_vf = ["rx", "tx", "sg", "tso", "gso", "gro", "rxvlan", "txvlan"]
|
|
self.assertEqual(expect_vf, actualvf)
|
|
|
|
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address')
|
|
def test_get_pcidev_info_non_nic(self, mock_get_ifname):
|
|
dev_name = "pci_0000_04_11_7"
|
|
pci_dev = fakelibvirt.NodeDevice(
|
|
self.host._get_connection(),
|
|
xml=fake_libvirt_data._fake_NodeDevXml[dev_name])
|
|
actual_vf = self.host._get_pcidev_info(dev_name, pci_dev, [], [])
|
|
expect_vf = {
|
|
"dev_id": dev_name, "address": "0000:04:11.7",
|
|
"product_id": '1520', "numa_node": 0,
|
|
"vendor_id": '8086', "label": 'label_8086_1520',
|
|
"dev_type": obj_fields.PciDeviceType.SRIOV_VF,
|
|
'parent_addr': '0000:04:00.3',
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
mock_get_ifname.assert_not_called()
|
|
|
|
@mock.patch.object(pci_utils, 'get_ifname_by_pci_address',
|
|
return_value='ens1')
|
|
def test_get_pcidev_info(self, mock_get_ifname):
|
|
devs = {
|
|
"pci_0000_04_00_3", "pci_0000_04_10_7", "pci_0000_04_11_7",
|
|
"pci_0000_04_00_1", "pci_0000_03_00_0", "pci_0000_03_00_1"
|
|
}
|
|
node_devs = {}
|
|
for dev_name in devs:
|
|
node_devs[dev_name] = (
|
|
fakelibvirt.NodeDevice(
|
|
self.host._get_connection(),
|
|
xml=fake_libvirt_data._fake_NodeDevXml[dev_name]))
|
|
for child in fake_libvirt_data._fake_NodeDevXml_children[dev_name]:
|
|
node_devs[child] = (
|
|
fakelibvirt.NodeDevice(
|
|
self.host._get_connection(),
|
|
xml=fake_libvirt_data._fake_NodeDevXml[child]))
|
|
net_devs = [
|
|
dev for dev in node_devs.values() if dev.name() not in devs]
|
|
|
|
name = "pci_0000_04_00_3"
|
|
actual_vf = self.host._get_pcidev_info(
|
|
name, node_devs[name], net_devs, [])
|
|
expect_vf = {
|
|
"dev_id": "pci_0000_04_00_3",
|
|
"address": "0000:04:00.3",
|
|
"product_id": '1521',
|
|
"numa_node": None,
|
|
"vendor_id": '8086',
|
|
"label": 'label_8086_1521',
|
|
"dev_type": obj_fields.PciDeviceType.SRIOV_PF,
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
|
|
name = "pci_0000_04_10_7"
|
|
actual_vf = self.host._get_pcidev_info(
|
|
name, node_devs[name], net_devs, [])
|
|
expect_vf = {
|
|
"dev_id": "pci_0000_04_10_7",
|
|
"address": "0000:04:10.7",
|
|
"product_id": '1520',
|
|
"numa_node": None,
|
|
"vendor_id": '8086',
|
|
"label": 'label_8086_1520',
|
|
"dev_type": obj_fields.PciDeviceType.SRIOV_VF,
|
|
"parent_addr": '0000:04:00.3',
|
|
"parent_ifname": "ens1",
|
|
"capabilities": {
|
|
"network": ["rx", "tx", "sg", "tso", "gso", "gro",
|
|
"rxvlan", "txvlan"]},
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
|
|
name = "pci_0000_04_11_7"
|
|
actual_vf = self.host._get_pcidev_info(
|
|
name, node_devs[name], net_devs, [])
|
|
expect_vf = {
|
|
"dev_id": "pci_0000_04_11_7",
|
|
"address": "0000:04:11.7",
|
|
"product_id": '1520',
|
|
"vendor_id": '8086',
|
|
"numa_node": 0,
|
|
"label": 'label_8086_1520',
|
|
"dev_type": obj_fields.PciDeviceType.SRIOV_VF,
|
|
"parent_addr": '0000:04:00.3',
|
|
"capabilities": {
|
|
"network": ["rx", "tx", "sg", "tso", "gso", "gro",
|
|
"rxvlan", "txvlan"]},
|
|
"parent_ifname": "ens1",
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
|
|
name = "pci_0000_04_00_1"
|
|
actual_vf = self.host._get_pcidev_info(
|
|
name, node_devs[name], net_devs, [])
|
|
expect_vf = {
|
|
"dev_id": "pci_0000_04_00_1",
|
|
"address": "0000:04:00.1",
|
|
"product_id": '1013',
|
|
"numa_node": 0,
|
|
"vendor_id": '15b3',
|
|
"label": 'label_15b3_1013',
|
|
"dev_type": obj_fields.PciDeviceType.STANDARD,
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
|
|
name = "pci_0000_03_00_0"
|
|
actual_vf = self.host._get_pcidev_info(
|
|
name, node_devs[name], net_devs, [])
|
|
expect_vf = {
|
|
"dev_id": "pci_0000_03_00_0",
|
|
"address": "0000:03:00.0",
|
|
"product_id": '1013',
|
|
"numa_node": 0,
|
|
"vendor_id": '15b3',
|
|
"label": 'label_15b3_1013',
|
|
"dev_type": obj_fields.PciDeviceType.SRIOV_PF,
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
|
|
name = "pci_0000_03_00_1"
|
|
actual_vf = self.host._get_pcidev_info(
|
|
name, node_devs[name], net_devs, [])
|
|
expect_vf = {
|
|
"dev_id": "pci_0000_03_00_1",
|
|
"address": "0000:03:00.1",
|
|
"product_id": '1013',
|
|
"numa_node": 0,
|
|
"vendor_id": '15b3',
|
|
"label": 'label_15b3_1013',
|
|
"dev_type": obj_fields.PciDeviceType.SRIOV_PF,
|
|
}
|
|
self.assertEqual(expect_vf, actual_vf)
|
|
|
|
def test_list_pci_devices(self):
|
|
with mock.patch.object(self.host, "_list_devices") as mock_listDevices:
|
|
self.host.list_pci_devices(8)
|
|
mock_listDevices.assert_called_once_with('pci', flags=8)
|
|
|
|
def test_list_mdev_capable_devices(self):
|
|
with mock.patch.object(self.host, "_list_devices") as mock_listDevices:
|
|
self.host.list_mdev_capable_devices(8)
|
|
mock_listDevices.assert_called_once_with('mdev_types', flags=8)
|
|
|
|
def test_list_mediated_devices(self):
|
|
with mock.patch.object(self.host, "_list_devices") as mock_listDevices:
|
|
self.host.list_mediated_devices(8)
|
|
mock_listDevices.assert_called_once_with('mdev', flags=8)
|
|
|
|
def test_list_all_devices(self):
|
|
with mock.patch.object(
|
|
self.host.get_connection(),
|
|
"listAllDevices") as mock_list_all_devices:
|
|
xml_str = """
|
|
<device>
|
|
<name>pci_0000_04_00_3</name>
|
|
<parent>pci_0000_00_01_1</parent>
|
|
<driver>
|
|
<name>igb</name>
|
|
</driver>
|
|
<capability type='pci'>
|
|
<domain>0</domain>
|
|
<bus>4</bus>
|
|
<slot>0</slot>
|
|
<function>3</function>
|
|
<product id='0x1521'>I350 Gigabit Network Connection</product>
|
|
<vendor id='0x8086'>Intel Corporation</vendor>
|
|
<capability type='virt_functions'>
|
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x3'/>
|
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x7'/>
|
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x3'/>
|
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x7'/>
|
|
</capability>
|
|
</capability>
|
|
</device>"""
|
|
pci_dev = fakelibvirt.NodeDevice(None, xml=xml_str)
|
|
node_devs = [pci_dev]
|
|
mock_list_all_devices.return_value = node_devs
|
|
ret = self.host.list_all_devices(flags=42)
|
|
self.assertEqual(node_devs, ret)
|
|
mock_list_all_devices.assert_called_once_with(42)
|
|
|
|
def test_list_all_devices_raises(self):
|
|
with mock.patch.object(
|
|
self.host.get_connection(),
|
|
"listAllDevices") as mock_list_all_devices:
|
|
xml_str = """
|
|
<device>
|
|
<name>pci_0000_04_00_3</name>
|
|
<parent>pci_0000_00_01_1</parent>
|
|
<driver>
|
|
<name>igb</name>
|
|
</driver>
|
|
<capability type='pci'>
|
|
<domain>0</domain>
|
|
<bus>4</bus>
|
|
<slot>0</slot>
|
|
<function>3</function>
|
|
<product id='0x1521'>I350 Gigabit Network Connection</product>
|
|
<vendor id='0x8086'>Intel Corporation</vendor>
|
|
<capability type='virt_functions'>
|
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x3'/>
|
|
<address domain='0x0000' bus='0x04' slot='0x10' function='0x7'/>
|
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x3'/>
|
|
<address domain='0x0000' bus='0x04' slot='0x11' function='0x7'/>
|
|
</capability>
|
|
</capability>
|
|
</device>"""
|
|
pci_dev = fakelibvirt.NodeDevice(None, xml=xml_str)
|
|
node_devs = [pci_dev]
|
|
mock_list_all_devices.return_value = node_devs
|
|
mock_list_all_devices.side_effect = fakelibvirt.libvirtError(
|
|
"message")
|
|
ret = self.host.list_all_devices(flags=42)
|
|
self.assertEqual([], ret)
|
|
mock_list_all_devices.assert_called_once_with(42)
|
|
|
|
def test_get_vdpa_nodedev_by_address(self):
|
|
with test.nested(
|
|
mock.patch.object(
|
|
self.host.get_connection(), "listAllDevices"),
|
|
mock.patch.object(self.host, "_get_pcinet_info"),
|
|
) as (mock_list_all_devices, mock_get_pci_info):
|
|
vdpa_str = """
|
|
<device>
|
|
<name>vdpa_vdpa0</name>
|
|
<path>/sys/devices/pci0000:00/0000:00:02.2/0000:06:00.2/vdpa0</path>
|
|
<parent>pci_0000_06_00_2</parent>
|
|
<driver>
|
|
<name>vhost_vdpa</name>
|
|
</driver>
|
|
<capability type='vdpa'>
|
|
<chardev>/dev/vhost-vdpa-0</chardev>
|
|
</capability>
|
|
</device>""" # noqa: E501
|
|
vdpa_dev = fakelibvirt.NodeDevice(None, xml=vdpa_str)
|
|
vf_str = """
|
|
<device>
|
|
<name>pci_0000_06_00_2</name>
|
|
<path>/sys/devices/pci0000:00/0000:00:02.2/0000:06:00.2</path>
|
|
<parent>pci_0000_00_02_2</parent>
|
|
<driver>
|
|
<name>mlx5_core</name>
|
|
</driver>
|
|
<capability type='pci'>
|
|
<class>0x020000</class>
|
|
<domain>0</domain>
|
|
<bus>6</bus>
|
|
<slot>0</slot>
|
|
<function>2</function>
|
|
<product id='0x101e'>ConnectX Family mlx5Gen Virtual Function</product>
|
|
<vendor id='0x15b3'>Mellanox Technologies</vendor>
|
|
<capability type='phys_function'>
|
|
<address domain='0x0000' bus='0x06' slot='0x00' function='0x0'/>
|
|
</capability>
|
|
<iommuGroup number='99'>
|
|
<address domain='0x0000' bus='0x06' slot='0x00' function='0x2'/>
|
|
</iommuGroup>
|
|
<numa node='0'/>
|
|
<pci-express>
|
|
<link validity='cap' port='0' speed='16' width='8'/>
|
|
<link validity='sta' width='0'/>
|
|
</pci-express>
|
|
</capability>
|
|
</device>""" # noqa: E501
|
|
vf_dev = fakelibvirt.NodeDevice(None, xml=vf_str)
|
|
node_devs = [vdpa_dev, vf_dev]
|
|
mock_list_all_devices.return_value = node_devs
|
|
mock_get_pci_info.return_value = [
|
|
{
|
|
"dev_id": "pci_0000_06_00_2",
|
|
"address": "0000:06:00.2",
|
|
"product_id": '101e',
|
|
"vendor_id": '15b3',
|
|
"numa_node": 0,
|
|
"label": 'label_101e_15b3',
|
|
"dev_type": 'type-VF',
|
|
"parent_addr": "0000:00:02.2",
|
|
"parent_ifname": "enp6s0f0",
|
|
"capabilities": {
|
|
"network": [
|
|
"rx", "tx", "sg", "tso", "gso", "gro", "rxvlan",
|
|
"txvlan",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
"dev_id": "vdpa_vdpa0",
|
|
"address": "0000:06:00.2",
|
|
"product_id": '101e',
|
|
"vendor_id": '15b3',
|
|
"numa_node": 0,
|
|
"label": 'label_101e_15b3',
|
|
"dev_type": 'vdpa',
|
|
'parent_addr': '0000:06:00.2',
|
|
"parent_ifname": "enp6s0f0v0",
|
|
"capabilities": {
|
|
"network": [
|
|
"rx", "tx", "sg", "tso", "gso", "gro",
|
|
"rxvlan", "txvlan"],
|
|
},
|
|
},
|
|
]
|
|
ret = self.host.get_vdpa_nodedev_by_address("0000:06:00.2")
|
|
cfgdev = vconfig.LibvirtConfigNodeDevice()
|
|
cfgdev.parse_str(vdpa_str)
|
|
self.assertEqual(cfgdev.name, ret.name)
|
|
self.assertEqual(
|
|
"/dev/vhost-vdpa-0", ret.vdpa_capability.dev_path)
|
|
|
|
def test_get_vdpa_device_path(self):
|
|
with mock.patch.object(
|
|
self.host, "get_vdpa_nodedev_by_address",
|
|
) as mock_vdpa:
|
|
xml_str = """
|
|
<device>
|
|
<name>vdpa_vdpa0</name>
|
|
<path>/sys/devices/pci0000:00/0000:00:02.2/0000:06:00.2/vdpa0</path>
|
|
<parent>pci_0000_06_00_2</parent>
|
|
<driver>
|
|
<name>vhost_vdpa</name>
|
|
</driver>
|
|
<capability type='vdpa'>
|
|
<chardev>/dev/vhost-vdpa-0</chardev>
|
|
</capability>
|
|
</device>"""
|
|
cfgdev = vconfig.LibvirtConfigNodeDevice()
|
|
cfgdev.parse_str(xml_str)
|
|
mock_vdpa.return_value = cfgdev
|
|
ret = self.host.get_vdpa_device_path("0000:06:00.2")
|
|
self.assertEqual("/dev/vhost-vdpa-0", ret)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "listDevices")
|
|
def test_list_devices(self, mock_listDevices):
|
|
self.host._list_devices('mdev', 8)
|
|
mock_listDevices.assert_called_once_with('mdev', 8)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "listDevices")
|
|
def test_list_devices_unsupported(self, mock_listDevices):
|
|
not_supported_exc = fakelibvirt.make_libvirtError(
|
|
fakelibvirt.libvirtError,
|
|
'this function is not supported by the connection driver:'
|
|
' listDevices',
|
|
error_code=fakelibvirt.VIR_ERR_NO_SUPPORT)
|
|
mock_listDevices.side_effect = not_supported_exc
|
|
self.assertEqual([], self.host._list_devices('mdev', 8))
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "listDevices")
|
|
def test_list_devices_other_exc(self, mock_listDevices):
|
|
mock_listDevices.side_effect = fakelibvirt.libvirtError('test')
|
|
self.assertRaises(fakelibvirt.libvirtError,
|
|
self.host._list_devices, 'mdev', 8)
|
|
|
|
@mock.patch.object(fakelibvirt.virConnect, "compareCPU")
|
|
def test_compare_cpu(self, mock_compareCPU):
|
|
self.host.compare_cpu("cpuxml")
|
|
mock_compareCPU.assert_called_once_with("cpuxml", 0)
|
|
|
|
def test_is_cpu_control_policy_capable_ok(self):
|
|
m = mock.mock_open(
|
|
read_data="""cg /cgroup/cpu,cpuacct cg opt1,cpu,opt3 0 0
|
|
cg /cgroup/memory cg opt1,opt2 0 0
|
|
""")
|
|
with mock.patch('builtins.open', m, create=True):
|
|
self.assertTrue(self.host.is_cpu_control_policy_capable())
|
|
|
|
def test_is_cpu_control_policy_capable_ko(self):
|
|
m = mock.mock_open(
|
|
read_data="""cg /cgroup/cpu,cpuacct cg opt1,opt2,opt3 0 0
|
|
cg /cgroup/memory cg opt1,opt2 0 0
|
|
""")
|
|
with mock.patch('builtins.open', m, create=True):
|
|
self.assertFalse(self.host.is_cpu_control_policy_capable())
|
|
|
|
@mock.patch('builtins.open', side_effect=IOError)
|
|
def test_is_cpu_control_policy_capable_ioerror(self, mock_open):
|
|
self.assertFalse(self.host.is_cpu_control_policy_capable())
|
|
|
|
def test_get_canonical_machine_type(self):
|
|
# this test relies on configuration from the FakeLibvirtFixture
|
|
|
|
machine_type = self.host.get_canonical_machine_type('x86_64', 'pc')
|
|
self.assertEqual('pc-i440fx-2.11', machine_type)
|
|
|
|
machine_type = self.host.get_canonical_machine_type(
|
|
'x86_64', 'pc-i440fx-2.11')
|
|
self.assertEqual('pc-i440fx-2.11', machine_type)
|
|
|
|
self.assertRaises(
|
|
exception.InternalError,
|
|
self.host.get_canonical_machine_type,
|
|
'x86_64', 'pc-foo-1.2')
|
|
|
|
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
|
def test_has_hyperthreading__true(self, mock_cap):
|
|
mock_cap.return_value = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>1f71d34a-7c89-45cf-95ce-3df20fc6b936</uuid>
|
|
<cpu>
|
|
</cpu>
|
|
<topology>
|
|
<cells num='1'>
|
|
<cell id='0'>
|
|
<cpus num='4'>
|
|
<cpu id='0' socket_id='0' core_id='0' siblings='0,2'/>
|
|
<cpu id='1' socket_id='0' core_id='1' siblings='1,3'/>
|
|
<cpu id='2' socket_id='0' core_id='0' siblings='0,2'/>
|
|
<cpu id='3' socket_id='0' core_id='1' siblings='1,3'/>
|
|
</cpus>
|
|
</cell>
|
|
</cells>
|
|
</topology>
|
|
</host>
|
|
</capabilities>
|
|
"""
|
|
self.assertTrue(self.host.has_hyperthreading)
|
|
|
|
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
|
def test_has_hyperthreading__false(self, mock_cap):
|
|
mock_cap.return_value = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>1f71d34a-7c89-45cf-95ce-3df20fc6b936</uuid>
|
|
<cpu>
|
|
</cpu>
|
|
<topology>
|
|
<cells num='1'>
|
|
<cell id='0'>
|
|
<cpus num='4'>
|
|
<cpu id='0' socket_id='0' core_id='0' siblings='0'/>
|
|
<cpu id='1' socket_id='0' core_id='1' siblings='1'/>
|
|
<cpu id='2' socket_id='0' core_id='2' siblings='2'/>
|
|
<cpu id='3' socket_id='0' core_id='3' siblings='3'/>
|
|
</cpus>
|
|
</cell>
|
|
</cells>
|
|
</topology>
|
|
</host>
|
|
</capabilities>
|
|
"""
|
|
self.assertFalse(self.host.has_hyperthreading)
|
|
|
|
@mock.patch(
|
|
'nova.virt.libvirt.host.libvirt.Connection.getDomainCapabilities')
|
|
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
|
def test_supports_uefi__false(self, mock_caps, mock_domcaps):
|
|
mock_caps.return_value = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<vendor>Intel</vendor>
|
|
</cpu>
|
|
</host>
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'/>
|
|
</guest>
|
|
</capabilities>
|
|
"""
|
|
mock_domcaps.return_value = """
|
|
<domainCapabilities>
|
|
<machine>pc-q35-5.1</machine>
|
|
<arch>x86_64</arch>
|
|
<os supported='yes'>
|
|
<enum name='firmware'>
|
|
<value>bios</value>
|
|
</enum>
|
|
<loader supported='yes'>
|
|
<enum name='type'>
|
|
<value>rom</value>
|
|
</enum>
|
|
<enum name='readonly'>
|
|
<value>yes</value>
|
|
</enum>
|
|
<enum name='secure'>
|
|
<value>no</value>
|
|
</enum>
|
|
</loader>
|
|
</os>
|
|
</domainCapabilities>
|
|
"""
|
|
self.assertFalse(self.host.supports_uefi)
|
|
|
|
@mock.patch(
|
|
'nova.virt.libvirt.host.libvirt.Connection.getDomainCapabilities')
|
|
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
|
def test_supports_uefi__true(self, mock_caps, mock_domcaps):
|
|
mock_caps.return_value = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<vendor>Intel</vendor>
|
|
</cpu>
|
|
</host>
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'/>
|
|
</guest>
|
|
</capabilities>
|
|
"""
|
|
mock_domcaps.return_value = """
|
|
<domainCapabilities>
|
|
<machine>pc-q35-5.1</machine>
|
|
<arch>x86_64</arch>
|
|
<os supported='yes'>
|
|
<enum name='firmware'>
|
|
<value>efi</value>
|
|
</enum>
|
|
<loader supported='yes'>
|
|
<value>/usr/share/edk2/ovmf/OVMF_CODE.fd</value>
|
|
<enum name='type'>
|
|
<value>rom</value>
|
|
<value>pflash</value>
|
|
</enum>
|
|
<enum name='readonly'>
|
|
<value>yes</value>
|
|
<value>no</value>
|
|
</enum>
|
|
<enum name='secure'>
|
|
<value>no</value>
|
|
</enum>
|
|
</loader>
|
|
</os>
|
|
</domainCapabilities>
|
|
"""
|
|
self.assertTrue(self.host.supports_uefi)
|
|
|
|
@mock.patch(
|
|
'nova.virt.libvirt.host.libvirt.Connection.getDomainCapabilities')
|
|
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
|
def test_supports_secure_boot__false(self, mock_caps, mock_domcaps):
|
|
mock_caps.return_value = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<vendor>Intel</vendor>
|
|
</cpu>
|
|
</host>
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'/>
|
|
</guest>
|
|
</capabilities>
|
|
"""
|
|
mock_domcaps.return_value = """
|
|
<domainCapabilities>
|
|
<machine>pc-q35-5.1</machine>
|
|
<arch>x86_64</arch>
|
|
<os supported='yes'>
|
|
<enum name='firmware'>
|
|
<value>efi</value>
|
|
</enum>
|
|
<loader supported='yes'>
|
|
<value>/usr/share/edk2/ovmf/OVMF_CODE.fd</value>
|
|
<enum name='type'>
|
|
<value>rom</value>
|
|
<value>pflash</value>
|
|
</enum>
|
|
<enum name='readonly'>
|
|
<value>yes</value>
|
|
<value>no</value>
|
|
</enum>
|
|
<enum name='secure'>
|
|
<value>no</value>
|
|
</enum>
|
|
</loader>
|
|
</os>
|
|
</domainCapabilities>
|
|
"""
|
|
self.assertFalse(self.host.supports_secure_boot)
|
|
|
|
@mock.patch(
|
|
'nova.virt.libvirt.host.libvirt.Connection.getDomainCapabilities')
|
|
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
|
def test_supports_secure_boot__true(self, mock_caps, mock_domcaps):
|
|
mock_caps.return_value = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<vendor>Intel</vendor>
|
|
</cpu>
|
|
</host>
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'/>
|
|
</guest>
|
|
</capabilities>
|
|
"""
|
|
mock_domcaps.return_value = """
|
|
<domainCapabilities>
|
|
<machine>pc-q35-5.1</machine>
|
|
<arch>x86_64</arch>
|
|
<os supported='yes'>
|
|
<enum name='firmware'>
|
|
<value>efi</value>
|
|
</enum>
|
|
<loader supported='yes'>
|
|
<value>/usr/share/edk2/ovmf/OVMF_CODE.fd</value>
|
|
<value>/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd</value>
|
|
<enum name='type'>
|
|
<value>rom</value>
|
|
<value>pflash</value>
|
|
</enum>
|
|
<enum name='readonly'>
|
|
<value>yes</value>
|
|
<value>no</value>
|
|
</enum>
|
|
<enum name='secure'>
|
|
<value>yes</value>
|
|
<value>no</value>
|
|
</enum>
|
|
</loader>
|
|
</os>
|
|
</domainCapabilities>
|
|
"""
|
|
self.assertTrue(self.host.supports_secure_boot)
|
|
|
|
@mock.patch.object(host.Host, 'loaders', new_callable=mock.PropertyMock)
|
|
@mock.patch.object(host.Host, 'get_canonical_machine_type')
|
|
def test_get_loader(self, mock_get_mtype, mock_loaders):
|
|
loaders = [
|
|
{
|
|
'description': 'Sample descriptor',
|
|
'interface-types': ['uefi'],
|
|
'mapping': {
|
|
'device': 'flash',
|
|
'executable': {
|
|
'filename': '/usr/share/edk2/ovmf/OVMF_CODE.fd',
|
|
'format': 'raw',
|
|
},
|
|
'nvram-template': {
|
|
'filename': '/usr/share/edk2/ovmf/OVMF_VARS.fd',
|
|
'format': 'raw',
|
|
},
|
|
},
|
|
'targets': [
|
|
{
|
|
'architecture': 'x86_64',
|
|
'machines': ['pc-q35-*'], # exclude pc-i440fx-*
|
|
},
|
|
],
|
|
'features': ['acpi-s3', 'amd-sev', 'verbose-dynamic'],
|
|
'tags': [],
|
|
},
|
|
]
|
|
|
|
def fake_get_mtype(arch, machine):
|
|
return {
|
|
'x86_64': {
|
|
'pc': 'pc-i440fx-5.1',
|
|
'q35': 'pc-q35-5.1',
|
|
},
|
|
'aarch64': {
|
|
'virt': 'virt-5.1',
|
|
},
|
|
}[arch][machine]
|
|
|
|
mock_get_mtype.side_effect = fake_get_mtype
|
|
mock_loaders.return_value = loaders
|
|
|
|
# this should pass because we're not reporting the secure-boot feature
|
|
# which is what we don't want
|
|
loader = self.host.get_loader('x86_64', 'q35', has_secure_boot=False)
|
|
self.assertIsNotNone(loader)
|
|
|
|
# while it should fail here since we want it now
|
|
self.assertRaises(
|
|
exception.UEFINotSupported,
|
|
self.host.get_loader,
|
|
'x86_64', 'q35', has_secure_boot=True)
|
|
|
|
# it should also fail for an unsupported architecture
|
|
self.assertRaises(
|
|
exception.UEFINotSupported,
|
|
self.host.get_loader,
|
|
'aarch64', 'virt', has_secure_boot=False)
|
|
|
|
# or an unsupported machine type
|
|
self.assertRaises(
|
|
exception.UEFINotSupported,
|
|
self.host.get_loader,
|
|
'x86_64', 'pc', has_secure_boot=False)
|
|
|
|
# add the secure-boot feature flag
|
|
loaders[0]['features'].append('secure-boot')
|
|
|
|
# this should pass because we're reporting the secure-boot feature
|
|
# which is what we want
|
|
loader = self.host.get_loader('x86_64', 'q35', has_secure_boot=True)
|
|
self.assertIsNotNone(loader)
|
|
|
|
# while it should fail here since we don't want it now
|
|
self.assertRaises(
|
|
exception.UEFINotSupported,
|
|
self.host.get_loader,
|
|
'x86_64', 'q35', has_secure_boot=False)
|
|
|
|
|
|
vc = fakelibvirt.virConnect
|
|
|
|
|
|
class TestLibvirtSEV(test.NoDBTestCase):
|
|
"""Libvirt host tests for AMD SEV support."""
|
|
|
|
def setUp(self):
|
|
super(TestLibvirtSEV, self).setUp()
|
|
|
|
self.useFixture(nova_fixtures.LibvirtFixture())
|
|
self.host = host.Host("qemu:///system")
|
|
|
|
|
|
class TestLibvirtSEVUnsupported(TestLibvirtSEV):
|
|
@mock.patch.object(os.path, 'exists', return_value=False)
|
|
def test_kernel_parameter_missing(self, fake_exists):
|
|
self.assertFalse(self.host._kernel_supports_amd_sev())
|
|
fake_exists.assert_called_once_with(
|
|
'/sys/module/kvm_amd/parameters/sev')
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
@mock.patch('builtins.open', mock.mock_open(read_data="0\n"))
|
|
def test_kernel_parameter_zero(self, fake_exists):
|
|
self.assertFalse(self.host._kernel_supports_amd_sev())
|
|
fake_exists.assert_called_once_with(
|
|
'/sys/module/kvm_amd/parameters/sev')
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
@mock.patch('builtins.open', mock.mock_open(read_data="1\n"))
|
|
def test_kernel_parameter_one(self, fake_exists):
|
|
self.assertTrue(self.host._kernel_supports_amd_sev())
|
|
fake_exists.assert_called_once_with(
|
|
'/sys/module/kvm_amd/parameters/sev')
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
@mock.patch('builtins.open', mock.mock_open(read_data="1\n"))
|
|
def test_unsupported_without_feature(self, fake_exists):
|
|
self.assertFalse(self.host.supports_amd_sev)
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
@mock.patch('builtins.open', mock.mock_open(read_data="1\n"))
|
|
@mock.patch.object(vc, '_domain_capability_features',
|
|
new=vc._domain_capability_features_with_SEV_unsupported)
|
|
def test_unsupported_with_feature(self, fake_exists):
|
|
self.assertFalse(self.host.supports_amd_sev)
|
|
|
|
def test_non_x86_architecture(self):
|
|
fake_caps_xml = '''
|
|
<capabilities>
|
|
<host>
|
|
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
|
<cpu>
|
|
<arch>aarch64</arch>
|
|
</cpu>
|
|
</host>
|
|
</capabilities>'''
|
|
with mock.patch.object(fakelibvirt.virConnect, 'getCapabilities',
|
|
return_value=fake_caps_xml):
|
|
self.assertFalse(self.host.supports_amd_sev)
|
|
|
|
|
|
class TestLibvirtSEVSupported(TestLibvirtSEV):
|
|
"""Libvirt driver tests for when AMD SEV support is present."""
|
|
|
|
@mock.patch.object(os.path, 'exists', return_value=True)
|
|
@mock.patch('builtins.open', mock.mock_open(read_data="1\n"))
|
|
@mock.patch.object(vc, '_domain_capability_features',
|
|
new=vc._domain_capability_features_with_SEV)
|
|
def test_supported_with_feature(self, fake_exists):
|
|
self.assertTrue(self.host.supports_amd_sev)
|
|
|
|
|
|
class LibvirtTpoolProxyTestCase(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(LibvirtTpoolProxyTestCase, self).setUp()
|
|
|
|
self.useFixture(nova_fixtures.LibvirtFixture())
|
|
self.host = host.Host("qemu:///system")
|
|
|
|
def _stub_xml(uuid):
|
|
return ("<domain>"
|
|
" <uuid>" + uuid + "</uuid>"
|
|
" <name>" + uuid + "</name>"
|
|
"</domain>")
|
|
|
|
self.conn = self.host.get_connection()
|
|
self.conn.defineXML(_stub_xml(uuids.vm1)).create()
|
|
self.conn.defineXML(_stub_xml(uuids.vm2)).create()
|
|
|
|
def test_get_libvirt_proxy_classes(self):
|
|
proxy_classes = host.Host._get_libvirt_proxy_classes(host.libvirt)
|
|
|
|
# Assert the classes we're using currently
|
|
# NOTE(mdbooth): We're obviously asserting the wrong classes here
|
|
# because we're explicitly asserting the faked versions. This is a
|
|
# concession to avoid a test dependency on libvirt.
|
|
self.assertIn(fakelibvirt.virDomain, proxy_classes)
|
|
self.assertIn(fakelibvirt.virConnect, proxy_classes)
|
|
self.assertIn(fakelibvirt.virNodeDevice, proxy_classes)
|
|
self.assertIn(fakelibvirt.virSecret, proxy_classes)
|
|
|
|
# Assert that we filtered out libvirtError and any private classes
|
|
self.assertNotIn(fakelibvirt.libvirtError, proxy_classes)
|
|
self.assertNotIn(fakelibvirt._EventAddHandleFunc, proxy_classes)
|
|
|
|
def test_tpool_get_connection(self):
|
|
# Test that Host.get_connection() returns a tpool.Proxy
|
|
self.assertIsInstance(self.conn, tpool.Proxy)
|
|
|
|
def test_tpool_instance_lookup(self):
|
|
# Test that domains returns by our libvirt connection are also proxied
|
|
dom = self.conn.lookupByUUIDString(uuids.vm1)
|
|
self.assertIsInstance(dom, tpool.Proxy)
|
|
|
|
def test_tpool_list_all_connections(self):
|
|
# Test that Host.list_all_connections() returns a list of proxied
|
|
# virDomain objects
|
|
|
|
domains = self.host.list_instance_domains()
|
|
self.assertEqual(2, len(domains))
|
|
for domain in domains:
|
|
self.assertIsInstance(domain, tpool.Proxy)
|
|
self.assertIn(domain.UUIDString(), (uuids.vm1, uuids.vm2))
|
|
|
|
|
|
class LoadersTestCase(test.NoDBTestCase):
|
|
|
|
def test_loaders(self):
|
|
loader = {
|
|
'description': 'Sample descriptor',
|
|
'interface-types': ['uefi'],
|
|
'mapping': {
|
|
'device': 'flash',
|
|
'executable': {
|
|
'filename': '/usr/share/edk2/ovmf/OVMF_CODE.fd',
|
|
'format': 'raw',
|
|
},
|
|
'nvram-template': {
|
|
'filename': '/usr/share/edk2/ovmf/OVMF_VARS.fd',
|
|
'format': 'raw',
|
|
},
|
|
},
|
|
'targets': [
|
|
{
|
|
'architecture': 'x86_64',
|
|
'machines': ['pc-i440fx-*', 'pc-q35-*'],
|
|
},
|
|
],
|
|
'features': ['acpi-s3', 'amd-sev', 'verbose-dynamic'],
|
|
'tags': [],
|
|
}
|
|
|
|
m = mock.mock_open(read_data=jsonutils.dumps(loader).encode('utf-8'))
|
|
with test.nested(
|
|
mock.patch.object(
|
|
os.path, 'exists',
|
|
side_effect=lambda path: path == '/usr/share/qemu/firmware'),
|
|
mock.patch('glob.glob', return_value=['10_fake.json']),
|
|
mock.patch('builtins.open', m, create=True),
|
|
) as (mock_exists, mock_glob, mock_open):
|
|
loaders = host._get_loaders()
|
|
|
|
self.assertEqual(loaders, [loader])
|
|
|
|
mock_exists.assert_has_calls([
|
|
mock.call('/usr/share/qemu/firmware'),
|
|
mock.call('/etc/qemu/firmware'),
|
|
])
|
|
mock_glob.assert_called_once_with(
|
|
'/usr/share/qemu/firmware/*.json')
|
|
mock_open.assert_called_once_with('10_fake.json', 'rb')
|