1
0
Fork 0
nvme-stas/stasadm.py
Benjamin Drung 757b718eff
Adding upstream version 2.2.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-16 12:42:14 +01:00

202 lines
6.6 KiB
Python
Executable file

#!/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()