1#!/usr/bin/env python3 2# SPDX-License-Identifier: BSD-3-Clause 3# Copyright (C) 2022 Intel Corporation 4# All rights reserved. 5# 6 7from argparse import ArgumentParser 8import importlib 9import logging 10import os 11import signal 12import sys 13import threading 14import time 15import yaml 16 17sys.path.append(os.path.dirname(__file__) + '/../python') 18 19import spdk.sma as sma # noqa 20import spdk.rpc.client as rpcclient # noqa 21 22 23def parse_config(path): 24 if path is None: 25 return {} 26 with open(path, 'r') as cfgfile: 27 config = yaml.load(cfgfile, Loader=yaml.FullLoader) 28 return {**config} if config is not None else {} 29 30 31def parse_argv(): 32 parser = ArgumentParser(description='Storage Management Agent command line interface') 33 parser.add_argument('--address', '-a', help='IP address to listen on') 34 parser.add_argument('--socket', '-s', help='SPDK RPC socket') 35 parser.add_argument('--port', '-p', type=int, help='IP port to listen on') 36 parser.add_argument('--config', '-c', help='Path to config file') 37 defaults = {'address': 'localhost', 38 'socket': '/var/tmp/spdk.sock', 39 'port': 8080, 40 'discovery_timeout': 10.0, 41 'volume_cleanup_period': 60.0} 42 # Merge the default values, config file, and the command-line 43 args = vars(parser.parse_args()) 44 config = parse_config(args.get('config')) 45 for argname, argvalue in defaults.items(): 46 if args.get(argname) is not None: 47 if config.get(argname) is not None: 48 logging.info(f'Overriding "{argname}" value from command-line') 49 config[argname] = args[argname] 50 if config.get(argname) is None: 51 config[argname] = argvalue 52 return config 53 54 55def get_build_client(sock): 56 def build_client(): 57 return rpcclient.JSONRPCClient(sock) 58 59 return build_client 60 61 62def register_devices(agent, devices, config): 63 for device_config in config.get('devices') or []: 64 name = device_config.get('name') 65 device_manager = next(filter(lambda s: s.name == name, devices), None) 66 if device_manager is None: 67 logging.error(f'Couldn\'t find device: {name}') 68 sys.exit(1) 69 logging.info(f'Registering device: {name}') 70 device_manager.init(device_config.get('params')) 71 agent.register_device(device_manager) 72 73 74def init_crypto(config, client): 75 crypto_config = config.get('crypto') 76 if crypto_config is None: 77 return 78 name = crypto_config.get('name') 79 if name is None: 80 logging.error('Crypto engine name is missing') 81 sys.exit(1) 82 try: 83 sma.set_crypto_engine(name) 84 sma.get_crypto_engine().init(client, crypto_config.get('params', {})) 85 except ValueError: 86 logging.error(f'Invalid crypto engine: {name}') 87 sys.exit(1) 88 89 90def load_plugins(plugins, client): 91 devices = [] 92 for plugin in plugins: 93 module = importlib.import_module(plugin) 94 for device in getattr(module, 'devices', []): 95 logging.debug(f'Loading external device: {plugin}.{device.__name__}') 96 devices.append(device(client)) 97 for engine_class in getattr(module, 'crypto_engines', []): 98 engine = engine_class() 99 logging.debug(f'Loading external crypto engine: {plugin}.{engine.name}') 100 sma.register_crypto_engine(engine) 101 return devices 102 103 104def wait_for_listen(client, timeout): 105 start = time.monotonic() 106 while True: 107 try: 108 with client() as _client: 109 _client.call('rpc_get_methods') 110 # If we got here, the process is responding to RPCs 111 break 112 except rpcclient.JSONRPCException: 113 logging.debug('The SPDK process is not responding for {}s'.format( 114 int(time.monotonic() - start))) 115 116 if time.monotonic() > start + timeout: 117 logging.error('Timed out while waiting for SPDK process to respond') 118 sys.exit(1) 119 time.sleep(1) 120 121 122def run(agent): 123 event = threading.Event() 124 125 def signal_handler(signum, frame): 126 event.set() 127 128 for signum in [signal.SIGTERM, signal.SIGINT]: 129 signal.signal(signum, signal_handler) 130 131 agent.start() 132 event.wait() 133 agent.stop() 134 135 136if __name__ == '__main__': 137 logging.basicConfig(level=os.environ.get('SMA_LOGLEVEL', 'WARNING').upper()) 138 139 config = parse_argv() 140 client = get_build_client(config['socket']) 141 142 # Wait until the SPDK process starts responding to RPCs 143 wait_for_listen(client, timeout=60.0) 144 145 agent = sma.StorageManagementAgent(config, client) 146 147 devices = [sma.NvmfTcpDeviceManager(client), sma.VhostBlkDeviceManager(client), 148 sma.NvmfVfioDeviceManager(client)] 149 devices += load_plugins(config.get('plugins') or [], client) 150 devices += load_plugins(filter(None, os.environ.get('SMA_PLUGINS', '').split(':')), 151 client) 152 init_crypto(config, client) 153 register_devices(agent, devices, config) 154 run(agent) 155