2022-11-13 02:15:47 +00:00
|
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
# Copyright (C) 2022 Intel Corporation.
|
|
|
|
# All rights reserved.
|
|
|
|
|
2022-01-05 09:32:09 +00:00
|
|
|
from concurrent import futures
|
|
|
|
from contextlib import contextmanager
|
|
|
|
from multiprocessing import Lock
|
|
|
|
import grpc
|
|
|
|
import logging
|
|
|
|
from .device import DeviceException
|
2022-04-27 12:30:00 +00:00
|
|
|
from .volume import VolumeException, VolumeManager
|
2022-08-01 06:15:33 +00:00
|
|
|
from .volume import crypto
|
2022-08-03 02:57:06 +00:00
|
|
|
from .volume import crypto_bdev
|
2022-01-05 09:32:09 +00:00
|
|
|
from .proto import sma_pb2 as pb2
|
|
|
|
from .proto import sma_pb2_grpc as pb2_grpc
|
|
|
|
|
|
|
|
|
|
|
|
class StorageManagementAgent(pb2_grpc.StorageManagementAgentServicer):
|
2022-04-27 12:30:00 +00:00
|
|
|
def __init__(self, config, client):
|
2022-05-17 12:40:19 +00:00
|
|
|
addr, port = config['address'], config['port']
|
2022-01-05 09:32:09 +00:00
|
|
|
self._devices = {}
|
|
|
|
self._server = grpc.server(futures.ThreadPoolExecutor(max_workers=1))
|
|
|
|
self._server.add_insecure_port(f'{addr}:{port}')
|
2022-05-16 10:28:29 +00:00
|
|
|
self._volume_mgr = VolumeManager(client, config['discovery_timeout'],
|
|
|
|
config['volume_cleanup_period'])
|
2022-01-05 09:32:09 +00:00
|
|
|
pb2_grpc.add_StorageManagementAgentServicer_to_server(self, self._server)
|
|
|
|
|
|
|
|
def _grpc_method(f):
|
|
|
|
def wrapper(self, request, context):
|
|
|
|
logging.debug(f'{f.__name__}\n{request}')
|
|
|
|
return f(self, request, context)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def register_device(self, device_manager):
|
2022-02-18 07:35:20 +00:00
|
|
|
self._devices[device_manager.protocol] = device_manager
|
2022-01-05 09:32:09 +00:00
|
|
|
|
2022-02-23 15:34:28 +00:00
|
|
|
def start(self):
|
2022-05-16 10:28:29 +00:00
|
|
|
self._volume_mgr.start()
|
2022-01-05 09:32:09 +00:00
|
|
|
self._server.start()
|
2022-02-23 15:34:28 +00:00
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
self._server.stop(None)
|
2022-05-16 10:28:29 +00:00
|
|
|
self._volume_mgr.stop()
|
2022-01-05 09:32:09 +00:00
|
|
|
|
2021-10-28 08:50:02 +00:00
|
|
|
def _find_device_by_name(self, name):
|
2022-01-05 09:32:09 +00:00
|
|
|
return self._devices.get(name)
|
|
|
|
|
2021-10-28 08:50:02 +00:00
|
|
|
def _find_device_by_handle(self, handle):
|
|
|
|
for device in self._devices.values():
|
|
|
|
try:
|
|
|
|
if device.owns_device(handle):
|
|
|
|
return device
|
|
|
|
except NotImplementedError:
|
|
|
|
pass
|
|
|
|
return None
|
|
|
|
|
2022-04-27 12:30:00 +00:00
|
|
|
def _cleanup_volume(self, volume_id, existing):
|
|
|
|
if volume_id is None or existing:
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
self._volume_mgr.disconnect_volume(volume_id)
|
|
|
|
except VolumeException:
|
|
|
|
logging.warning('Failed to cleanup volume {volume_id}')
|
|
|
|
|
2022-01-05 09:32:09 +00:00
|
|
|
@_grpc_method
|
|
|
|
def CreateDevice(self, request, context):
|
|
|
|
response = pb2.CreateDeviceResponse()
|
2022-04-27 12:30:00 +00:00
|
|
|
volume_id, existing = None, False
|
2022-01-05 09:32:09 +00:00
|
|
|
try:
|
2022-04-27 12:30:00 +00:00
|
|
|
if request.HasField('volume'):
|
|
|
|
volume_id, existing = self._volume_mgr.connect_volume(request.volume)
|
|
|
|
|
2021-10-28 08:50:02 +00:00
|
|
|
manager = self._find_device_by_name(request.WhichOneof('params'))
|
2022-01-05 09:32:09 +00:00
|
|
|
if manager is None:
|
|
|
|
raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT,
|
|
|
|
'Unsupported device type')
|
|
|
|
response = manager.create_device(request)
|
2022-04-27 12:30:00 +00:00
|
|
|
# Now that we know the device handle, mark the volume as attached to
|
|
|
|
# that device
|
|
|
|
if volume_id is not None:
|
|
|
|
self._volume_mgr.set_device(volume_id, response.handle)
|
|
|
|
except (DeviceException, VolumeException) as ex:
|
|
|
|
self._cleanup_volume(volume_id, existing)
|
2022-01-05 09:32:09 +00:00
|
|
|
context.set_details(ex.message)
|
|
|
|
context.set_code(ex.code)
|
|
|
|
except NotImplementedError:
|
2022-04-27 12:30:00 +00:00
|
|
|
self._cleanup_volume(volume_id, existing)
|
2022-01-05 09:32:09 +00:00
|
|
|
context.set_details('Method is not implemented by selected device type')
|
|
|
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
|
|
return response
|
2021-10-28 08:50:02 +00:00
|
|
|
|
|
|
|
@_grpc_method
|
|
|
|
def DeleteDevice(self, request, context):
|
|
|
|
response = pb2.DeleteDeviceResponse()
|
|
|
|
try:
|
|
|
|
device = self._find_device_by_handle(request.handle)
|
|
|
|
if device is None:
|
|
|
|
raise DeviceException(grpc.StatusCode.NOT_FOUND,
|
|
|
|
'Invalid device handle')
|
2022-11-28 15:31:55 +00:00
|
|
|
if not device.allow_delete_volumes and self._volume_mgr.has_volumes(request.handle):
|
|
|
|
raise DeviceException(grpc.StatusCode.FAILED_PRECONDITION,
|
|
|
|
'Device has attached volumes')
|
2021-10-28 08:50:02 +00:00
|
|
|
device.delete_device(request)
|
2022-11-28 15:31:55 +00:00
|
|
|
# Either there are no volumes attached to this device or we're allowed to delete it
|
|
|
|
# with volumes still attached
|
2022-04-27 12:30:00 +00:00
|
|
|
self._volume_mgr.disconnect_device_volumes(request.handle)
|
2021-10-28 08:50:02 +00:00
|
|
|
except DeviceException as ex:
|
|
|
|
context.set_details(ex.message)
|
|
|
|
context.set_code(ex.code)
|
|
|
|
except NotImplementedError:
|
|
|
|
context.set_details('Method is not implemented by selected device type')
|
|
|
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
|
|
return response
|
2021-11-05 09:23:13 +00:00
|
|
|
|
|
|
|
@_grpc_method
|
|
|
|
def AttachVolume(self, request, context):
|
|
|
|
response = pb2.AttachVolumeResponse()
|
2022-04-27 12:30:00 +00:00
|
|
|
volume_id, existing = None, False
|
2021-11-05 09:23:13 +00:00
|
|
|
try:
|
2022-04-27 12:30:00 +00:00
|
|
|
if not request.HasField('volume'):
|
|
|
|
raise VolumeException(grpc.StatusCode.INVALID_ARGUMENT,
|
|
|
|
'Missing required field: volume')
|
|
|
|
volume_id, existing = self._volume_mgr.connect_volume(request.volume,
|
|
|
|
request.device_handle)
|
2021-11-05 09:23:13 +00:00
|
|
|
device = self._find_device_by_handle(request.device_handle)
|
|
|
|
if device is None:
|
|
|
|
raise DeviceException(grpc.StatusCode.NOT_FOUND, 'Invalid device handle')
|
|
|
|
device.attach_volume(request)
|
2022-04-27 12:30:00 +00:00
|
|
|
except (DeviceException, VolumeException) as ex:
|
|
|
|
self._cleanup_volume(volume_id, existing)
|
2021-11-05 09:23:13 +00:00
|
|
|
context.set_details(ex.message)
|
|
|
|
context.set_code(ex.code)
|
|
|
|
except NotImplementedError:
|
2022-04-27 12:30:00 +00:00
|
|
|
self._cleanup_volume(volume_id, existing)
|
2021-11-05 09:23:13 +00:00
|
|
|
context.set_details('Method is not implemented by selected device type')
|
|
|
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
|
|
return response
|
|
|
|
|
|
|
|
@_grpc_method
|
|
|
|
def DetachVolume(self, request, context):
|
|
|
|
response = pb2.DetachVolumeResponse()
|
|
|
|
try:
|
|
|
|
device = self._find_device_by_handle(request.device_handle)
|
|
|
|
if device is not None:
|
|
|
|
device.detach_volume(request)
|
2022-04-27 12:30:00 +00:00
|
|
|
self._volume_mgr.disconnect_volume(request.volume_id)
|
2021-11-05 09:23:13 +00:00
|
|
|
except DeviceException as ex:
|
|
|
|
context.set_details(ex.message)
|
|
|
|
context.set_code(ex.code)
|
|
|
|
return response
|
2022-08-01 06:15:33 +00:00
|
|
|
|
2022-08-30 02:49:27 +00:00
|
|
|
@_grpc_method
|
|
|
|
def SetQos(self, request, context):
|
|
|
|
response = pb2.SetQosResponse()
|
|
|
|
try:
|
|
|
|
device = self._find_device_by_handle(request.device_handle)
|
|
|
|
if device is None:
|
|
|
|
raise DeviceException(grpc.StatusCode.NOT_FOUND, 'Invalid device handle')
|
|
|
|
device.set_qos(request)
|
|
|
|
except DeviceException as ex:
|
|
|
|
context.set_details(ex.message)
|
|
|
|
context.set_code(ex.code)
|
|
|
|
except NotImplementedError:
|
|
|
|
context.set_details('Method is not implemented by selected device type')
|
|
|
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
|
|
return response
|
|
|
|
|
|
|
|
@_grpc_method
|
|
|
|
def GetQosCapabilities(self, request, context):
|
|
|
|
device_type_map = {
|
|
|
|
pb2.DeviceType.DEVICE_TYPE_NVME: 'nvme',
|
|
|
|
pb2.DeviceType.DEVICE_TYPE_VIRTIO_BLK: 'virtio_blk',
|
|
|
|
pb2.DeviceType.DEVICE_TYPE_NVMF_TCP: 'nvmf_tcp',
|
|
|
|
}
|
|
|
|
response = pb2.GetQosCapabilitiesResponse()
|
|
|
|
try:
|
|
|
|
name = device_type_map.get(request.device_type)
|
|
|
|
if name is None:
|
|
|
|
raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT,
|
|
|
|
'Invalid device type')
|
|
|
|
manager = self._find_device_by_name(name)
|
|
|
|
if manager is None:
|
|
|
|
raise DeviceException(grpc.StatusCode.INVALID_ARGUMENT,
|
|
|
|
'Unsupported device type')
|
|
|
|
response = manager.get_qos_capabilities(request)
|
|
|
|
except DeviceException as ex:
|
|
|
|
context.set_details(ex.message)
|
|
|
|
context.set_code(ex.code)
|
|
|
|
except NotImplementedError:
|
|
|
|
# If a device manager doesn't implement this method, return empty capabilities to
|
|
|
|
# indicate that no QoS capabilities are supported
|
|
|
|
pass
|
|
|
|
return response
|
|
|
|
|
2022-08-01 06:15:33 +00:00
|
|
|
|
|
|
|
crypto.register_crypto_engine(crypto.CryptoEngineNop())
|
2022-08-03 02:57:06 +00:00
|
|
|
crypto.register_crypto_engine(crypto_bdev.CryptoEngineBdev())
|
2022-08-01 06:15:33 +00:00
|
|
|
crypto.set_crypto_engine('nop')
|