2022-04-08 11:43:28 +00:00
|
|
|
import logging
|
2022-08-11 11:37:29 +00:00
|
|
|
import os
|
2022-04-08 11:43:28 +00:00
|
|
|
from socket import AddressFamily
|
2022-08-11 11:37:29 +00:00
|
|
|
|
|
|
|
import grpc
|
2022-04-08 11:43:28 +00:00
|
|
|
from spdk.rpc.client import JSONRPCException
|
2022-08-11 11:37:29 +00:00
|
|
|
|
|
|
|
from ..common import format_volume_id, volume_id_to_nguid
|
|
|
|
from ..proto import sma_pb2, virtio_blk_pb2
|
2022-04-08 11:43:28 +00:00
|
|
|
from ..qmp import QMPClient, QMPError
|
2022-08-11 11:37:29 +00:00
|
|
|
from ..volume import CryptoException, get_crypto_engine
|
|
|
|
from .device import DeviceException, DeviceManager
|
2022-04-08 11:43:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-08-11 11:37:29 +00:00
|
|
|
def _find_bdev(self, client, guid):
|
2022-04-08 11:43:28 +00:00
|
|
|
try:
|
2022-08-11 11:37:29 +00:00
|
|
|
bdev_name = get_crypto_engine().get_crypto_bdev(guid) or guid
|
|
|
|
return client.call('bdev_get_bdevs', {'name': bdev_name})[0]
|
|
|
|
except (JSONRPCException, CryptoException):
|
2022-04-08 11:43:28 +00:00
|
|
|
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:
|
2022-08-11 11:37:29 +00:00
|
|
|
bdev_name = get_crypto_engine().get_crypto_bdev(volume_guid) or volume_guid
|
2022-04-08 11:43:28 +00:00
|
|
|
return client.call('vhost_create_blk_controller',
|
2022-08-11 11:37:29 +00:00
|
|
|
{'ctrlr': ctrlr, 'dev_name': bdev_name})
|
2022-04-08 11:43:28 +00:00
|
|
|
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')
|