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