2022-01-05 09:32:09 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
from argparse import ArgumentParser
|
2022-02-03 14:36:55 +00:00
|
|
|
import importlib
|
2022-01-05 09:32:09 +00:00
|
|
|
import logging
|
|
|
|
import os
|
2022-02-23 15:34:28 +00:00
|
|
|
import signal
|
2022-01-05 09:32:09 +00:00
|
|
|
import sys
|
2022-02-23 15:34:28 +00:00
|
|
|
import threading
|
2022-03-03 14:56:36 +00:00
|
|
|
import time
|
2022-02-22 13:53:06 +00:00
|
|
|
import yaml
|
2022-01-05 09:32:09 +00:00
|
|
|
|
|
|
|
sys.path.append(os.path.dirname(__file__) + '/../python')
|
|
|
|
|
2022-03-03 14:56:36 +00:00
|
|
|
import spdk.sma as sma # noqa
|
|
|
|
import spdk.rpc.client as rpcclient # noqa
|
2022-01-05 09:32:09 +00:00
|
|
|
|
|
|
|
|
2022-02-22 13:53:06 +00:00
|
|
|
def parse_config(path):
|
|
|
|
if path is None:
|
|
|
|
return {}
|
|
|
|
with open(path, 'r') as cfgfile:
|
|
|
|
config = yaml.load(cfgfile, Loader=yaml.FullLoader)
|
|
|
|
return {**config} if config is not None else {}
|
|
|
|
|
|
|
|
|
2022-01-05 15:54:14 +00:00
|
|
|
def parse_argv():
|
|
|
|
parser = ArgumentParser(description='Storage Management Agent command line interface')
|
2022-02-22 13:53:06 +00:00
|
|
|
parser.add_argument('--address', '-a', help='IP address to listen on')
|
|
|
|
parser.add_argument('--socket', '-s', help='SPDK RPC socket')
|
|
|
|
parser.add_argument('--port', '-p', type=int, help='IP port to listen on')
|
|
|
|
parser.add_argument('--config', '-c', help='Path to config file')
|
|
|
|
defaults = {'address': 'localhost',
|
|
|
|
'socket': '/var/tmp/spdk.sock',
|
2022-04-27 12:30:00 +00:00
|
|
|
'port': 8080,
|
2022-05-16 10:28:29 +00:00
|
|
|
'discovery_timeout': 10.0,
|
|
|
|
'volume_cleanup_period': 60.0}
|
2022-02-22 13:53:06 +00:00
|
|
|
# Merge the default values, config file, and the command-line
|
|
|
|
args = vars(parser.parse_args())
|
|
|
|
config = parse_config(args.get('config'))
|
|
|
|
for argname, argvalue in defaults.items():
|
|
|
|
if args.get(argname) is not None:
|
|
|
|
if config.get(argname) is not None:
|
|
|
|
logging.info(f'Overriding "{argname}" value from command-line')
|
|
|
|
config[argname] = args[argname]
|
|
|
|
if config.get(argname) is None:
|
|
|
|
config[argname] = argvalue
|
|
|
|
return config
|
2022-01-05 15:54:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_build_client(sock):
|
|
|
|
def build_client():
|
2022-03-03 14:56:36 +00:00
|
|
|
return rpcclient.JSONRPCClient(sock)
|
2022-01-05 15:54:14 +00:00
|
|
|
|
|
|
|
return build_client
|
2022-01-05 09:32:09 +00:00
|
|
|
|
|
|
|
|
2022-02-22 14:41:24 +00:00
|
|
|
def register_devices(agent, devices, config):
|
|
|
|
for device_config in config.get('devices') or []:
|
|
|
|
name = device_config.get('name')
|
|
|
|
device_manager = next(filter(lambda s: s.name == name, devices), None)
|
|
|
|
if device_manager is None:
|
|
|
|
logging.error(f'Couldn\'t find device: {name}')
|
|
|
|
sys.exit(1)
|
|
|
|
logging.info(f'Registering device: {name}')
|
|
|
|
device_manager.init(device_config.get('params'))
|
|
|
|
agent.register_device(device_manager)
|
|
|
|
|
|
|
|
|
|
|
|
def load_plugins(plugins, client):
|
|
|
|
devices = []
|
2022-02-03 14:36:55 +00:00
|
|
|
for plugin in plugins:
|
|
|
|
module = importlib.import_module(plugin)
|
|
|
|
for device in getattr(module, 'devices', []):
|
|
|
|
logging.debug(f'Loading external device: {plugin}.{device.__name__}')
|
2022-02-22 14:41:24 +00:00
|
|
|
devices.append(device(client))
|
|
|
|
return devices
|
2022-02-03 14:36:55 +00:00
|
|
|
|
|
|
|
|
2022-03-03 14:56:36 +00:00
|
|
|
def wait_for_listen(client, timeout):
|
|
|
|
start = time.monotonic()
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
with client() as _client:
|
|
|
|
_client.call('rpc_get_methods')
|
|
|
|
# If we got here, the process is responding to RPCs
|
|
|
|
break
|
|
|
|
except rpcclient.JSONRPCException:
|
|
|
|
logging.debug('The SPDK process is not responding for {}s'.format(
|
|
|
|
int(time.monotonic() - start)))
|
|
|
|
|
|
|
|
if time.monotonic() > start + timeout:
|
|
|
|
logging.error('Timed out while waiting for SPDK process to respond')
|
|
|
|
sys.exit(1)
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
|
2022-02-23 15:34:28 +00:00
|
|
|
def run(agent):
|
|
|
|
event = threading.Event()
|
|
|
|
|
|
|
|
def signal_handler(signum, frame):
|
|
|
|
event.set()
|
|
|
|
|
|
|
|
for signum in [signal.SIGTERM, signal.SIGINT]:
|
|
|
|
signal.signal(signum, signal_handler)
|
|
|
|
|
|
|
|
agent.start()
|
|
|
|
event.wait()
|
|
|
|
agent.stop()
|
|
|
|
|
|
|
|
|
2022-01-05 09:32:09 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
logging.basicConfig(level=os.environ.get('SMA_LOGLEVEL', 'WARNING').upper())
|
2022-02-22 13:53:06 +00:00
|
|
|
|
|
|
|
config = parse_argv()
|
2022-02-22 14:41:24 +00:00
|
|
|
client = get_build_client(config['socket'])
|
|
|
|
|
2022-03-03 14:56:36 +00:00
|
|
|
# Wait until the SPDK process starts responding to RPCs
|
|
|
|
wait_for_listen(client, timeout=60.0)
|
|
|
|
|
2022-04-27 12:30:00 +00:00
|
|
|
agent = sma.StorageManagementAgent(config, client)
|
2022-02-22 14:41:24 +00:00
|
|
|
|
2022-02-11 21:21:25 +00:00
|
|
|
devices = [sma.NvmfTcpDeviceManager(client), sma.VhostBlkDeviceManager(client),
|
|
|
|
sma.NvmfVfioDeviceManager(client)]
|
2022-02-22 14:41:24 +00:00
|
|
|
devices += load_plugins(config.get('plugins') or [], client)
|
|
|
|
devices += load_plugins(filter(None, os.environ.get('SMA_PLUGINS', '').split(':')),
|
|
|
|
client)
|
|
|
|
register_devices(agent, devices, config)
|
2022-02-23 15:34:28 +00:00
|
|
|
run(agent)
|