devices_block.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This library is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU Lesser General Public
  10. # License as published by the Free Software Foundation; either
  11. # version 2.1 of the License, or (at your option) any later version.
  12. #
  13. # This library is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. # Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public
  19. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  20. from unittest import mock
  21. import jinja2
  22. import qubes.tests
  23. import qubes.ext.block
  24. domain_xml_template = '''
  25. <domain type='xen' id='9'>
  26. <name>test-vm</name>
  27. <uuid>00000000-0000-0000-0000-0000000000ae</uuid>
  28. <memory unit='KiB'>4096000</memory>
  29. <currentMemory unit='KiB'>409600</currentMemory>
  30. <vcpu placement='static'>8</vcpu>
  31. <os>
  32. <type arch='x86_64' machine='xenpv'>linux</type>
  33. <kernel>/var/lib/qubes/vm-kernels/4.4.55-11/vmlinuz</kernel>
  34. <initrd>/var/lib/qubes/vm-kernels/4.4.55-11/initramfs</initrd>
  35. <cmdline>root=/dev/mapper/dmroot ro nomodeset console=hvc0 rd_NO_PLYMOUTH rd.plymouth.enable=0 plymouth.enable=0 dyndbg=&quot;file drivers/xen/gntdev.c +p&quot; printk=8</cmdline>
  36. </os>
  37. <clock offset='utc' adjustment='reset'>
  38. <timer name='tsc' mode='native'/>
  39. </clock>
  40. <on_poweroff>destroy</on_poweroff>
  41. <on_reboot>destroy</on_reboot>
  42. <on_crash>destroy</on_crash>
  43. <devices>
  44. <disk type='block' device='disk'>
  45. <driver name='phy'/>
  46. <source dev='/var/lib/qubes/vm-templates/fedora-25/root.img:/var/lib/qubes/vm-templates/fedora-25/root-cow.img'/>
  47. <backingStore/>
  48. <script path='block-snapshot'/>
  49. <target dev='xvda' bus='xen'/>
  50. <readonly/>
  51. </disk>
  52. <disk type='block' device='disk'>
  53. <driver name='phy'/>
  54. <source dev='/var/lib/qubes/appvms/test-vm/private.img'/>
  55. <backingStore/>
  56. <target dev='xvdb' bus='xen'/>
  57. </disk>
  58. <disk type='block' device='disk'>
  59. <driver name='phy'/>
  60. <source dev='/var/lib/qubes/appvms/test-vm/volatile.img'/>
  61. <backingStore/>
  62. <target dev='xvdc' bus='xen'/>
  63. </disk>
  64. <disk type='block' device='disk'>
  65. <driver name='phy'/>
  66. <source dev='/var/lib/qubes/vm-kernels/4.4.55-11/modules.img'/>
  67. <backingStore/>
  68. <target dev='xvdd' bus='xen'/>
  69. <readonly/>
  70. </disk>
  71. {}
  72. <interface type='ethernet'>
  73. <mac address='00:16:3e:5e:6c:06'/>
  74. <ip address='10.137.1.8' family='ipv4'/>
  75. <script path='vif-route-qubes'/>
  76. <backenddomain name='sys-firewall'/>
  77. </interface>
  78. <console type='pty' tty='/dev/pts/0'>
  79. <source path='/dev/pts/0'/>
  80. <target type='xen' port='0'/>
  81. </console>
  82. </devices>
  83. </domain>
  84. '''
  85. class TestQubesDB(object):
  86. def __init__(self, data):
  87. self._data = data
  88. def read(self, key):
  89. return self._data.get(key, None)
  90. def list(self, prefix):
  91. return [key for key in self._data if key.startswith(prefix)]
  92. class TestApp(object):
  93. def __init__(self):
  94. #: jinja2 environment for libvirt XML templates
  95. self.env = jinja2.Environment(
  96. loader=jinja2.FileSystemLoader([
  97. 'templates',
  98. '/etc/qubes/templates',
  99. '/usr/share/qubes/templates',
  100. ]),
  101. undefined=jinja2.StrictUndefined)
  102. self.domains = {}
  103. class TestVM(object):
  104. def __init__(self, qdb, domain_xml=None, running=True, name='test-vm'):
  105. self.name = name
  106. self.untrusted_qdb = TestQubesDB(qdb)
  107. self.libvirt_domain = mock.Mock()
  108. self.is_running = lambda: running
  109. self.log = mock.Mock()
  110. self.app = TestApp()
  111. if domain_xml:
  112. self.libvirt_domain.configure_mock(**{
  113. 'XMLDesc.return_value': domain_xml
  114. })
  115. def __eq__(self, other):
  116. if isinstance(other, TestVM):
  117. return self.name == other.name
  118. class TC_00_Block(qubes.tests.QubesTestCase):
  119. def setUp(self):
  120. super().setUp()
  121. self.ext = qubes.ext.block.BlockDeviceExtension()
  122. def test_000_device_get(self):
  123. vm = TestVM({
  124. '/qubes-block-devices/sda': b'',
  125. '/qubes-block-devices/sda/desc': b'Test device',
  126. '/qubes-block-devices/sda/size': b'1024000',
  127. '/qubes-block-devices/sda/mode': b'w',
  128. })
  129. device_info = self.ext.device_get(vm, 'sda')
  130. self.assertIsInstance(device_info, qubes.ext.block.BlockDevice)
  131. self.assertEqual(device_info.backend_domain, vm)
  132. self.assertEqual(device_info.ident, 'sda')
  133. self.assertEqual(device_info.description, 'Test device')
  134. self.assertEqual(device_info._description, 'Test device')
  135. self.assertEqual(device_info.size, 1024000)
  136. self.assertEqual(device_info.mode, 'w')
  137. self.assertEqual(device_info.frontend_domain, None)
  138. self.assertEqual(device_info.device_node, '/dev/sda')
  139. def test_001_device_get_other_node(self):
  140. vm = TestVM({
  141. '/qubes-block-devices/mapper_dmroot': b'',
  142. '/qubes-block-devices/mapper_dmroot/desc': b'Test device',
  143. '/qubes-block-devices/mapper_dmroot/size': b'1024000',
  144. '/qubes-block-devices/mapper_dmroot/mode': b'w',
  145. })
  146. device_info = self.ext.device_get(vm, 'mapper_dmroot')
  147. self.assertIsInstance(device_info, qubes.ext.block.BlockDevice)
  148. self.assertEqual(device_info.backend_domain, vm)
  149. self.assertEqual(device_info.ident, 'mapper_dmroot')
  150. self.assertEqual(device_info.description, 'Test device')
  151. self.assertEqual(device_info._description, 'Test device')
  152. self.assertEqual(device_info.size, 1024000)
  153. self.assertEqual(device_info.mode, 'w')
  154. self.assertEqual(device_info.frontend_domain, None)
  155. self.assertEqual(device_info.device_node, '/dev/mapper/dmroot')
  156. def test_002_device_get_invalid_desc(self):
  157. vm = TestVM({
  158. '/qubes-block-devices/sda': b'',
  159. '/qubes-block-devices/sda/desc': b'Test device<>za\xc4\x87abc',
  160. '/qubes-block-devices/sda/size': b'1024000',
  161. '/qubes-block-devices/sda/mode': b'w',
  162. })
  163. device_info = self.ext.device_get(vm, 'sda')
  164. self.assertEqual(device_info.description, 'Test device__za__abc')
  165. def test_003_device_get_invalid_size(self):
  166. vm = TestVM({
  167. '/qubes-block-devices/sda': b'',
  168. '/qubes-block-devices/sda/desc': b'Test device',
  169. '/qubes-block-devices/sda/size': b'1024000abc',
  170. '/qubes-block-devices/sda/mode': b'w',
  171. })
  172. device_info = self.ext.device_get(vm, 'sda')
  173. self.assertEqual(device_info.size, 0)
  174. vm.log.warning.assert_called_once_with('Device sda has invalid size')
  175. def test_004_device_get_invalid_mode(self):
  176. vm = TestVM({
  177. '/qubes-block-devices/sda': b'',
  178. '/qubes-block-devices/sda/desc': b'Test device',
  179. '/qubes-block-devices/sda/size': b'1024000',
  180. '/qubes-block-devices/sda/mode': b'abc',
  181. })
  182. device_info = self.ext.device_get(vm, 'sda')
  183. self.assertEqual(device_info.mode, 'w')
  184. vm.log.warning.assert_called_once_with('Device sda has invalid mode')
  185. def test_005_device_get_none(self):
  186. vm = TestVM({
  187. '/qubes-block-devices/sda': b'',
  188. '/qubes-block-devices/sda/desc': b'Test device',
  189. '/qubes-block-devices/sda/size': b'1024000',
  190. '/qubes-block-devices/sda/mode': b'w',
  191. })
  192. device_info = self.ext.device_get(vm, 'sdb')
  193. self.assertIsNone(device_info)
  194. def test_010_devices_list(self):
  195. vm = TestVM({
  196. '/qubes-block-devices/sda': b'',
  197. '/qubes-block-devices/sda/desc': b'Test device',
  198. '/qubes-block-devices/sda/size': b'1024000',
  199. '/qubes-block-devices/sda/mode': b'w',
  200. '/qubes-block-devices/sdb': b'',
  201. '/qubes-block-devices/sdb/desc': b'Test device2',
  202. '/qubes-block-devices/sdb/size': b'2048000',
  203. '/qubes-block-devices/sdb/mode': b'r',
  204. })
  205. devices = sorted(list(self.ext.on_device_list_block(vm, '')))
  206. self.assertEqual(len(devices), 2)
  207. self.assertEqual(devices[0].backend_domain, vm)
  208. self.assertEqual(devices[0].ident, 'sda')
  209. self.assertEqual(devices[0].description, 'Test device')
  210. self.assertEqual(devices[0].size, 1024000)
  211. self.assertEqual(devices[0].mode, 'w')
  212. self.assertEqual(devices[1].backend_domain, vm)
  213. self.assertEqual(devices[1].ident, 'sdb')
  214. self.assertEqual(devices[1].description, 'Test device2')
  215. self.assertEqual(devices[1].size, 2048000)
  216. self.assertEqual(devices[1].mode, 'r')
  217. def test_011_devices_list_empty(self):
  218. vm = TestVM({})
  219. devices = sorted(list(self.ext.on_device_list_block(vm, '')))
  220. self.assertEqual(len(devices), 0)
  221. def test_012_devices_list_invalid_ident(self):
  222. vm = TestVM({
  223. '/qubes-block-devices/invalid ident': b'',
  224. '/qubes-block-devices/invalid+ident': b'',
  225. '/qubes-block-devices/invalid#': b'',
  226. })
  227. devices = sorted(list(self.ext.on_device_list_block(vm, '')))
  228. self.assertEqual(len(devices), 0)
  229. msg = 'test-vm vm\'s device path name contains unsafe characters. '\
  230. 'Skipping it.'
  231. self.assertEqual(vm.log.warning.mock_calls, [
  232. mock.call(msg),
  233. mock.call(msg),
  234. mock.call(msg),
  235. ])
  236. def test_020_find_unused_frontend(self):
  237. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  238. frontend = self.ext.find_unused_frontend(vm)
  239. self.assertEqual(frontend, 'xvdi')
  240. def test_022_find_unused_frontend2(self):
  241. disk = '''
  242. <disk type="block" device="disk">
  243. <driver name="phy" />
  244. <source dev="/dev/sda" />
  245. <target dev="xvdi" />
  246. <readonly />
  247. <backenddomain name="sys-usb" />
  248. </disk>
  249. '''
  250. vm = TestVM({}, domain_xml=domain_xml_template.format(disk))
  251. frontend = self.ext.find_unused_frontend(vm)
  252. self.assertEqual(frontend, 'xvdj')
  253. def test_030_list_attached_empty(self):
  254. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  255. devices = sorted(list(self.ext.on_device_list_attached(vm, '')))
  256. self.assertEqual(len(devices), 0)
  257. def test_031_list_attached(self):
  258. disk = '''
  259. <disk type="block" device="disk">
  260. <driver name="phy" />
  261. <source dev="/dev/sda" />
  262. <target dev="xvdi" />
  263. <readonly />
  264. <backenddomain name="sys-usb" />
  265. </disk>
  266. '''
  267. vm = TestVM({}, domain_xml=domain_xml_template.format(disk))
  268. vm.app.domains['test-vm'] = vm
  269. vm.app.domains['sys-usb'] = TestVM({}, name='sys-usb')
  270. devices = sorted(list(self.ext.on_device_list_attached(vm, '')))
  271. self.assertEqual(len(devices), 1)
  272. dev = devices[0][0]
  273. options = devices[0][1]
  274. self.assertEqual(dev.backend_domain, vm.app.domains['sys-usb'])
  275. self.assertEqual(dev.ident, 'sda')
  276. self.assertEqual(options['frontend-dev'], 'xvdi')
  277. self.assertEqual(options['read-only'], 'yes')
  278. def test_032_list_attached_dom0(self):
  279. disk = '''
  280. <disk type="block" device="disk">
  281. <driver name="phy" />
  282. <source dev="/dev/sda" />
  283. <target dev="xvdi" />
  284. </disk>
  285. '''
  286. vm = TestVM({}, domain_xml=domain_xml_template.format(disk))
  287. vm.app.domains['test-vm'] = vm
  288. vm.app.domains['sys-usb'] = TestVM({}, name='sys-usb')
  289. vm.app.domains['dom0'] = TestVM({}, name='dom0')
  290. vm.app.domains[0] = vm.app.domains['dom0']
  291. devices = sorted(list(self.ext.on_device_list_attached(vm, '')))
  292. self.assertEqual(len(devices), 1)
  293. dev = devices[0][0]
  294. options = devices[0][1]
  295. self.assertEqual(dev.backend_domain, vm.app.domains['dom0'])
  296. self.assertEqual(dev.ident, 'sda')
  297. self.assertEqual(options['frontend-dev'], 'xvdi')
  298. self.assertEqual(options['read-only'], 'no')
  299. def test_033_list_attached_cdrom(self):
  300. disk = '''
  301. <disk type="block" device="cdrom">
  302. <driver name="phy" />
  303. <source dev="/dev/sr0" />
  304. <target dev="xvdi" />
  305. <readonly />
  306. <backenddomain name="sys-usb" />
  307. </disk>
  308. '''
  309. vm = TestVM({}, domain_xml=domain_xml_template.format(disk))
  310. vm.app.domains['test-vm'] = vm
  311. vm.app.domains['sys-usb'] = TestVM({}, name='sys-usb')
  312. devices = sorted(list(self.ext.on_device_list_attached(vm, '')))
  313. self.assertEqual(len(devices), 1)
  314. dev = devices[0][0]
  315. options = devices[0][1]
  316. self.assertEqual(dev.backend_domain, vm.app.domains['sys-usb'])
  317. self.assertEqual(dev.ident, 'sr0')
  318. self.assertEqual(options['frontend-dev'], 'xvdi')
  319. self.assertEqual(options['read-only'], 'yes')
  320. self.assertEqual(options['devtype'], 'cdrom')
  321. def test_040_attach(self):
  322. back_vm = TestVM(name='sys-usb', qdb={
  323. '/qubes-block-devices/sda': b'',
  324. '/qubes-block-devices/sda/desc': b'Test device',
  325. '/qubes-block-devices/sda/size': b'1024000',
  326. '/qubes-block-devices/sda/mode': b'w',
  327. })
  328. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  329. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  330. self.ext.on_device_pre_attached_block(vm, '', dev, {})
  331. device_xml = (
  332. '<disk type="block" device="disk">\n'
  333. ' <driver name="phy" />\n'
  334. ' <source dev="/dev/sda" />\n'
  335. ' <target dev="xvdi" />\n'
  336. ' <backenddomain name="sys-usb" />\n'
  337. '</disk>')
  338. vm.libvirt_domain.attachDevice.assert_called_once_with(device_xml)
  339. def test_041_attach_frontend(self):
  340. back_vm = TestVM(name='sys-usb', qdb={
  341. '/qubes-block-devices/sda': b'',
  342. '/qubes-block-devices/sda/desc': b'Test device',
  343. '/qubes-block-devices/sda/size': b'1024000',
  344. '/qubes-block-devices/sda/mode': b'w',
  345. })
  346. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  347. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  348. self.ext.on_device_pre_attached_block(vm, '', dev,
  349. {'frontend-dev': 'xvdj'})
  350. device_xml = (
  351. '<disk type="block" device="disk">\n'
  352. ' <driver name="phy" />\n'
  353. ' <source dev="/dev/sda" />\n'
  354. ' <target dev="xvdj" />\n'
  355. ' <backenddomain name="sys-usb" />\n'
  356. '</disk>')
  357. vm.libvirt_domain.attachDevice.assert_called_once_with(device_xml)
  358. def test_042_attach_read_only(self):
  359. back_vm = TestVM(name='sys-usb', qdb={
  360. '/qubes-block-devices/sda': b'',
  361. '/qubes-block-devices/sda/desc': b'Test device',
  362. '/qubes-block-devices/sda/size': b'1024000',
  363. '/qubes-block-devices/sda/mode': b'w',
  364. })
  365. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  366. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  367. self.ext.on_device_pre_attached_block(vm, '', dev,
  368. {'read-only': 'yes'})
  369. device_xml = (
  370. '<disk type="block" device="disk">\n'
  371. ' <driver name="phy" />\n'
  372. ' <source dev="/dev/sda" />\n'
  373. ' <target dev="xvdi" />\n'
  374. ' <readonly />\n'
  375. ' <backenddomain name="sys-usb" />\n'
  376. '</disk>')
  377. vm.libvirt_domain.attachDevice.assert_called_once_with(device_xml)
  378. def test_043_attach_invalid_option(self):
  379. back_vm = TestVM(name='sys-usb', qdb={
  380. '/qubes-block-devices/sda': b'',
  381. '/qubes-block-devices/sda/desc': b'Test device',
  382. '/qubes-block-devices/sda/size': b'1024000',
  383. '/qubes-block-devices/sda/mode': b'w',
  384. })
  385. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  386. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  387. with self.assertRaises(qubes.exc.QubesValueError):
  388. self.ext.on_device_pre_attached_block(vm, '', dev,
  389. {'no-such-option': '123'})
  390. self.assertFalse(vm.libvirt_domain.attachDevice.called)
  391. def test_044_attach_invalid_option2(self):
  392. back_vm = TestVM(name='sys-usb', qdb={
  393. '/qubes-block-devices/sda': b'',
  394. '/qubes-block-devices/sda/desc': b'Test device',
  395. '/qubes-block-devices/sda/size': b'1024000',
  396. '/qubes-block-devices/sda/mode': b'w',
  397. })
  398. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  399. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  400. with self.assertRaises(qubes.exc.QubesValueError):
  401. self.ext.on_device_pre_attached_block(vm, '', dev,
  402. {'read-only': 'maybe'})
  403. self.assertFalse(vm.libvirt_domain.attachDevice.called)
  404. def test_045_attach_backend_not_running(self):
  405. back_vm = TestVM(name='sys-usb', running=False, qdb={
  406. '/qubes-block-devices/sda': b'',
  407. '/qubes-block-devices/sda/desc': b'Test device',
  408. '/qubes-block-devices/sda/size': b'1024000',
  409. '/qubes-block-devices/sda/mode': b'w',
  410. })
  411. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  412. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  413. with self.assertRaises(qubes.exc.QubesVMNotRunningError):
  414. self.ext.on_device_pre_attached_block(vm, '', dev, {})
  415. self.assertFalse(vm.libvirt_domain.attachDevice.called)
  416. def test_046_attach_ro_dev_rw(self):
  417. back_vm = TestVM(name='sys-usb', qdb={
  418. '/qubes-block-devices/sda': b'',
  419. '/qubes-block-devices/sda/desc': b'Test device',
  420. '/qubes-block-devices/sda/size': b'1024000',
  421. '/qubes-block-devices/sda/mode': b'r',
  422. })
  423. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  424. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  425. with self.assertRaises(qubes.exc.QubesValueError):
  426. self.ext.on_device_pre_attached_block(vm, '', dev,
  427. {'read-only': 'no'})
  428. self.assertFalse(vm.libvirt_domain.attachDevice.called)
  429. def test_047_attach_read_only_auto(self):
  430. back_vm = TestVM(name='sys-usb', qdb={
  431. '/qubes-block-devices/sda': b'',
  432. '/qubes-block-devices/sda/desc': b'Test device',
  433. '/qubes-block-devices/sda/size': b'1024000',
  434. '/qubes-block-devices/sda/mode': b'r',
  435. })
  436. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  437. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  438. self.ext.on_device_pre_attached_block(vm, '', dev, {})
  439. device_xml = (
  440. '<disk type="block" device="disk">\n'
  441. ' <driver name="phy" />\n'
  442. ' <source dev="/dev/sda" />\n'
  443. ' <target dev="xvdi" />\n'
  444. ' <readonly />\n'
  445. ' <backenddomain name="sys-usb" />\n'
  446. '</disk>')
  447. vm.libvirt_domain.attachDevice.assert_called_once_with(device_xml)
  448. def test_050_detach(self):
  449. back_vm = TestVM(name='sys-usb', qdb={
  450. '/qubes-block-devices/sda': b'',
  451. '/qubes-block-devices/sda/desc': b'Test device',
  452. '/qubes-block-devices/sda/size': b'1024000',
  453. '/qubes-block-devices/sda/mode': b'r',
  454. })
  455. device_xml = (
  456. '<disk type="block" device="disk">\n'
  457. ' <driver name="phy" />\n'
  458. ' <source dev="/dev/sda" />\n'
  459. ' <target dev="xvdi" />\n'
  460. ' <readonly />\n'
  461. ' <backenddomain name="sys-usb" />\n'
  462. '</disk>')
  463. vm = TestVM({}, domain_xml=domain_xml_template.format(device_xml))
  464. vm.app.domains['test-vm'] = vm
  465. vm.app.domains['sys-usb'] = TestVM({}, name='sys-usb')
  466. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  467. self.ext.on_device_pre_detached_block(vm, '', dev)
  468. vm.libvirt_domain.detachDevice.assert_called_once_with(device_xml)
  469. def test_051_detach_not_attached(self):
  470. back_vm = TestVM(name='sys-usb', qdb={
  471. '/qubes-block-devices/sda': b'',
  472. '/qubes-block-devices/sda/desc': b'Test device',
  473. '/qubes-block-devices/sda/size': b'1024000',
  474. '/qubes-block-devices/sda/mode': b'r',
  475. })
  476. device_xml = (
  477. '<disk type="block" device="disk">\n'
  478. ' <driver name="phy" />\n'
  479. ' <source dev="/dev/sda" />\n'
  480. ' <target dev="xvdi" />\n'
  481. ' <readonly />\n\n'
  482. ' <backenddomain name="sys-usb" />\n'
  483. '</disk>')
  484. vm = TestVM({}, domain_xml=domain_xml_template.format(''))
  485. vm.app.domains['test-vm'] = vm
  486. vm.app.domains['sys-usb'] = TestVM({}, name='sys-usb')
  487. dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
  488. self.ext.on_device_pre_detached_block(vm, '', dev)
  489. self.assertFalse(vm.libvirt_domain.detachDevice.called)