Skip to content

Commit ace4332

Browse files
committed
q-dev: fixes
1 parent ed25713 commit ace4332

File tree

4 files changed

+72
-61
lines changed

4 files changed

+72
-61
lines changed

doc/manpages/qvm-device.rst

+10-2
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,11 @@ Assign the device with *DEVICE_ID* from *BACKEND_DOMAIN* to the domain *VMNAME*
116116

117117
Assign device but always ask before auto-attachment.
118118

119-
.. option:: --port
119+
.. option:: --port, --only-port
120120

121121
Ignore device presented identity and attach any device connected to the given port number.
122122

123-
.. option:: --device
123+
.. option:: --device, --only-device
124124

125125
Ignore current port identity and attach this device connected to any port.
126126

@@ -134,6 +134,14 @@ unassign
134134
Remove assignment of device with *BACKEND_DOMAIN:DEVICE_ID* from domain *VMNAME*.
135135
If no device is given, remove assignments of all *DEVICE_CLASS* devices.
136136

137+
.. option:: --port, --only-port
138+
139+
Remove port assignment.
140+
141+
.. option:: --device, --only-device
142+
143+
Remove device identity based assignment.
144+
137145
aliases: u
138146

139147
info

qubesadmin/device_protocol.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def parse_basic_device_properties(
165165
the expected values.
166166
"""
167167
expected = expected_device.port
168-
exp_vm_name = expected.backend_domain.name
168+
exp_vm_name = expected.backend_name
169169
if properties.get('backend_domain', exp_vm_name) != exp_vm_name:
170170
raise UnexpectedDeviceProperty(
171171
f"Got device exposed by {properties['backend_domain']}"
@@ -284,7 +284,7 @@ def __str__(self):
284284

285285
@property
286286
def backend_name(self) -> str:
287-
if self.backend_domain is not None:
287+
if self.backend_domain not in (None, "*"):
288288
return self.backend_domain.name
289289
return "*"
290290

@@ -360,7 +360,7 @@ def __init__(
360360
port: Optional[Port] = None,
361361
device_id: Optional[str] = None,
362362
):
363-
# TODO! one of them cannot be None
363+
assert port is not None or device_id is not None
364364
self.port: Optional[Port] = port
365365
self._device_id = device_id
366366

@@ -517,8 +517,8 @@ def _parse(
517517
else:
518518
identity = representation
519519
port_id, _, devid = identity.partition(':')
520-
if devid in ('', '*'):
521-
devid = '*'
520+
if devid == '':
521+
devid = None
522522
return cls(
523523
Port(backend_domain=backend, port_id=port_id, devclass=devclass),
524524
device_id=devid
@@ -1054,7 +1054,7 @@ def __init__(
10541054
mode: Union[str, AssignmentMode] = "manual",
10551055
):
10561056
if isinstance(device, DeviceInfo):
1057-
device = VirtualDevice(device.port, device._device_id)
1057+
device = VirtualDevice(device.port, device.device_id)
10581058
self.virtual_device = device
10591059
self.__options = options or {}
10601060
if isinstance(mode, AssignmentMode):
@@ -1164,7 +1164,7 @@ def attached(self) -> bool:
11641164
Returns False if device is attached to different domain
11651165
"""
11661166
for device in self.devices:
1167-
if device.attachment == self.frontend_domain:
1167+
if device.attachment and device.attachment == self.frontend_domain:
11681168
return True
11691169
return False
11701170

@@ -1256,7 +1256,7 @@ def _deserialize(
12561256
def matches(self, device: VirtualDevice) -> bool:
12571257
if self.devclass != device.devclass:
12581258
return False
1259-
if self.backend_domain != '*' and self.backend_domain != device.backend_domain:
1259+
if self.backend_domain != device.backend_domain:
12601260
return False
12611261
if self.port_id != '*' and self.port_id != device.port_id:
12621262
return False

qubesadmin/tests/tools/qvm_device.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ def test_012_attach_invalid(self):
199199
qubesadmin.tools.qvm_device.main(
200200
['testclass', 'attach', '-p', 'test-vm2', 'dev1'],
201201
app=self.app)
202-
self.assertIn('expected a backend vm & device id',
202+
self.assertIn(
203+
'expected a backend vm, port id and [optional] device id',
203204
stderr.getvalue())
204205
self.assertAllCalled()
205206

@@ -221,7 +222,7 @@ def test_014_attach_invalid_backend(self):
221222
qubesadmin.tools.qvm_device.main(
222223
['testclass', 'attach', '-p', 'test-vm2', 'no-such-vm:dev3'],
223224
app=self.app)
224-
self.assertIn('no backend vm',
225+
self.assertIn('no such backend vm!',
225226
stderr.getvalue())
226227
self.assertAllCalled()
227228

@@ -311,7 +312,8 @@ def test_033_assign_invalid(self):
311312
qubesadmin.tools.qvm_device.main(
312313
['testclass', 'assign', 'test-vm2', 'dev1'],
313314
app=self.app)
314-
self.assertIn('expected a backend vm & device id',
315+
self.assertIn(
316+
'expected a backend vm, port id and [optional] device id',
315317
stderr.getvalue())
316318
self.assertAllCalled()
317319

@@ -332,14 +334,14 @@ def test_035_assign_invalid_backend(self):
332334
qubesadmin.tools.qvm_device.main(
333335
['testclass', 'assign', 'test-vm2', 'no-such-vm:dev3'],
334336
app=self.app)
335-
self.assertIn('no backend vm', stderr.getvalue())
337+
self.assertIn('no such backend vm!', stderr.getvalue())
336338
self.assertAllCalled()
337339

338340
def test_040_unassign(self):
339341
""" Test unassign action """
340342
self.app.expected_calls[
341343
('test-vm2', 'admin.vm.device.testclass.Unassign',
342-
'test-vm1+dev1:*', None)] = b'0\0'
344+
'test-vm1+dev1:dead:beef:babe:u0123456', None)] = b'0\0'
343345
qubesadmin.tools.qvm_device.main(
344346
['testclass', 'unassign', 'test-vm2', 'test-vm1:dev1'], app=self.app)
345347
self.assertAllCalled()
@@ -350,7 +352,7 @@ def test_041_unassign_unknown(self):
350352
('test-vm2', 'admin.vm.device.testclass.Unassign',
351353
'test-vm1+dev7:*', None)] = b'0\0'
352354
qubesadmin.tools.qvm_device.main(
353-
['testclass', 'unassign', 'test-vm2', 'test-vm1:dev7'], app=self.app)
355+
['testclass', 'unassign', 'test-vm2', 'test-vm1:dev7', '--port'], app=self.app)
354356
self.assertAllCalled()
355357

356358
def test_042_unassign_all(self):

qubesadmin/tools/qvm_device.py

+46-45
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import qubesadmin.tools
3434
import qubesadmin.device_protocol
3535
from qubesadmin.device_protocol import (Port, DeviceInfo, UnknownDevice,
36-
DeviceAssignment)
36+
DeviceAssignment, VirtualDevice)
3737

3838

3939
def prepare_table(dev_list):
@@ -206,7 +206,7 @@ def detach_device(args):
206206
if args.device:
207207
device = args.device
208208
# ignore device id, detach any device
209-
device.device_id = None
209+
device.device_id = '*'
210210
assignment = DeviceAssignment(device)
211211
vm.devices[args.devclass].detach(assignment)
212212
else:
@@ -221,9 +221,10 @@ def assign_device(args):
221221
vm = args.domains[0]
222222
device = args.device
223223
if args.only_port:
224-
device.device_id = None
224+
device = device.clone(device_id="*")
225225
if args.only_device:
226-
device.port = Port(None, None, device.devclass)
226+
device = device.clone(
227+
port=Port(device.backend_domain, "*", device.devclass))
227228
options = dict(opt.split('=', 1) for opt in args.option or [])
228229
if args.ro:
229230
options['read-only'] = 'yes'
@@ -233,12 +234,10 @@ def assign_device(args):
233234
mode = 'required'
234235
if args.ask:
235236
mode = 'ask-to-attach'
236-
assignment = DeviceAssignment(
237-
device,
238-
mode=mode,
239-
options=options
240-
)
237+
assignment = DeviceAssignment(device, mode=mode, options=options)
241238
vm.devices[args.devclass].assign(assignment)
239+
# retrieve current port info
240+
assignment = DeviceAssignment(args.device, mode=mode, options=options)
242241
if vm.is_running() and not assignment.attached and not args.quiet:
243242
print("Assigned. To attach you can now restart domain or run: \n"
244243
f"\tqvm-{assignment.devclass} attach {vm} "
@@ -252,10 +251,12 @@ def unassign_device(args):
252251
vm = args.domains[0]
253252
if args.device:
254253
device = args.device
255-
# ignore device id, detach any device
256-
device.device_id = None
257-
assignment = DeviceAssignment(
258-
device, frontend_domain=vm)
254+
if args.only_port:
255+
device = device.clone(device_id="*")
256+
if args.only_device:
257+
device = device.clone(
258+
port=Port(device.backend_domain, '*', device.devclass))
259+
assignment = DeviceAssignment(device, frontend_domain=vm)
259260
_unassign_and_show_message(assignment, vm, args)
260261
else:
261262
for assignment in vm.devices[args.devclass].get_assigned_devices():
@@ -306,11 +307,11 @@ def init_list_parser(sub_parsers):
306307

307308
class DeviceAction(qubesadmin.tools.QubesAction):
308309
""" Action for argument parser that gets the
309-
:py:class:``qubesadmin.device.Device`` from a
310-
BACKEND:DEVICE_ID string.
310+
:py:class:``qubesadmin.device_protocol.VirtualDevice`` from a
311+
BACKEND:PORT_ID:DEVICE_ID string.
311312
""" # pylint: disable=too-few-public-methods
312313

313-
def __init__(self, help='A backend & device id combination',
314+
def __init__(self, help='A backend, port & device id combination',
314315
required=True, allow_unknown=False, **kwargs):
315316
# pylint: disable=redefined-builtin
316317
self.allow_unknown = allow_unknown
@@ -322,34 +323,36 @@ def __call__(self, parser, namespace, values, option_string=None):
322323

323324
def parse_qubes_app(self, parser, namespace):
324325
app = namespace.app
325-
backend_device_id = getattr(namespace, self.dest)
326+
representation = getattr(namespace, self.dest)
326327
devclass = namespace.devclass
327-
if backend_device_id is None:
328+
if representation is None:
328329
return
329330

330331
try:
331-
vmname, port_id = backend_device_id.split(':', 1)
332-
vm = None
333332
try:
334-
vm = app.domains[vmname]
333+
dev = VirtualDevice.from_str(
334+
representation, devclass, app.domains)
335335
except KeyError:
336-
parser.error_runtime("no backend vm {!r}".format(vmname))
336+
parser.error_runtime("no such backend vm!")
337+
return
337338

338339
try:
339-
dev = vm.devices[devclass][port_id]
340-
if not self.allow_unknown and \
341-
isinstance(dev, UnknownDevice):
342-
raise KeyError(port_id)
340+
# load device info
341+
_dev = dev.backend_domain.devices[devclass][dev.port_id]
342+
if dev._device_id is None or dev.device_id == _dev.device_id:
343+
dev = _dev
344+
if not self.allow_unknown and isinstance(dev, UnknownDevice):
345+
raise KeyError(dev.port_id)
343346
except KeyError:
344347
parser.error_runtime(
345-
f"backend vm {vmname!r} doesn't expose "
346-
f"{devclass} device {port_id!r}")
347-
dev = UnknownDevice(Port(vm, port_id, devclass))
348+
f"backend vm {dev.backend_name} doesn't expose "
349+
f"{devclass} device {dev.port_id!r}")
350+
dev = UnknownDevice.from_device(dev)
348351
setattr(namespace, self.dest, dev)
349352
except ValueError:
350353
parser.error(
351-
'expected a backend vm & device id combination like foo:bar '
352-
'got %s' % backend_device_id)
354+
'expected a backend vm, port id and [optional] device id '
355+
f'combination like foo:bar[:baz] got {representation}')
353356

354357

355358
def get_parser(device_class=None):
@@ -455,20 +458,18 @@ def get_parser(device_class=None):
455458
"be required to the qube's startup and then"
456459
" automatically attached)")
457460

458-
id_parser = assign_parser.add_mutually_exclusive_group()
459-
id_parser.add_argument('--port',
460-
dest='only_port',
461-
action='store_true',
462-
default=False,
463-
help="Ignore device presented identity and "
464-
"attach later any device connected "
465-
"to the given port")
466-
id_parser.add_argument('--device',
467-
dest='only_device',
468-
action='store_true',
469-
default=False,
470-
help="Ignore current port identity and "
471-
"attach this device connected to any port")
461+
for pars in (assign_parser, unassign_parser):
462+
id_parser = pars.add_mutually_exclusive_group()
463+
id_parser.add_argument('--port', '--only-port',
464+
dest='only_port',
465+
action='store_true',
466+
default=False,
467+
help="Ignore device presented identity")
468+
id_parser.add_argument('--device', '--only-device',
469+
dest='only_device',
470+
action='store_true',
471+
default=False,
472+
help="Ignore current port identity")
472473
attach_parser.set_defaults(func=attach_device)
473474
detach_parser.set_defaults(func=detach_device)
474475
assign_parser.set_defaults(func=assign_device)

0 commit comments

Comments
 (0)