summaryrefslogtreecommitdiffstats
path: root/stasadm.py
diff options
context:
space:
mode:
Diffstat (limited to 'stasadm.py')
-rwxr-xr-xstasadm.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/stasadm.py b/stasadm.py
new file mode 100755
index 0000000..294fdde
--- /dev/null
+++ b/stasadm.py
@@ -0,0 +1,202 @@
+#!/usr/bin/python3
+# Copyright (c) 2021, Dell Inc. or its subsidiaries. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+# See the LICENSE file for details.
+#
+# This file is part of NVMe STorage Appliance Services (nvme-stas).
+#
+# Authors: Martin Belanger <Martin.Belanger@dell.com>
+#
+''' STorage Appliance Services Admin Tool '''
+import os
+import sys
+import uuid
+import configparser
+from argparse import ArgumentParser
+from staslib import defs
+
+try:
+ import hmac
+ import hashlib
+except (ImportError, ModuleNotFoundError):
+ hmac = None
+ hashlib = None
+
+
+def read_from_file(fname, size): # pylint: disable=missing-function-docstring
+ try:
+ with open(fname) as f: # pylint: disable=unspecified-encoding
+ data = f.read(size)
+ if len(data) == size:
+ return data
+ except FileNotFoundError:
+ pass
+
+ return None
+
+
+def get_machine_app_specific(app_id):
+ '''@brief Get a machine ID specific to an application. We use the
+ value retrieved from /etc/machine-id. The documentation states that
+ /etc/machine-id:
+ "should be considered "confidential", and must not be exposed in
+ untrusted environments, in particular on the network. If a stable
+ unique identifier that is tied to the machine is needed for some
+ application, the machine ID or any part of it must not be used
+ directly. Instead the machine ID should be hashed with a crypto-
+ graphic, keyed hash function, using a fixed, application-specific
+ key. That way the ID will be properly unique, and derived in a
+ constant way from the machine ID but there will be no way to
+ retrieve the original machine ID from the application-specific one"
+
+ @note systemd's C function sd_id128_get_machine_app_specific() was the
+ inspiration for this code.
+
+ @ref https://www.freedesktop.org/software/systemd/man/machine-id.html
+ '''
+ if not hmac:
+ return None
+
+ data = read_from_file('/etc/machine-id', 32)
+ if not data:
+ return None
+
+ hmac_obj = hmac.new(app_id, uuid.UUID(data).bytes, hashlib.sha256)
+ id128_bytes = hmac_obj.digest()[0:16]
+ return str(uuid.UUID(bytes=id128_bytes, version=4))
+
+
+def get_uuid_from_system():
+ '''@brief Try to find system UUID in the following order:
+ 1) /etc/machine-id
+ 2) /sys/class/dmi/id/product_uuid
+ 3) /proc/device-tree/ibm,partition-uuid
+ '''
+ uuid_str = get_machine_app_specific(b'$nvmexpress.org$')
+ if uuid_str:
+ return uuid_str
+
+ # The following files are only readable by root
+ if os.geteuid() != 0:
+ sys.exit('Permission denied. Root privileges required.')
+
+ id128 = read_from_file('/sys/class/dmi/id/product_uuid', 36)
+ if id128:
+ # Swap little-endian to network order per
+ # DMTF SMBIOS 3.0 Section 7.2.1 System UUID.
+ swapped = ''.join([id128[x] for x in (6, 7, 4, 5, 2, 3, 0, 1, 8, 11, 12, 9, 10, 13, 16, 17, 14, 15)])
+ return swapped + id128[18:]
+
+ return read_from_file('/proc/device-tree/ibm,partition-uuid', 36)
+
+
+def save(section, option, string, conf_file, fname):
+ '''@brief Save configuration
+
+ @param section: section in @conf_file where @option will be added
+ @param option: option to be added under @section in @conf_file
+ @param string: Text to be saved to @fname
+ @param conf_file: Configuration file name
+ @param fname: Optional file where @string will be saved
+ '''
+ if fname and string is not None:
+ with open(fname, 'w') as f: # pylint: disable=unspecified-encoding
+ print(string, file=f)
+
+ if conf_file:
+ config = configparser.ConfigParser(
+ default_section=None, allow_no_value=True, delimiters=('='), interpolation=None, strict=False
+ )
+ if os.path.isfile(conf_file):
+ config.read(conf_file)
+
+ try:
+ config.add_section(section)
+ except configparser.DuplicateSectionError:
+ pass
+
+ if fname:
+ string = 'file://' + fname
+
+ if string is not None:
+ config.set(section, option, string)
+ else:
+ config.remove_option(section, option)
+
+ with open(conf_file, 'w') as f: # pylint: disable=unspecified-encoding
+ config.write(f)
+
+
+def hostnqn(args):
+ '''@brief Configure the host NQN'''
+ uuid_str = get_uuid_from_system() or str(uuid.uuid4())
+ uuid_str = f'nqn.2014-08.org.nvmexpress:uuid:{uuid_str}'
+ save('Host', 'nqn', uuid_str, args.conf_file, args.file)
+
+
+def hostid(args):
+ '''@brief Configure the host ID'''
+ save('Host', 'id', str(uuid.uuid4()), args.conf_file, args.file)
+
+
+def set_symname(args):
+ '''@brief Define the host Symbolic Name'''
+ save('Host', 'symname', args.symname, args.conf_file, args.file)
+
+
+def clr_symname(args):
+ '''@brief Undefine the host NQN'''
+ save('Host', 'symname', None, args.conf_file, None)
+
+
+def get_parser(): # pylint: disable=missing-function-docstring
+ parser = ArgumentParser(description='Configuration utility for STAS.')
+ parser.add_argument('-v', '--version', action='store_true', help='Print version, then exit', default=False)
+ parser.add_argument(
+ '-c',
+ '--conf-file',
+ action='store',
+ help='Configuration file. Default %(default)s.',
+ default=defs.SYS_CONF_FILE,
+ type=str,
+ metavar='FILE',
+ )
+
+ subparser = parser.add_subparsers(title='Commands')
+
+ prsr = subparser.add_parser('hostnqn', help='Configure the host NQN. The NQN is auto-generated.')
+ prsr.add_argument(
+ '-f', '--file', action='store', help='Optional file where to save the NQN.', type=str, metavar='FILE'
+ )
+ prsr.set_defaults(cmd=hostnqn)
+
+ prsr = subparser.add_parser('hostid', help='Configure the host ID. The ID is auto-generated.')
+ prsr.add_argument(
+ '-f', '--file', action='store', help='Optional file where to save the ID.', type=str, metavar='FILE'
+ )
+ prsr.set_defaults(cmd=hostid)
+
+ prsr = subparser.add_parser('set-symname', help='Set the host symbolic')
+ prsr.add_argument(
+ '-f', '--file', action='store', help='Optional file where to save the symbolic name.', type=str, metavar='FILE'
+ )
+ prsr.add_argument('symname', action='store', help='Symbolic name', default=None, metavar='SYMNAME')
+ prsr.set_defaults(cmd=set_symname)
+
+ prsr = subparser.add_parser('clear-symname', help='Clear the host symbolic')
+ prsr.set_defaults(cmd=clr_symname)
+
+ return parser
+
+
+PARSER = get_parser()
+ARGS = PARSER.parse_args()
+if ARGS.version:
+ print(f'nvme-stas {defs.VERSION}')
+ sys.exit(0)
+
+try:
+ ARGS.cmd(ARGS)
+except AttributeError as ex:
+ print(str(ex))
+ PARSER.print_usage()