@@ -63,7 +63,7 @@ def main():
6363from unittest import mock
6464from copy import deepcopy
6565
66- from typing import List , Optional , Dict , Tuple
66+ from typing import List , Optional , Dict , Tuple , Any
6767
6868import qubesadmin .events
6969from qubesadmin .tests import QubesTest
@@ -120,6 +120,7 @@ def default_string(self):
120120 "debug" : Property ("False" , "bool" , True ),
121121 "default_dispvm" : Property ("default-dvm" , "vm" , False ),
122122 "default_user" : Property ("user" , "str" , True ),
123+ "devices_denied" : Property ("" , "str" , True ),
123124 "dns" : Property ("10.139.1.1 10.139.1.2" , "str" , True ),
124125 "gateway" : Property ("" , "str" , True ),
125126 "gateway6" : Property ("" , "str" , True ),
@@ -224,6 +225,18 @@ def default_string(self):
224225 "boot-mode.name.mode1" ,
225226 "boot-mode.name.mode2" ,
226227 "service.updates-proxy-setup" ,
228+ "qubes-vm-update-update-if-stale" ,
229+ "restart-after-update" ,
230+ "qubes-vm-update-hide-skipped" ,
231+ "template-name" ,
232+ "last-updates-check" ,
233+ "last-update" ,
234+ "prohibit-start" ,
235+ "qubes-vm-update-hide-updated" ,
236+ "qubes-vm-update-restart-servicevms" ,
237+ "qubes-vm-update-restart-system" ,
238+ "qubes-vm-update-restart-other" ,
239+ "qubes-vm-update-max-concurrency" ,
227240]
228241
229242POSSIBLE_TAGS = ["whonix-updatevm" , "anon-gateway" , "anon-vm" ]
@@ -648,72 +661,80 @@ def __init__(self, qapp):
648661 continue
649662
650663
664+ # pylint: disable=too-many-positional-arguments
651665class MockDevice :
652666 """helper for adding a device to a qubes test instance"""
653667
654668 def __init__ (
655669 self ,
656670 qapp : QubesTest ,
657671 dev_class : str ,
658- description : str ,
659- dev_id : str ,
672+ device_id : str ,
660673 backend_vm : str ,
674+ port : str ,
675+ product : str ,
676+ vendor : str ,
661677 attached : Optional [str ] = None ,
678+ assigned : Optional [List [Tuple [str , str , list [Any ] | None ]]] = None ,
662679 ):
663680 """
664681 :param qapp: QubesTest object
665682 :param dev_class: block / mic / usb
666- :param description: device description
667- :param dev_id: dev id (such as sda, 2-1, mic)
683+ :param device_id: device_id
684+ :param port: port
668685 :param backend_vm: name of the vm providing this device
669- :param attached: name of the qube to which the device is attached,
670- if any
686+ :param attached: name of the qube to which the device is
687+ currently attached,
688+ :param assigned: list of the qubes to which the device is currently
689+ assigned, tupled with mode ('ask-to-attach' or 'auto-attach')
671690 """
672691 self .qapp = qapp
673692 self .dev_class = dev_class
674- self .description = description
675- self .dev_id = dev_id
693+ self .device_id = device_id
694+ self .port = port
676695 self .backend_vm = backend_vm
677696 self .attached = attached
697+ self .assigned = assigned
698+ self .product = product
699+ self .vendor = vendor
700+
701+ self .interface = self .device_id .split (":" )[- 1 ]
678702
679703 self .update_calls ()
680704
681705 def device_string (self ):
682- if self .dev_class == "block" :
683- port , d_id = self .dev_id .split (":" , 1 )
684- return (
685- f"{ self .dev_id } device_id='{ d_id } ' port_id='{ port } ' "
686- f"devclass='block' backend_domain='{ self .backend_vm } ' "
687- f"serial='root/test.img' manufacturer='{ self .description } ' "
688- "interfaces='b******'\n "
689- )
690- if self .dev_class == "pci" :
691- _ , d_id = self .dev_id .split (":" )
692- port = self .dev_id .replace (":" , "_" )
693- return (
694- f"{ port } device_id='{ d_id } ' "
695- f"port_id='{ port } ' devclass='pci' "
696- f"product='{ self .description } ' vendor='{ self .description } ' "
697- f"interfaces='p0c0500' backend_domain='{ self .backend_vm } '\n "
698- )
699- return f"{ self .dev_id } description='{ self .description } '\n "
706+ return (
707+ f"{ self .port } device_id='{ self .device_id } ' "
708+ f"port_id='{ self .port } ' devclass='{ self .dev_class } ' "
709+ f"product='{ self .product } ' vendor='{ self .vendor } ' "
710+ f"interfaces='{ self .interface } ' "
711+ f"backend_domain='{ self .backend_vm } '\n "
712+ )
700713
701714 def attachment_string (self ):
702- if ":" in self .dev_id :
703- port , _ = self .dev_id .split (":" )
704- elif self .dev_class == "mic" :
705- port = "mic"
706- else :
707- port = "00"
708715 string = (
709- f"{ self .backend_vm } +{ self .dev_id } "
710- f"port_id='{ port } ' devclass='{ self .dev_class } ' "
711- f"backend_domain='{ self .backend_vm } ' mode='required ' "
716+ f"{ self .backend_vm } +{ self .port } "
717+ f"port_id='{ self . port } ' devclass='{ self .dev_class } ' "
718+ f"backend_domain='{ self .backend_vm } ' mode='manual ' "
712719 f"frontend_domain='{ self .attached } '"
713720 )
714721 string += "\n "
715722 return string
716723
724+ def assignment_string (self , vm , mode , opts ):
725+ string = (
726+ f"{ self .backend_vm } +{ self .port } device_id='"
727+ f"{ self .device_id } ' "
728+ f"port_id='{ self .port } ' devclass='{ self .dev_class } ' "
729+ f"backend_domain='{ self .backend_vm } ' mode='{ mode } ' "
730+ f"frontend_domain='{ vm } '"
731+ )
732+ if opts :
733+ for opt in opts :
734+ string += f" _{ opt } ='True'"
735+ string += "\n "
736+ return string
737+
717738 def update_calls (self ):
718739 # modify call
719740 current_response = self .qapp .expected_calls [
@@ -740,22 +761,44 @@ def update_calls(self):
740761 current_response = self .qapp .expected_calls [
741762 (
742763 self .attached ,
743- f"admin.vm.device.{ self .dev_class } .Assigned " ,
764+ f"admin.vm.device.{ self .dev_class } .Attached " ,
744765 None ,
745766 None ,
746767 )
747768 ]
748769 self .qapp .expected_calls [
749770 (
750771 self .attached ,
751- f"admin.vm.device.{ self .dev_class } .Assigned " ,
772+ f"admin.vm.device.{ self .dev_class } .Attached " ,
752773 None ,
753774 None ,
754775 )
755776 ] = (
756777 current_response + self .attachment_string ().encode ()
757778 )
758779
780+ if self .assigned :
781+ for vm , mode , opts in self .assigned :
782+ current_response = self .qapp .expected_calls [
783+ (
784+ vm ,
785+ f"admin.vm.device.{ self .dev_class } .Assigned" ,
786+ None ,
787+ None ,
788+ )
789+ ]
790+ self .qapp .expected_calls [
791+ (
792+ vm ,
793+ f"admin.vm.device.{ self .dev_class } .Assigned" ,
794+ None ,
795+ None ,
796+ )
797+ ] = (
798+ current_response
799+ + self .assignment_string (vm , mode , opts ).encode ()
800+ )
801+
759802
760803class QubesTestWrapper (QubesTest ):
761804 def __init__ (self ):
@@ -987,20 +1030,78 @@ def __init__(self):
9871030 self ._devices = [
9881031 MockDevice (
9891032 self ,
990- "mic" ,
991- "Internal Microphone" ,
992- "mic" ,
993- "dom0" ,
1033+ dev_class = "mic" ,
1034+ device_id = "dom0:mic::m000000" ,
1035+ backend_vm = "dom0" ,
1036+ port = "mic" ,
1037+ product = "Internal Mic" ,
1038+ vendor = "ACME" ,
9941039 attached = "test-blue" ,
9951040 ),
9961041 # the usb stick appears as multiple devices, as they are wont to
997- MockDevice (self , "usb" , "My USB Drive" , "2-1" , "sys-usb" ),
998- MockDevice (self , "block" , " ()" , "sda::0" , "sys-usb" ),
999- MockDevice (self , "block" , "(USB DISK)" , "sda1::1" , "sys-usb" ),
1000- MockDevice (self , "usb" , "Internal Camera" , "2-10" , "sys-usb" ),
1001- MockDevice (self , "pci" , "Host bridge" , "00:00.0" , "dom0" ),
1002- MockDevice (self , "pci" , "PCI Bridge" , "00:02.2" , "dom0" ),
1003- MockDevice (self , "pci" , "USB Controller" , "00:03.2" , "dom0" ),
1042+ MockDevice (
1043+ self ,
1044+ dev_class = "usb" ,
1045+ device_id = "1d6b:0104:CAFEBABE:u030101u300000" ,
1046+ backend_vm = "sys-usb" ,
1047+ port = "2-1" ,
1048+ product = "My USB Drive" ,
1049+ vendor = "SCP Foundation" ,
1050+ ),
1051+ MockDevice (
1052+ self ,
1053+ dev_class = "block" ,
1054+ device_id = "1d6b:0104:CAFEBABE:b123456" ,
1055+ backend_vm = "sys-usb" ,
1056+ port = "sda" ,
1057+ product = "(USB)" ,
1058+ vendor = "SCP Foundation" ,
1059+ ),
1060+ MockDevice (
1061+ self ,
1062+ dev_class = "block" ,
1063+ device_id = "1d6b:0104:CAFEBABE:b123456" ,
1064+ backend_vm = "sys-usb" ,
1065+ port = "sda" ,
1066+ product = "()" ,
1067+ vendor = "SCP Foundation" ,
1068+ ),
1069+ MockDevice (
1070+ self ,
1071+ dev_class = "usb" ,
1072+ device_id = "04f2:b684:01.00.00:u0e0101u0e0201" ,
1073+ backend_vm = "sys-usb" ,
1074+ port = "2-7" ,
1075+ product = "Internal Camera" ,
1076+ vendor = "Saruman Industries" ,
1077+ ),
1078+ MockDevice (
1079+ self ,
1080+ dev_class = "pci" ,
1081+ device_id = "0x8086:0x4621::p060000" ,
1082+ backend_vm = "dom0" ,
1083+ port = "00_00.0" ,
1084+ product = "PCI Bridge" ,
1085+ vendor = "Unnamed Industries" ,
1086+ ),
1087+ MockDevice (
1088+ self ,
1089+ dev_class = "pci" ,
1090+ device_id = "0x8086:0x51e9::p0c8000" ,
1091+ backend_vm = "dom0" ,
1092+ port = "00_02.0" ,
1093+ product = "I2C Controller" ,
1094+ vendor = "Unnamed Industries" ,
1095+ ),
1096+ MockDevice (
1097+ self ,
1098+ dev_class = "pci" ,
1099+ device_id = "0x8086:0x461e::p0c0330" ,
1100+ backend_vm = "dom0" ,
1101+ port = "00_03.0" ,
1102+ product = "I2C Controller" ,
1103+ vendor = "Unnamed Industries" ,
1104+ ),
10041105 ]
10051106
10061107 self .update_vm_calls ()
0 commit comments