import os import grpc import logging from ..common import format_volume_id from socket import AddressFamily from spdk.rpc.client import JSONRPCException from .device import DeviceManager, DeviceException from ..qmp import QMPClient, QMPError from ..proto import sma_pb2 from ..proto import virtio_blk_pb2 class VhostBlkDeviceManager(DeviceManager): def __init__(self, client): super().__init__('vhost_blk', 'virtio_blk', client) def init(self, config): self._buses = config.get('buses', []) try: if len(self._buses) != len(list({v['name']: v for v in self._buses}.values())): raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT, 'Duplicate PCI bridge names') except KeyError: raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT, 'PCI bridge name is missing') for bus in self._buses: bus['count'] = bus.get('count', 32) if bus['count'] < 0: raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT, 'Incorrect PCI bridge count') self._qmp_addr = (config.get('qmp_addr', '127.0.0.1'), config.get('qmp_port')) self._vhost_path = config.get('sock_path', '/var/tmp/') self._prefix = f'{self.protocol}' def owns_device(self, id): return id.startswith(self._prefix) def _find_controller(self, client, controller): try: ctrlrs = client.call('vhost_get_controllers') for ctrlr in ctrlrs: if ctrlr['ctrlr'] == controller: return ctrlr except JSONRPCException: logging.error('Failed to find vhost controller') return None def _qmp_delete_device(self, ctrlr): try: with QMPClient(self._qmp_addr, AddressFamily.AF_INET) as qclient: if self._find_pcidev(qclient, ctrlr) is not None: qclient.device_del({'id': ctrlr}, {'event': 'DEVICE_DELETED', 'data': {'device': ctrlr}}) except QMPError: logging.error('QMP: Failed to delete device') try: with QMPClient(self._qmp_addr, AddressFamily.AF_INET) as qclient: if (self._find_pcidev(qclient, ctrlr) is None and self._find_chardev(qclient, ctrlr) is not None): qclient.chardev_remove({'id': ctrlr}) return True except QMPError: logging.error('QMP: Failed to delete chardev') return False def _delete_controller(self, client, ctrlr): if self._find_controller(client, ctrlr) is None: return True try: return client.call('vhost_delete_controller', {'ctrlr': ctrlr}) except JSONRPCException: logging.error('Failed to delete controller') return False def _find_bdev(self, client, name): try: return client.call('bdev_get_bdevs', {'name': name})[0] except JSONRPCException: return None def _bdev_cmp(self, client, bdev1, bdev2): try: return self._find_bdev(client, bdev1)['name'] == self._find_bdev(client, bdev2)['name'] except KeyError: return False def _create_controller(self, client, ctrlr, volume_guid): nctrlr = self._find_controller(client, ctrlr) if nctrlr is not None: return self._bdev_cmp(client, nctrlr['backend_specific']['block']['bdev'], volume_guid) try: return client.call('vhost_create_blk_controller', {'ctrlr': ctrlr, 'dev_name': volume_guid}) except JSONRPCException: logging.error('Failed to create subsystem') return False def _find_pcidev(self, qclient, name): try: buses = qclient.query_pci()['return'] for bus in buses: for dev in bus['devices']: if 'pci_bridge' in dev: for pcidev in dev['pci_bridge']['devices']: if pcidev['qdev_id'] == name: return pcidev return None except QMPError: return None def _find_chardev(self, qclient, name): try: devs = qclient.query_chardev()['return'] for dev in devs: if dev['label'] == name: return dev return None except QMPError: return None def _qmp_add_device(self, ctrlr, phid, sock_path): # Find a bus that the physical_id maps to for bus in self._buses: if phid >= bus.get('count'): phid = phid - bus.get('count') else: break else: raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT, 'Invalid physical_id') try: with QMPClient(self._qmp_addr, AddressFamily.AF_INET) as qclient: if self._find_chardev(qclient, ctrlr) is None: qclient.chardev_add({ 'id': ctrlr, 'backend': { 'type': 'socket', 'data': { 'addr': { 'type': 'unix', 'data': { 'path': os.path.join(sock_path, ctrlr), } }, 'server': False, } }}) if self._find_pcidev(qclient, ctrlr) is None: qclient.device_add({'driver': 'vhost-user-blk-pci', 'chardev': ctrlr, 'bus': bus.get('name'), 'addr': hex(phid), 'id': ctrlr}) return True except QMPError: self._qmp_delete_device(ctrlr) logging.error('QMP: Failed to add device') return False def create_device(self, request): params = request.virtio_blk ctrlr = f'sma-{params.physical_id}' volume_guid = format_volume_id(request.volume.volume_id) if params.virtual_id != 0: raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT, 'Unsupported virtual_id value') with self._client() as client: rc = self._create_controller(client, ctrlr, volume_guid) if not rc: raise DeviceException(grpc.StatusCode.INTERNAL, 'Failed to create vhost device') rc = self._qmp_add_device(ctrlr, params.physical_id, self._vhost_path) if not rc: self._delete_controller(client, ctrlr) raise DeviceException(grpc.StatusCode.INTERNAL, 'Failed to create vhost device') return sma_pb2.CreateDeviceResponse(handle=f'{self.protocol}:{ctrlr}') def delete_device(self, request): with self._client() as client: ctrlr = request.handle[len(f'{self._prefix}:'):] if not self._qmp_delete_device(ctrlr): raise DeviceException(grpc.StatusCode.INTERNAL, 'Failed to delete vhost device') if not self._delete_controller(client, ctrlr): raise DeviceException(grpc.StatusCode.INTERNAL, 'Failed to delete vhost device')