Skip to content
This repository was archived by the owner on Jan 22, 2026. It is now read-only.

Commit 1b38b7e

Browse files
committed
Add VirtualHostDevice classes, for attaching physical host devices to a guest.
1 parent 7868bcf commit 1b38b7e

8 files changed

Lines changed: 299 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<hostdev mode='subsystem' type='pci' managed='yes'>
2+
<source>
3+
<address domain='0' bus='21' slot='0' function='4'/>
4+
</source>
5+
</hostdev>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<hostdev mode='subsystem' type='usb' managed='yes'>
2+
<source>
3+
<vendor id='0x0781'/>
4+
<product id='0x5151'/>
5+
</source>
6+
</hostdev>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<hostdev mode='subsystem' type='usb' managed='yes'>
2+
<source>
3+
<address bus='1' device='4'/>
4+
</source>
5+
</hostdev>

tests/nodedev.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
1515
# MA 02110-1301 USA.
1616

17+
import tests
1718
import os.path
1819
import unittest
1920
import virtinst.NodeDeviceParser as nodeparse
21+
from virtinst import VirtualHostDevice
2022
import libvirt
2123

2224
conn = libvirt.open("test:///default")
@@ -33,6 +35,14 @@ def _testCompare(self, filename, vals):
3335
for attr in vals.keys():
3436
self.assertEqual(vals[attr], getattr(dev, attr))
3537

38+
def _testNode2DeviceCompare(self, nodefile, devfile, nodedev=None):
39+
devfile = os.path.join("tests/nodedev-xml/devxml", devfile)
40+
if not nodedev:
41+
nodedev = self._nodeDevFromFile(nodefile)
42+
43+
dev = VirtualHostDevice.device_from_node(conn, nodedev=nodedev)
44+
tests.diff_compare(dev.get_xml_config(), devfile)
45+
3646
def testSystemDevice(self):
3747
filename = "system.xml"
3848
vals = {"hw_vendor": "LENOVO", "hw_version": "ThinkPad T61",
@@ -150,5 +160,37 @@ def testSCSIDevice(self):
150160
"type": "disk"}
151161
self._testCompare(filename, vals)
152162

163+
164+
# NodeDevice 2 Device XML tests
165+
def testNodeDev2USB1(self):
166+
nodefile = "usbdev1.xml"
167+
devfile = "usbdev1.xml"
168+
self._testNode2DeviceCompare(nodefile, devfile)
169+
170+
def testNodeDev2USB2(self):
171+
nodefile = "usbdev1.xml"
172+
devfile = "usbdev2.xml"
173+
nodedev = self._nodeDevFromFile(nodefile)
174+
175+
# Force xml building to use bus, addr
176+
nodedev.product_id = None
177+
nodedev.vendor_id = None
178+
179+
self._testNode2DeviceCompare(nodefile, devfile, nodedev=nodedev)
180+
181+
def testNodeDev2PCI(self):
182+
nodefile = "pci1.xml"
183+
devfile = "pcidev.xml"
184+
self._testNode2DeviceCompare(nodefile, devfile)
185+
186+
def testNodeDevFail(self):
187+
nodefile = "usbbus.xml"
188+
devfile = ""
189+
190+
# This should exist, since usbbus is not a valid device to
191+
# pass to a guest.
192+
self.assertRaises(ValueError,
193+
self._testNode2DeviceCompare, nodefile, devfile)
194+
153195
if __name__ == "__main__":
154196
unittest.main()

virtinst/Guest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __init__(self, type=None, connection=None, hypervisorURI=None,
7979
self.disks = []
8080
self.nics = []
8181
self.sound_devs = []
82+
self.hostdevs = []
8283

8384
# Device lists to use/alter during install process
8485
self._install_disks = []
@@ -413,6 +414,12 @@ def _get_sound_xml(self):
413414
xml = _util.xml_append(xml, sound_dev.get_xml_config())
414415
return xml
415416

417+
def _get_hostdev_xml(self):
418+
xml = ""
419+
for hostdev in self.hostdevs:
420+
xml = _util.xml_append(xml, hostdev.get_xml_config())
421+
return xml
422+
416423
def _get_device_xml(self, install=True):
417424
xml = ""
418425

@@ -421,6 +428,7 @@ def _get_device_xml(self, install=True):
421428
xml = _util.xml_append(xml, self._get_input_xml())
422429
xml = _util.xml_append(xml, self._get_graphics_xml())
423430
xml = _util.xml_append(xml, self._get_sound_xml())
431+
xml = _util.xml_append(xml, self._get_hostdev_xml())
424432
return xml
425433

426434
def _get_features_xml(self):
@@ -582,12 +590,14 @@ def _prepare_install(self, meter):
582590
if self._installer.install_disk is not None:
583591
self._install_disks.append(self._installer.install_disk)
584592

585-
def _create_devices(self,progresscb):
593+
def _create_devices(self, progresscb):
586594
"""Ensure that devices are setup"""
587595
for disk in self._install_disks:
588596
disk.setup(progresscb)
589597
for nic in self._install_nics:
590598
nic.setup(self.conn)
599+
for hostdev in self.hostdevs:
600+
hostdev.setup()
591601

592602
def _do_install(self, consolecb, meter, removeOld=False, wait=True):
593603
vm = None

virtinst/NodeDeviceParser.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,25 @@ def is_nodedev_capable(conn):
330330

331331
return True
332332

333+
def is_pci_detach_capable(conn):
334+
"""
335+
Check if the passed libvirt connection support pci device Detach/Reset
336+
337+
@param conn: Connection to check
338+
@type conn: libvirt.virConnect
339+
340+
@rtype: C{bool}
341+
"""
342+
if not conn:
343+
return False
344+
if not isinstance(conn, libvirt.virConnect):
345+
raise ValueError(_("'conn' must be a virConnect instance."))
346+
347+
if dir(libvirt).count("virNodeDeviceDettach") == 0:
348+
return False
349+
350+
return True
351+
333352
def lookupNodeName(conn, name):
334353
"""
335354
Convert the passed libvirt node device name to a NodeDevice

virtinst/VirtualHostDevice.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#
2+
# Copyright 2009 Red Hat, Inc.
3+
# Cole Robinson <crobinso@redhat.com>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 2 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18+
# MA 02110-1301 USA.
19+
20+
import VirtualDevice
21+
import NodeDeviceParser
22+
import logging
23+
from virtinst import _virtinst as _
24+
25+
class VirtualHostDevice(VirtualDevice.VirtualDevice):
26+
27+
def device_from_node(conn, name=None, nodedev=None):
28+
"""
29+
Convert the passed libvirt node device name to a VirtualHostDevice
30+
instance, with proper error reporting.
31+
32+
@param conn: libvirt.virConnect instance to perform the lookup on
33+
@param name: libvirt node device name to lookup
34+
35+
@rtype: L{virtinst.VirtualHostDevice} instance
36+
"""
37+
38+
if not name and not nodedev:
39+
raise ValueError(_("'name' or 'nodedev' required."))
40+
41+
if nodedev:
42+
nodeinst = nodedev
43+
else:
44+
nodeinst = NodeDeviceParser.lookupNodeName(conn, name)
45+
46+
if isinstance(nodeinst, NodeDeviceParser.PCIDevice):
47+
return VirtualHostDevicePCI(conn, nodedev=nodeinst)
48+
elif isinstance(nodeinst, NodeDeviceParser.USBDevice):
49+
return VirtualHostDeviceUSB(conn, nodedev=nodeinst)
50+
elif isinstance(nodeinst, NodeDeviceParser.NetDevice):
51+
parentname = nodeinst.parent
52+
try:
53+
return VirtualHostDevice.device_from_node(conn,
54+
name=parentname)
55+
except:
56+
logging.exception("Fetching net parent device failed.")
57+
58+
raise ValueError(_("Node device type '%s' cannot be attached to "
59+
" guest.") % nodeinst.device_type)
60+
61+
device_from_node = staticmethod(device_from_node)
62+
63+
def __init__(self, conn, nodedev):
64+
"""
65+
@param conn: Connection the device/guest will be installed on
66+
@type conn: libvirt.virConnect
67+
@param nodedev: Optional NodeDevice instance for device being
68+
attached to the guest
69+
@type nodedev: L{virtinst.NodeDeviceParser.NodeDevice}
70+
"""
71+
VirtualDevice.VirtualDevice.__init__(self, conn)
72+
73+
self.mode = None
74+
self.type = None
75+
76+
self.managed = True
77+
78+
self._nodedev = nodedev
79+
80+
def _get_source_xml(self):
81+
raise NotImplementedError("Must be implemented in subclass")
82+
83+
def setup(self, conn = None):
84+
"""
85+
Perform DeviceDetach and DeviceReset calls if necessary
86+
87+
@param conn: libvirt virConnect instance to use (defaults to devices
88+
connection)
89+
"""
90+
raise NotImplementedError
91+
92+
def get_xml_config(self):
93+
xml = (" <hostdev mode='%s' type='%s' managed='%s'>\n" % \
94+
(self.mode, self.type, self.managed and "yes" or "no"))
95+
xml += " <source>\n"
96+
xml += self._get_source_xml()
97+
xml += " </source>\n"
98+
xml += " </hostdev>\n"
99+
return xml
100+
101+
102+
class VirtualHostDeviceUSB(VirtualHostDevice):
103+
104+
def __init__(self, conn, nodedev=None):
105+
VirtualHostDevice.__init__(self, conn, nodedev)
106+
107+
self.mode = "subsystem"
108+
self.type = "usb"
109+
110+
self.vendor = None
111+
self.product = None
112+
113+
self.bus = None
114+
self.device = None
115+
116+
self._set_from_nodedev(self._nodedev)
117+
118+
119+
def _set_from_nodedev(self, nodedev):
120+
if not nodedev:
121+
return
122+
123+
if not isinstance(nodedev, NodeDeviceParser.USBDevice):
124+
raise ValueError(_("'nodedev' must be a USBDevice instance."))
125+
126+
self.vendor = nodedev.vendor_id
127+
self.product = nodedev.product_id
128+
self.bus = nodedev.bus
129+
self.device = nodedev.device
130+
131+
def _get_source_xml(self):
132+
xml = ""
133+
if self.vendor and self.product:
134+
xml += " <vendor id='%s'/>\n" % self.vendor
135+
xml += " <product id='%s'/>\n" % self.product
136+
elif self.bus and self.device:
137+
xml += " <address bus='%s' device='%s'/>\n" % (self.bus,
138+
self.device)
139+
else:
140+
raise RuntimeError(_("'vendor' and 'product', or 'bus' and "
141+
" 'device' are required."))
142+
return xml
143+
144+
145+
def setup(self, conn = None):
146+
if not conn:
147+
conn = self.conn
148+
149+
# No libvirt api support for USB Detach/Reset yet
150+
return
151+
152+
class VirtualHostDevicePCI(VirtualHostDevice):
153+
154+
def __init__(self, conn, nodedev=None):
155+
VirtualHostDevice.__init__(self, conn, nodedev)
156+
157+
self.mode = "subsystem"
158+
self.type = "pci"
159+
160+
self.domain = "0x0"
161+
self.bus = None
162+
self.slot = None
163+
self.function = None
164+
165+
self._set_from_nodedev(self._nodedev)
166+
167+
168+
def _set_from_nodedev(self, nodedev):
169+
if not nodedev:
170+
return
171+
172+
if not isinstance(nodedev, NodeDeviceParser.PCIDevice):
173+
raise ValueError(_("'nodedev' must be a PCIDevice instance."))
174+
175+
self.domain = nodedev.domain
176+
self.bus = nodedev.bus
177+
self.slot = nodedev.slot
178+
self.function = nodedev.function
179+
180+
def _get_source_xml(self):
181+
if not (self.domain and self.bus and self.slot and self.function):
182+
raise RuntimeError(_("'domain', 'bus', 'slot', and 'function' "
183+
"must be specified."))
184+
185+
xml = " <address domain='%s' bus='%s' slot='%s' function='%s'/>\n"
186+
return xml % (self.domain, self.bus, self.slot, self.function)
187+
188+
def setup(self, conn = None):
189+
"""
190+
Perform DeviceDetach and DeviceReset calls if necessary
191+
192+
@param conn: libvirt virConnect instance to use (defaults to devices
193+
connection)
194+
"""
195+
if not conn:
196+
conn = self.conn
197+
198+
if not NodeDeviceParser.is_pci_detach_capable(conn):
199+
return
200+
201+
try:
202+
# Do this as a sanity check, so that we don't fail at domain
203+
# start time
204+
self._nodedev.deviceDetach()
205+
self._nodedev.deviceReset()
206+
except Exception, e:
207+
raise RuntimeError(_("Could not detach PCI device: %s" % str(e)))
208+
209+

virtinst/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def _virtinst(msg):
3232
from VirtualGraphics import VirtualGraphics
3333
from VirtualAudio import VirtualAudio
3434
from VirtualDisk import VirtualDisk, XenDisk
35+
from VirtualHostDevice import (VirtualHostDevice, VirtualHostDeviceUSB,
36+
VirtualHostDevicePCI)
3537
from FullVirtGuest import FullVirtGuest
3638
from ParaVirtGuest import ParaVirtGuest
3739
from DistroInstaller import DistroInstaller

0 commit comments

Comments
 (0)