Skip to content

Commit

Permalink
feat(storage): expose shared storage device on the network
Browse files Browse the repository at this point in the history
Signed-off-by: Cedric Hombourger <[email protected]>
  • Loading branch information
chombourger committed Jan 16, 2024
1 parent 8ee75a5 commit 087ca3e
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 9 deletions.
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Depends: python3:any (>= 3.7~),
wamerican,
${misc:Depends}
Replaces: mtda-usb-functions
Suggests: nbd-server
Description: Multi-Tenant Device Access
Multi-Tenant Device Access (or MTDA for short) is a relatively
small Python application and library acting as an interface
Expand Down
13 changes: 13 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ target`` command::

$ mtda-cli storage target

Lastly, the shared device may be exposed on the network using the
``storage network`` command:

$ mtda-cli storage network

It uses `nbd-server` on the MTDA host and `sudo` and `nbd-client` on
the client (the `nbd` kernel module must loaded or built-in into the
kernel for `nbd-client to succeed). The name of the network block
device will be printed to `stdout` and should be used to detach/release
the block device when done:

$ nbd-client -d /dev/nbd0

Monitor commands
~~~~~~~~~~~~~~~~

Expand Down
6 changes: 6 additions & 0 deletions meta-isar/recipes-conf/network/files/postinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
# This script is part of MTDA
# Copyright (C) 2024 Siemens AG
# SPDX-License-Identifier: MIT

sed -i -e 's,group = .*,group = disk,g' /etc/nbd-server/config
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

DESCRIPTION = "MTDA network configuration using network-manager"
MAINTAINER = "Cedric Hombourger <[email protected]>"
DEBIAN_DEPENDS = "network-manager"
DEBIAN_DEPENDS = "nbd-server, network-manager"
DPKG_ARCH = "all"

SRC_URI = "file://90-systemd-networkd-disabled.preset"
SRC_URI = "file://postinst \
file://90-systemd-networkd-disabled.preset"

inherit dpkg-raw

Expand Down
13 changes: 12 additions & 1 deletion mtda-cli
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ class Application:
cmds = {
'host': self.storage_host,
'mount': self.storage_mount,
'network': self.storage_network,
'target': self.storage_target,
'update': self.storage_update,
'write': self.storage_write
Expand All @@ -302,6 +303,13 @@ class Application:
return 1
return 0

def storage_network(self, args=None):
status = self.agent.storage_network(self.remote)
if status is False:
print("'storage network' failed!", file=sys.stderr)
return 1
return 0

def storage_target(self, args):
status = self.client().storage_to_target()
if status is False:
Expand Down Expand Up @@ -683,7 +691,10 @@ class Application:
metavar="partition number",
type=int,
nargs="?",
help="Parititon number to mount",
help="Partition number to mount",
)
s = subsub.add_parser(
"network", help="Access the shared storage device over the network"
)
s = subsub.add_parser(
"target", help="Attach the shared storage device to the target"
Expand Down
13 changes: 13 additions & 0 deletions mtda/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os
import random
import socket
import subprocess
import tempfile
import time
import zerorpc
Expand Down Expand Up @@ -163,6 +164,18 @@ def storage_locked(self):
def storage_mount(self, part=None):
return self._impl.storage_mount(part, self._session)

def storage_network(self, remote):
cmd = '/usr/sbin/nbd-client'
if os.path.exists(cmd) is False:
raise RuntimeError('{} not found'.format(cmd))

rdev = self._impl.storage_network()
if rdev is None:
raise RuntimeError('could not put storage on network')

cmd = ['sudo', cmd, '-N', 'mtda-storage', remote]
subprocess.check_call(cmd)

def storage_open(self):
tries = 60
while tries > 0:
Expand Down
1 change: 1 addition & 0 deletions mtda/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class SESSION:

class STORAGE:
ON_HOST = "HOST"
ON_NETWORK = "NETWORK"
ON_TARGET = "TARGET"
LOCKED = "LOCKED"
UNLOCKED = "UNLOCKED"
Expand Down
60 changes: 54 additions & 6 deletions mtda/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
DEFAULT_PREFIX_KEY = 'ctrl-a'
DEFAULT_PASTEBIN_EP = "http://pastebin.com/api/api_post.php"

NBD_CONF_DIR = '/etc/nbd-server/conf.d'
NBD_CONF_FILE = 'mtda-storage.conf'


def _make_printable(s):
return s.encode('ascii', 'replace').decode()
Expand Down Expand Up @@ -86,6 +89,7 @@ def __init__(self):
self._storage_mounted = False
self._storage_opened = False
self._storage_owner = None
self._storage_status = CONSTS.STORAGE.UNKNOWN
self._writer = None
self._writer_data = None
self.blksz = CONSTS.WRITER.READ_SIZE
Expand Down Expand Up @@ -586,6 +590,10 @@ def publish(self, topic, data):
self.socket.send(data)

def _storage_event(self, status, reason=""):
if status in [CONSTS.STORAGE.ON_HOST,
CONSTS.STORAGE.ON_NETWORK,
CONSTS.STORAGE.ON_TARGET]:
self._storage_status = status
if reason:
status = status + " " + reason
self.notify(CONSTS.EVENTS.STORAGE, status)
Expand Down Expand Up @@ -630,6 +638,12 @@ def storage_close(self, session=None):
if self.storage is None:
result = False
else:
conf = os.path.join(NBD_CONF_DIR, NBD_CONF_FILE)
if os.path.exists(conf):
os.unlink(conf)
cmd = ['systemctl', 'restart', 'nbd-server']
subprocess.check_call(cmd)

self._writer.stop()
self._writer_data = None
self._storage_opened = not self.storage.close()
Expand Down Expand Up @@ -727,6 +741,38 @@ def storage_update(self, dst, offset, session=None):
self.mtda.debug(3, "main.storage_update(): %s" % str(result))
return result

def storage_network(self, session=None):
self.mtda.debug(3, "main.storage_network()")

result = False
self._session_check(session)
if self.storage_locked(session) is False:
if self.storage.to_host() is True:
conf = os.path.join(NBD_CONF_DIR, NBD_CONF_FILE)
file = None

if hasattr(self.storage, 'path'):
file = self.storage.path()

if file is not None and os.path.exists(NBD_CONF_DIR):
with open(conf, 'w') as f:
f.write('[mtda-storage]\n')
f.write('authfile = /etc/nbd-server/allow\n')
f.write('exportname = {}\n'.format(file))
f.close()

cmd = ['systemctl', 'restart', 'nbd-server']
subprocess.check_call(cmd)

cmd = ['systemctl', 'is-active', 'nbd-server']
subprocess.check_call(cmd)

self._storage_event(CONSTS.STORAGE.ON_NETWORK)
result = True

self.mtda.debug(3, "main.storage_network(): {}".format(result))
return result

def storage_open(self, session=None):
self.mtda.debug(3, 'main.storage_open()')

Expand Down Expand Up @@ -758,10 +804,9 @@ def storage_status(self, session=None):
self.mtda.debug(4, "storage_status(): no shared storage device")
result = CONSTS.STORAGE.UNKNOWN, False, 0
else:
# avoid costly query of storage state when we know it anyways
status = CONSTS.STORAGE.ON_HOST \
if self._writer.writing else self.storage.status()
result = status, self._writer.writing, self._writer.written
result = (self._storage_status,
self._writer.writing,
self._writer.written)

self.mtda.debug(3, "main.storage_status(): %s" % str(result))
return result
Expand All @@ -778,7 +823,7 @@ def storage_to_host(self, session=None):
self.error('cannot switch storage to host: locked')
result = False

self.mtda.debug(3, "main.storage_to_host(): %s" % str(result))
self.mtda.debug(3, "main.storage_to_host(): {}".format(result))
return result

def storage_to_target(self, session=None):
Expand All @@ -803,7 +848,7 @@ def storage_swap(self, session=None):
self._session_check(session)
if self.storage_locked(session) is False:
result, writing, written = self.storage_status(session)
if result == CONSTS.STORAGE.ON_HOST:
if result in [CONSTS.STORAGE.ON_HOST, CONSTS.STORAGE.ON_NETWORK]:
if self.storage.to_target() is True:
self._storage_event(CONSTS.STORAGE.ON_TARGET)
elif result == CONSTS.STORAGE.ON_TARGET:
Expand Down Expand Up @@ -1392,6 +1437,9 @@ def post_configure_storage(self, storage, config, parser):
self.mtda.debug(3, "main.post_configure_storage()")
self._writer = AsyncImageWriter(self, storage)

import atexit
atexit.register(self.storage_close)

def load_remote_config(self, parser):
self.mtda.debug(3, "main.load_remote_config()")

Expand Down
5 changes: 5 additions & 0 deletions mtda/storage/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ def open(self):
""" Open the shared storage device for I/O operations"""
return False

@abc.abstractmethod
def path(self):
""" Expose path to the shared storage device"""
return None

@abc.abstractmethod
def probe(self):
""" Check presence of the shared storage device"""
Expand Down
6 changes: 6 additions & 0 deletions mtda/storage/helpers/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ def _open_impl(self):
self.handle.seek(0, 0)
return True

def path(self):
self.mtda.debug(3, "storage.helpers.image.path()")
result = self.file
self.mtda.debug(3, "storage.helpers.image.open(): {}".format(result))
return result

def status(self):
self.mtda.debug(3, "storage.helpers.image.status()")
with self.lock:
Expand Down

0 comments on commit 087ca3e

Please sign in to comment.