xref: /spdk/scripts/sma.py (revision 45a053c5777494f4e8ce4bc1191c9de3920377f7)
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