1048fb36aSKonrad Sztyber#!/usr/bin/env python3 2*17538bdcSpaul luse# SPDX-License-Identifier: BSD-3-Clause 3*17538bdcSpaul luse# Copyright (C) 2022 Intel Corporation 4*17538bdcSpaul luse# All rights reserved. 5*17538bdcSpaul luse# 6048fb36aSKonrad Sztyber 7048fb36aSKonrad Sztyberfrom argparse import ArgumentParser 893a20e79SKonrad Sztyberimport importlib 9048fb36aSKonrad Sztyberimport logging 10048fb36aSKonrad Sztyberimport os 114ee3d468SKonrad Sztyberimport signal 12048fb36aSKonrad Sztyberimport sys 134ee3d468SKonrad Sztyberimport threading 14b5678ba8SKonrad Sztyberimport time 15d2db3959SKonrad Sztyberimport yaml 16048fb36aSKonrad Sztyber 17048fb36aSKonrad Sztybersys.path.append(os.path.dirname(__file__) + '/../python') 18048fb36aSKonrad Sztyber 19048fb36aSKonrad Sztyberimport spdk.sma as sma # noqa 20b5678ba8SKonrad Sztyberimport spdk.rpc.client as rpcclient # noqa 21048fb36aSKonrad Sztyber 22048fb36aSKonrad Sztyber 23d2db3959SKonrad Sztyberdef parse_config(path): 24d2db3959SKonrad Sztyber if path is None: 25d2db3959SKonrad Sztyber return {} 26d2db3959SKonrad Sztyber with open(path, 'r') as cfgfile: 27d2db3959SKonrad Sztyber config = yaml.load(cfgfile, Loader=yaml.FullLoader) 28d2db3959SKonrad Sztyber return {**config} if config is not None else {} 29d2db3959SKonrad Sztyber 30d2db3959SKonrad Sztyber 31f0f65d24Ssberbzdef parse_argv(): 32f0f65d24Ssberbz parser = ArgumentParser(description='Storage Management Agent command line interface') 33d2db3959SKonrad Sztyber parser.add_argument('--address', '-a', help='IP address to listen on') 34d2db3959SKonrad Sztyber parser.add_argument('--socket', '-s', help='SPDK RPC socket') 35d2db3959SKonrad Sztyber parser.add_argument('--port', '-p', type=int, help='IP port to listen on') 36d2db3959SKonrad Sztyber parser.add_argument('--config', '-c', help='Path to config file') 37d2db3959SKonrad Sztyber defaults = {'address': 'localhost', 38d2db3959SKonrad Sztyber 'socket': '/var/tmp/spdk.sock', 390c2b10f2SKonrad Sztyber 'port': 8080, 4094308849SKonrad Sztyber 'discovery_timeout': 10.0, 4194308849SKonrad Sztyber 'volume_cleanup_period': 60.0} 42d2db3959SKonrad Sztyber # Merge the default values, config file, and the command-line 43d2db3959SKonrad Sztyber args = vars(parser.parse_args()) 44d2db3959SKonrad Sztyber config = parse_config(args.get('config')) 45d2db3959SKonrad Sztyber for argname, argvalue in defaults.items(): 46d2db3959SKonrad Sztyber if args.get(argname) is not None: 47d2db3959SKonrad Sztyber if config.get(argname) is not None: 48d2db3959SKonrad Sztyber logging.info(f'Overriding "{argname}" value from command-line') 49d2db3959SKonrad Sztyber config[argname] = args[argname] 50d2db3959SKonrad Sztyber if config.get(argname) is None: 51d2db3959SKonrad Sztyber config[argname] = argvalue 52d2db3959SKonrad Sztyber return config 53f0f65d24Ssberbz 54f0f65d24Ssberbz 55f0f65d24Ssberbzdef get_build_client(sock): 56048fb36aSKonrad Sztyber def build_client(): 57b5678ba8SKonrad Sztyber return rpcclient.JSONRPCClient(sock) 58f0f65d24Ssberbz 59f0f65d24Ssberbz return build_client 60048fb36aSKonrad Sztyber 61048fb36aSKonrad Sztyber 62add4a7ceSKonrad Sztyberdef register_devices(agent, devices, config): 63add4a7ceSKonrad Sztyber for device_config in config.get('devices') or []: 64add4a7ceSKonrad Sztyber name = device_config.get('name') 65add4a7ceSKonrad Sztyber device_manager = next(filter(lambda s: s.name == name, devices), None) 66add4a7ceSKonrad Sztyber if device_manager is None: 67add4a7ceSKonrad Sztyber logging.error(f'Couldn\'t find device: {name}') 68add4a7ceSKonrad Sztyber sys.exit(1) 69add4a7ceSKonrad Sztyber logging.info(f'Registering device: {name}') 70add4a7ceSKonrad Sztyber device_manager.init(device_config.get('params')) 71add4a7ceSKonrad Sztyber agent.register_device(device_manager) 72048fb36aSKonrad Sztyber 73048fb36aSKonrad Sztyber 74cc3f842cSKonrad Sztyberdef init_crypto(config, client): 75cc3f842cSKonrad Sztyber crypto_config = config.get('crypto') 76cc3f842cSKonrad Sztyber if crypto_config is None: 77cc3f842cSKonrad Sztyber return 78cc3f842cSKonrad Sztyber name = crypto_config.get('name') 79cc3f842cSKonrad Sztyber if name is None: 80cc3f842cSKonrad Sztyber logging.error('Crypto engine name is missing') 81cc3f842cSKonrad Sztyber sys.exit(1) 82cc3f842cSKonrad Sztyber try: 83cc3f842cSKonrad Sztyber sma.set_crypto_engine(name) 84cc3f842cSKonrad Sztyber sma.get_crypto_engine().init(client, crypto_config.get('params', {})) 85cc3f842cSKonrad Sztyber except ValueError: 86cc3f842cSKonrad Sztyber logging.error(f'Invalid crypto engine: {name}') 87cc3f842cSKonrad Sztyber sys.exit(1) 88cc3f842cSKonrad Sztyber 89cc3f842cSKonrad Sztyber 90add4a7ceSKonrad Sztyberdef load_plugins(plugins, client): 91add4a7ceSKonrad Sztyber devices = [] 9293a20e79SKonrad Sztyber for plugin in plugins: 9393a20e79SKonrad Sztyber module = importlib.import_module(plugin) 9493a20e79SKonrad Sztyber for device in getattr(module, 'devices', []): 9593a20e79SKonrad Sztyber logging.debug(f'Loading external device: {plugin}.{device.__name__}') 96add4a7ceSKonrad Sztyber devices.append(device(client)) 97cc3f842cSKonrad Sztyber for engine_class in getattr(module, 'crypto_engines', []): 98cc3f842cSKonrad Sztyber engine = engine_class() 99cc3f842cSKonrad Sztyber logging.debug(f'Loading external crypto engine: {plugin}.{engine.name}') 100cc3f842cSKonrad Sztyber sma.register_crypto_engine(engine) 101add4a7ceSKonrad Sztyber return devices 10293a20e79SKonrad Sztyber 10393a20e79SKonrad Sztyber 104b5678ba8SKonrad Sztyberdef wait_for_listen(client, timeout): 105b5678ba8SKonrad Sztyber start = time.monotonic() 106b5678ba8SKonrad Sztyber while True: 107b5678ba8SKonrad Sztyber try: 108b5678ba8SKonrad Sztyber with client() as _client: 109b5678ba8SKonrad Sztyber _client.call('rpc_get_methods') 110b5678ba8SKonrad Sztyber # If we got here, the process is responding to RPCs 111b5678ba8SKonrad Sztyber break 112b5678ba8SKonrad Sztyber except rpcclient.JSONRPCException: 113b5678ba8SKonrad Sztyber logging.debug('The SPDK process is not responding for {}s'.format( 114b5678ba8SKonrad Sztyber int(time.monotonic() - start))) 115b5678ba8SKonrad Sztyber 116b5678ba8SKonrad Sztyber if time.monotonic() > start + timeout: 117b5678ba8SKonrad Sztyber logging.error('Timed out while waiting for SPDK process to respond') 118b5678ba8SKonrad Sztyber sys.exit(1) 119b5678ba8SKonrad Sztyber time.sleep(1) 120b5678ba8SKonrad Sztyber 121b5678ba8SKonrad Sztyber 1224ee3d468SKonrad Sztyberdef run(agent): 1234ee3d468SKonrad Sztyber event = threading.Event() 1244ee3d468SKonrad Sztyber 1254ee3d468SKonrad Sztyber def signal_handler(signum, frame): 1264ee3d468SKonrad Sztyber event.set() 1274ee3d468SKonrad Sztyber 1284ee3d468SKonrad Sztyber for signum in [signal.SIGTERM, signal.SIGINT]: 1294ee3d468SKonrad Sztyber signal.signal(signum, signal_handler) 1304ee3d468SKonrad Sztyber 1314ee3d468SKonrad Sztyber agent.start() 1324ee3d468SKonrad Sztyber event.wait() 1334ee3d468SKonrad Sztyber agent.stop() 1344ee3d468SKonrad Sztyber 1354ee3d468SKonrad Sztyber 136048fb36aSKonrad Sztyberif __name__ == '__main__': 137048fb36aSKonrad Sztyber logging.basicConfig(level=os.environ.get('SMA_LOGLEVEL', 'WARNING').upper()) 138d2db3959SKonrad Sztyber 139d2db3959SKonrad Sztyber config = parse_argv() 140add4a7ceSKonrad Sztyber client = get_build_client(config['socket']) 141add4a7ceSKonrad Sztyber 142b5678ba8SKonrad Sztyber # Wait until the SPDK process starts responding to RPCs 143b5678ba8SKonrad Sztyber wait_for_listen(client, timeout=60.0) 144b5678ba8SKonrad Sztyber 1450c2b10f2SKonrad Sztyber agent = sma.StorageManagementAgent(config, client) 146add4a7ceSKonrad Sztyber 147d5d6efd8SMilosz Linkiewicz devices = [sma.NvmfTcpDeviceManager(client), sma.VhostBlkDeviceManager(client), 148d5d6efd8SMilosz Linkiewicz sma.NvmfVfioDeviceManager(client)] 149add4a7ceSKonrad Sztyber devices += load_plugins(config.get('plugins') or [], client) 150add4a7ceSKonrad Sztyber devices += load_plugins(filter(None, os.environ.get('SMA_PLUGINS', '').split(':')), 151add4a7ceSKonrad Sztyber client) 152cc3f842cSKonrad Sztyber init_crypto(config, client) 153add4a7ceSKonrad Sztyber register_devices(agent, devices, config) 1544ee3d468SKonrad Sztyber run(agent) 155