libvirt: introduce a Guest to wrap around virDomain

Introduces object Guest to wrap around virDomain object
and provide a higher level API.
With the introduction of this new class also update _create_domain
logic and update tests.

Next patches will clean code in drivers to use this
new implementation.

Note about Copyrights, they have been copied from
libvirt/driver.py and libvirt/test_driver.py since several
parts of the code from those files will be moved in
guests.py and test_guest.py

Change-Id: I2d193ae44a4ac48ad863d237172cf2c1da05e3e9
This commit is contained in:
Sahid Orentino Ferdjaoui 2015-04-23 07:08:12 -04:00
parent 93cbba96fa
commit 5ced65f873
5 changed files with 332 additions and 81 deletions

View File

@ -68,6 +68,9 @@ class NUMAServersTest(ServersTestBase):
self.useFixture(fixtures.MonkeyPatch(
'nova.virt.libvirt.host.libvirt',
fakelibvirt))
self.useFixture(fixtures.MonkeyPatch(
'nova.virt.libvirt.guest.libvirt',
fakelibvirt))
self.useFixture(fakelibvirt.FakeLibvirtFixture())
def _setup_compute_service(self):

View File

@ -86,6 +86,7 @@ from nova.virt.libvirt import blockinfo
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt import driver as libvirt_driver
from nova.virt.libvirt import firewall
from nova.virt.libvirt import guest as libvirt_guest
from nova.virt.libvirt import host
from nova.virt.libvirt import imagebackend
from nova.virt.libvirt import lvm
@ -95,6 +96,7 @@ from nova.virt.libvirt import volume as volume_drivers
libvirt_driver.libvirt = fakelibvirt
host.libvirt = fakelibvirt
libvirt_guest.libvirt = fakelibvirt
CONF = cfg.CONF
@ -602,7 +604,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
return objects.Service(**service_ref)
def _get_launch_flags(self, drvr, network_info, power_on=True,
def _get_pause_flag(self, drvr, network_info, power_on=True,
vifs_already_plugged=False):
timeout = CONF.vif_plugging_timeout
@ -613,9 +615,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
power_on and timeout):
events = drvr._get_neutron_events(network_info)
launch_flags = events and fakelibvirt.VIR_DOMAIN_START_PAUSED or 0
return launch_flags
return bool(events)
def test_public_api_signatures(self):
baseinst = driver.ComputeDriver(None)
@ -9957,29 +9957,14 @@ class LibvirtConnTestCase(test.NoDBTestCase):
dom_mock.ID.assert_called_once_with()
mock_get_domain.assert_called_once_with(instance)
@mock.patch.object(encodeutils, 'safe_decode')
def test_create_domain(self, mock_safe_decode):
def test_create_domain(self):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
mock_domain = mock.MagicMock()
mock_instance = mock.MagicMock()
domain = drvr._create_domain(domain=mock_domain,
instance=mock_instance)
domain = drvr._create_domain(domain=mock_domain)
self.assertEqual(mock_domain, domain)
mock_domain.createWithFlags.assert_has_calls([mock.call(0)])
# There is a global in oslo.log which calls encodeutils.safe_decode
# which could be getting called from any number of places, so we need
# to just assert that safe_decode was called at least twice in
# _create_domain with the errors='ignore' kwarg.
safe_decode_ignore_errors_calls = 0
for call in mock_safe_decode.call_args_list:
# call is a tuple where 0 is positional args and 1 is a kwargs dict
if call[1].get('errors') == 'ignore':
safe_decode_ignore_errors_calls += 1
self.assertTrue(safe_decode_ignore_errors_calls >= 2,
'safe_decode should have been called at least twice')
@mock.patch('nova.virt.disk.api.clean_lxc_namespace')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.get_info')
@ -10154,13 +10139,13 @@ class LibvirtConnTestCase(test.NoDBTestCase):
self.log_error_called = False
def fake_error(msg, *args):
def fake_error(msg, *args, **kwargs):
self.log_error_called = True
self.assertIn(fake_xml, msg % args)
self.assertIn('safe decoded', msg % args)
self.stubs.Set(encodeutils, 'safe_decode', fake_safe_decode)
self.stubs.Set(nova.virt.libvirt.driver.LOG, 'error', fake_error)
self.stubs.Set(nova.virt.libvirt.guest.LOG, 'error', fake_error)
self.create_fake_libvirt_mock(defineXML=fake_defineXML)
self.mox.ReplayAll()
@ -10182,12 +10167,12 @@ class LibvirtConnTestCase(test.NoDBTestCase):
self.log_error_called = False
def fake_error(msg, *args):
def fake_error(msg, *args, **kwargs):
self.log_error_called = True
self.assertIn(fake_xml, msg % args)
self.stubs.Set(fake_domain, 'createWithFlags', fake_createWithFlags)
self.stubs.Set(nova.virt.libvirt.driver.LOG, 'error', fake_error)
self.stubs.Set(nova.virt.libvirt.guest.LOG, 'error', fake_error)
self.create_fake_libvirt_mock()
self.mox.ReplayAll()
@ -10204,21 +10189,27 @@ class LibvirtConnTestCase(test.NoDBTestCase):
fake_xml = "<test>this is a test</test>"
fake_domain = FakeVirtDomain(fake_xml)
def fake_enable_hairpin(launch_flags):
def fake_execute(*args, **kwargs):
raise processutils.ProcessExecutionError('error')
def fake_get_interfaces(*args):
return ["dev"]
self.log_error_called = False
def fake_error(msg, *args):
def fake_error(msg, *args, **kwargs):
self.log_error_called = True
self.assertIn(fake_xml, msg % args)
self.stubs.Set(nova.virt.libvirt.driver.LOG, 'error', fake_error)
self.stubs.Set(nova.virt.libvirt.guest.LOG, 'error', fake_error)
self.create_fake_libvirt_mock()
self.mox.ReplayAll()
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.stubs.Set(drvr, '_enable_hairpin', fake_enable_hairpin)
self.stubs.Set(nova.utils, 'execute', fake_execute)
self.stubs.Set(
nova.virt.libvirt.guest.Guest, 'get_interfaces',
fake_get_interfaces)
self.assertRaises(processutils.ProcessExecutionError,
drvr._create_domain,
@ -10584,7 +10575,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
_handler, cleanup, firewall_driver, create, plug_vifs):
domain = drvr._create_domain_and_network(self.context, 'xml',
instance, None, None)
self.assertEqual(0, create.call_args_list[0][1]['launch_flags'])
self.assertEqual(0, create.call_args_list[0][1]['pause'])
self.assertEqual(0, domain.resume.call_count)
def _test_create_with_network_events(self, neutron_failure=None,
@ -10628,10 +10619,10 @@ class LibvirtConnTestCase(test.NoDBTestCase):
power_on=power_on)
plug_vifs.assert_called_with(instance, vifs)
flag = self._get_launch_flags(drvr, vifs, power_on=power_on)
self.assertEqual(flag,
create.call_args_list[0][1]['launch_flags'])
if flag:
pause = self._get_pause_flag(drvr, vifs, power_on=power_on)
self.assertEqual(pause,
create.call_args_list[0][1]['pause'])
if pause:
domain.resume.assert_called_once_with()
if neutron_failure and CONF.vif_plugging_is_fatal:
cleanup.assert_called_once_with(self.context,
@ -10762,10 +10753,9 @@ class LibvirtConnTestCase(test.NoDBTestCase):
network_info)
prepare_instance_filter.assert_called_once_with(instance,
network_info)
flags = self._get_launch_flags(drvr, network_info)
create_domain.assert_called_once_with(fake_xml, instance=instance,
launch_flags=flags,
power_on=True)
pause = self._get_pause_flag(drvr, network_info)
create_domain.assert_called_once_with(
fake_xml, pause=pause, power_on=True)
self.assertEqual(mock_dom, domain)
def test_get_guest_storage_config(self):
@ -11787,7 +11777,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
self.assertEqual(powered_on, power_on)
self.assertTrue(vifs_already_plugged)
def fake_enable_hairpin(instance):
def fake_enable_hairpin():
pass
def fake_execute(*args, **kwargs):
@ -11811,7 +11801,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
fake_create_image)
self.stubs.Set(self.drvr, '_create_domain_and_network',
fake_create_domain_and_network)
self.stubs.Set(self.drvr, '_enable_hairpin',
self.stubs.Set(nova.virt.libvirt.guest.Guest, 'enable_hairpin',
fake_enable_hairpin)
self.stubs.Set(utils, 'execute', fake_execute)
fw = base_firewall.NoopFirewallDriver()
@ -11867,7 +11857,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
self.assertTrue(vifs_already_plugged)
return mock.MagicMock()
def fake_enable_hairpin(instance):
def fake_enable_hairpin():
pass
def fake_get_info(instance):
@ -11888,7 +11878,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
self.stubs.Set(self.drvr, 'firewall_driver', fw)
self.stubs.Set(self.drvr, '_create_domain_and_network',
fake_create_domain)
self.stubs.Set(self.drvr, '_enable_hairpin',
self.stubs.Set(nova.virt.libvirt.guest.Guest, 'enable_hairpin',
fake_enable_hairpin)
self.stubs.Set(self.drvr, 'get_info',
fake_get_info)

View File

@ -0,0 +1,139 @@
# Copyright 2010 OpenStack Foundation
# Copyright 2012 University Of Minho
# Copyright 2014-2015 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 mock
from oslo_config import cfg
from oslo_utils import encodeutils
from nova import context
from nova import test
from nova.tests.unit.virt.libvirt import fakelibvirt
from nova import utils
from nova.virt.libvirt import guest as libvirt_guest
from nova.virt.libvirt import host
host.libvirt = fakelibvirt
libvirt_guest.libvirt = fakelibvirt
CONF = cfg.CONF
class GuestTestCase(test.NoDBTestCase):
def setUp(self):
super(GuestTestCase, self).setUp()
self.useFixture(fakelibvirt.FakeLibvirtFixture())
self.host = host.Host("qemu:///system")
self.context = context.get_admin_context()
def test_repr(self):
domain = mock.MagicMock()
domain.ID.return_value = 99
domain.UUIDString.return_value = "UUID"
domain.name.return_value = "foo"
guest = libvirt_guest.Guest(domain)
self.assertEqual("<Guest 99 foo UUID>", repr(guest))
@mock.patch.object(fakelibvirt.Connection, 'defineXML')
def test_create(self, mock_define):
libvirt_guest.Guest.create("xml", self.host)
mock_define.assert_called_once_with("xml")
@mock.patch.object(fakelibvirt.Connection, 'defineXML')
def test_create_exception(self, mock_define):
mock_define.side_effect = test.TestingException
self.assertRaises(test.TestingException,
libvirt_guest.Guest.create,
"foo", self.host)
def test_launch(self):
domain = mock.MagicMock()
guest = libvirt_guest.Guest(domain)
guest.launch()
domain.createWithFlags.assert_called_once_with(0)
def test_launch_and_pause(self):
domain = mock.MagicMock()
guest = libvirt_guest.Guest(domain)
guest.launch(pause=True)
domain.createWithFlags.assert_called_once_with(
fakelibvirt.VIR_DOMAIN_START_PAUSED)
@mock.patch.object(encodeutils, 'safe_decode')
def test_launch_exception(self, mock_safe_decode):
domain = mock.MagicMock()
domain.createWithFlags.side_effect = test.TestingException
mock_safe_decode.return_value = "</xml>"
guest = libvirt_guest.Guest(domain)
self.assertRaises(test.TestingException, guest.launch)
self.assertEqual(1, mock_safe_decode.called)
@mock.patch.object(utils, 'execute')
@mock.patch.object(libvirt_guest.Guest, 'get_interfaces')
def test_enable_hairpin(self, mock_get_interfaces, mock_execute):
mock_get_interfaces.return_value = ["vnet0", "vnet1"]
guest = libvirt_guest.Guest(mock.MagicMock())
guest.enable_hairpin()
mock_execute.assert_has_calls([
mock.call(
'tee', '/sys/class/net/vnet0/brport/hairpin_mode',
run_as_root=True, process_input='1', check_exit_code=[0, 1]),
mock.call(
'tee', '/sys/class/net/vnet1/brport/hairpin_mode',
run_as_root=True, process_input='1', check_exit_code=[0, 1])])
@mock.patch.object(encodeutils, 'safe_decode')
@mock.patch.object(utils, 'execute')
@mock.patch.object(libvirt_guest.Guest, 'get_interfaces')
def test_enable_hairpin_exception(self, mock_get_interfaces,
mock_execute, mock_safe_decode):
mock_get_interfaces.return_value = ["foo"]
mock_execute.side_effect = test.TestingException('oops')
guest = libvirt_guest.Guest(mock.MagicMock())
self.assertRaises(test.TestingException, guest.enable_hairpin)
self.assertEqual(1, mock_safe_decode.called)
def test_get_interfaces(self):
dom = mock.MagicMock()
dom.XMLDesc.return_value = """
<domain>
<devices>
<interface type="network">
<target dev="vnet0"/>
</interface>
<interface type="network">
<target dev="vnet1"/>
</interface>
</devices>
</domain>"""
guest = libvirt_guest.Guest(dom)
self.assertEqual(["vnet0", "vnet1"], guest.get_interfaces())
def test_get_interfaces_exception(self):
dom = mock.MagicMock()
dom.XMLDesc.return_value = "<bad xml>"
guest = libvirt_guest.Guest(dom)
self.assertEqual([], guest.get_interfaces())

View File

@ -47,7 +47,6 @@ from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import excutils
from oslo_utils import importutils
from oslo_utils import strutils
@ -93,6 +92,7 @@ from nova.virt.libvirt import blockinfo
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt import dmcrypt
from nova.virt.libvirt import firewall as libvirt_firewall
from nova.virt.libvirt import guest as libvirt_guest
from nova.virt.libvirt import host
from nova.virt.libvirt import imagebackend
from nova.virt.libvirt import imagecache
@ -1403,8 +1403,8 @@ class LibvirtDriver(driver.ComputeDriver):
if state == power_state.RUNNING:
new_dom = self._create_domain(domain=virt_dom)
elif state == power_state.PAUSED:
new_dom = self._create_domain(domain=virt_dom,
launch_flags=libvirt.VIR_DOMAIN_START_PAUSED)
new_dom = self._create_domain(
domain=virt_dom, pause=True)
if new_dom is not None:
self._attach_pci_devices(new_dom,
pci_manager.get_instance_pci_devs(instance))
@ -2353,15 +2353,6 @@ class LibvirtDriver(driver.ComputeDriver):
def poll_rebooting_instances(self, timeout, instances):
pass
def _enable_hairpin(self, xml):
interfaces = self._get_interfaces(xml)
for interface in interfaces:
utils.execute('tee',
'/sys/class/net/%s/brport/hairpin_mode' % interface,
process_input='1',
run_as_root=True,
check_exit_code=[0, 1])
# NOTE(ilyaalekseyev): Implementation like in multinics
# for xenapi(tr3buchet)
def spawn(self, context, instance, image_meta, injected_files,
@ -4330,36 +4321,25 @@ class LibvirtDriver(driver.ComputeDriver):
self._create_domain_cleanup_lxc(instance)
def _create_domain(self, xml=None, domain=None,
instance=None, launch_flags=0, power_on=True):
power_on=True, pause=False):
"""Create a domain.
Either domain or xml must be passed in. If both are passed, then
the domain definition is overwritten from the xml.
"""
err = None
try:
if xml:
err = (_LE('Error defining a domain with XML: %s') %
encodeutils.safe_decode(xml, errors='ignore'))
domain = self._host.write_instance_config(xml)
if xml:
guest = libvirt_guest.Guest.create(xml, self._host)
else:
guest = libvirt_guest.Guest(domain)
if power_on:
err = _LE('Error launching a defined domain with XML: %s') \
% encodeutils.safe_decode(domain.XMLDesc(0),
errors='ignore')
domain.createWithFlags(launch_flags)
if power_on or pause:
guest.launch(pause=pause)
if not utils.is_neutron():
err = _LE('Error enabling hairpin mode with XML: %s') \
% encodeutils.safe_decode(domain.XMLDesc(0),
errors='ignore')
self._enable_hairpin(domain.XMLDesc(0))
except Exception:
with excutils.save_and_reraise_exception():
if err:
LOG.error(err)
if not utils.is_neutron():
guest.enable_hairpin()
return domain
# TODO(sahid): This method should return the Guest object
return guest._domain
def _neutron_failed_callback(self, event_name, instance):
LOG.error(_LE('Neutron Reported failure on event '
@ -4410,7 +4390,7 @@ class LibvirtDriver(driver.ComputeDriver):
else:
events = []
launch_flags = events and libvirt.VIR_DOMAIN_START_PAUSED or 0
pause = bool(events)
domain = None
try:
with self.virtapi.wait_for_instance_event(
@ -4424,9 +4404,7 @@ class LibvirtDriver(driver.ComputeDriver):
with self._lxc_disk_handler(instance, image_meta,
block_device_info, disk_info):
domain = self._create_domain(
xml, instance=instance,
launch_flags=launch_flags,
power_on=power_on)
xml, pause=pause, power_on=power_on)
self.firewall_driver.apply_instance_filter(instance,
network_info)
@ -4450,7 +4428,7 @@ class LibvirtDriver(driver.ComputeDriver):
raise exception.VirtualInterfaceCreateException()
# Resume only if domain has been paused
if launch_flags & libvirt.VIR_DOMAIN_START_PAUSED:
if pause:
domain.resume()
return domain

141
nova/virt/libvirt/guest.py Normal file
View File

@ -0,0 +1,141 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
# Copyright (c) 2010 Citrix Systems, Inc.
# Copyright (c) 2011 Piston Cloud Computing, Inc
# Copyright (c) 2012 University Of Minho
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# Copyright (c) 2015 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.
"""
Manages information about the guest.
This class encapsulates libvirt domain provides certain
higher level APIs around the raw libvirt API. These APIs are
then used by all the other libvirt related classes
"""
from lxml import etree
from oslo_log import log as logging
from oslo_utils import encodeutils
from oslo_utils import excutils
from oslo_utils import importutils
from nova.i18n import _LE
from nova import utils
libvirt = None
LOG = logging.getLogger(__name__)
class Guest(object):
def __init__(self, domain):
global libvirt
if libvirt is None:
libvirt = importutils.import_module('libvirt')
self._domain = domain
def __repr__(self):
return "<Guest %(id)d %(name)s %(uuid)s>" % {
'id': self.id,
'name': self.name,
'uuid': self.uuid
}
@property
def id(self):
return self._domain.ID()
@property
def uuid(self):
return self._domain.UUIDString()
@property
def name(self):
return self._domain.name()
@property
def _encoded_xml(self):
return encodeutils.safe_decode(self._domain.XMLDesc(0))
@classmethod
def create(cls, xml, host):
"""Create a new Guest
:param xml: XML definition of the domain to create
:param host: host.Host connection to define the guest on
:returns guest.Guest: Guest ready to be launched
"""
try:
# TODO(sahid): Host.write_instance_config should return
# an instance of Guest
domain = host.write_instance_config(xml)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Error defining a domain with XML: %s') %
encodeutils.safe_decode(xml))
return cls(domain)
def launch(self, pause=False):
"""Starts a created guest.
:param pause: Indicates whether to start and pause the guest
"""
flags = pause and libvirt.VIR_DOMAIN_START_PAUSED or 0
try:
return self._domain.createWithFlags(flags)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Error launching a defined domain '
'with XML: %s') %
self._encoded_xml, errors='ignore')
def enable_hairpin(self):
"""Enables hairpin mode for this guest."""
interfaces = self.get_interfaces()
try:
for interface in interfaces:
utils.execute(
'tee',
'/sys/class/net/%s/brport/hairpin_mode' % interface,
process_input='1',
run_as_root=True,
check_exit_code=[0, 1])
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE('Error enabling hairpin mode with XML: %s') %
self._encoded_xml, errors='ignore')
def get_interfaces(self):
"""Returns a list of all network interfaces for this domain."""
doc = None
try:
doc = etree.fromstring(self._encoded_xml)
except Exception:
return []
interfaces = []
nodes = doc.findall('./devices/interface/target')
for target in nodes:
interfaces.append(target.get('dev'))
return interfaces