Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package nvme-stas for openSUSE:Factory checked in at 2022-04-06 21:52:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nvme-stas (Old) and /work/SRC/openSUSE:Factory/.nvme-stas.new.1900 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nvme-stas" Wed Apr 6 21:52:02 2022 rev:4 rq:967286 version:1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/nvme-stas/nvme-stas.changes 2022-03-25 21:55:17.118313149 +0100 +++ /work/SRC/openSUSE:Factory/.nvme-stas.new.1900/nvme-stas.changes 2022-04-06 21:52:37.278793523 +0200 @@ -1,0 +2,8 @@ +Wed Apr 06 11:07:53 UTC 2022 - Daniel Wagner <daniel.wag...@suse.com> + +- Update to version v1.0: + * Do not call persistent_set() from libnvme + * dbus: return native dbus data instead of json when possible. + * update documentation + +------------------------------------------------------------------- Old: ---- nvme-stas-1.0~rc5.obscpio New: ---- nvme-stas-1.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nvme-stas.spec ++++++ --- /var/tmp/diff_new_pack.u1rCZY/_old 2022-04-06 21:52:37.762788000 +0200 +++ /var/tmp/diff_new_pack.u1rCZY/_new 2022-04-06 21:52:37.770787908 +0200 @@ -17,26 +17,26 @@ Name: nvme-stas -Version: 1.0~rc5 +Version: 1.0 Release: 0 Summary: NVMe STorage Appliance Services License: Apache-2.0 URL: https://github.com/linux-nvme/nvme-stas Source0: nvme-stas-%{version}.tar.gz BuildRequires: gobject-introspection -BuildRequires: libnvme-devel >= 1.0~7 +BuildRequires: libnvme-devel >= 1.0~8 BuildRequires: meson >= 0.52.0 BuildRequires: python3 BuildRequires: python3-dasbus BuildRequires: python3-gobject -BuildRequires: python3-libnvme >= 1.0~7 +BuildRequires: python3-libnvme >= 1.0~8 BuildRequires: python3-pyudev BuildRequires: python3-systemd BuildRequires: systemd-rpm-macros Requires: avahi Requires: python3-dasbus Requires: python3-gobject -Requires: python3-libnvme >= 1.0~7 +Requires: python3-libnvme >= 1.0~8 Requires: python3-pyudev Requires: python3-systemd @@ -92,5 +92,6 @@ %{python3_sitearch}/staslib/defs.py %{python3_sitearch}/staslib/glibudev.py %{python3_sitearch}/staslib/stas.py +%{python3_sitearch}/staslib/version.py %changelog ++++++ _service ++++++ --- /var/tmp/diff_new_pack.u1rCZY/_old 2022-04-06 21:52:37.830787223 +0200 +++ /var/tmp/diff_new_pack.u1rCZY/_new 2022-04-06 21:52:37.834787178 +0200 @@ -3,12 +3,10 @@ <param name="scm">git</param> <param name="url">https://github.com/linux-nvme/nvme-stas.git</param> <param name="filename">nvme-stas</param> - <!-- <param name="versionformat">@PARENT_TAG@+@TAG_OFFSET@</param> --> <param name="versionformat">@PARENT_TAG@</param> - <param name="revision">v1.0-rc5</param> - <param name="match-tag">v[01].[0-9]*</param> - <param name="versionrewrite-pattern">v([^+]*)-rc([0-9]+)</param> - <param name="versionrewrite-replacement">\1~rc\2</param> + <param name="revision">v1.0</param> + <param name="versionrewrite-pattern">v(\d+.\d+)</param> + <param name="versionrewrite-replacement">\1</param> <param name="changesgenerate">enable</param> </service> <service name="set_version" mode="manual"> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.u1rCZY/_old 2022-04-06 21:52:37.850786995 +0200 +++ /var/tmp/diff_new_pack.u1rCZY/_new 2022-04-06 21:52:37.858786904 +0200 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/linux-nvme/nvme-stas.git</param> - <param name="changesrevision">d014d6e4739cdbe3b595ee55a81e1b047647cea9</param> + <param name="changesrevision">0cafd52167dccf044dd45ec78fb4d1a6bd36d2cb</param> </service> </servicedata> (No newline at EOF) ++++++ nvme-stas-1.0~rc5.obscpio -> nvme-stas-1.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/.github/workflows/pylint.yml new/nvme-stas-1.0/.github/workflows/pylint.yml --- old/nvme-stas-1.0~rc5/.github/workflows/pylint.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/nvme-stas-1.0/.github/workflows/pylint.yml 2022-04-05 20:01:26.000000000 +0200 @@ -0,0 +1,53 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + sudo apt-get install -y meson libgirepository1.0-dev libsystemd-dev python3-systemd python3-pyudev python3-lxml + python3 -m pip install --upgrade pip wheel + python3 -m pip install --upgrade dasbus pylint pyflakes PyGObject + + - name: Install libnvme + run: | + meson subprojects download + meson setup builddir subprojects/libnvme + ninja -C builddir + + - name: Set PYTHONPATH + run: | + echo "PYTHONPATH=builddir:/usr/lib/python3/dist-packages/" >> $GITHUB_ENV + + - name: Show test environment + run: | + python3 -VV + python3 -m site + python3 -m pylint --version + echo "pyflakes $(python3 -m pyflakes --version)" + + - name: Pylint + run: | + python3 -m pylint --rcfile=test/pylint.rc *.py staslib + + - name: Pyflakes + if: always() + run: | + python3 -m pyflakes *.py staslib + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/etc/stas/stacd.conf new/nvme-stas-1.0/etc/stas/stacd.conf --- old/nvme-stas-1.0~rc5/etc/stas/stacd.conf 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/etc/stas/stacd.conf 2022-04-05 20:01:26.000000000 +0200 @@ -33,11 +33,11 @@ # kato: Keep Alive Timeout (KATO): This field specifies the timeout value # for the Keep Alive feature in seconds. The default value for this -# field is 120 seconds (2 minutes). +# field is 30 seconds (2 minutes). # Type: Unsigned integer # Range: 0..N # Unit: Seconds -#kato=120 +#kato=30 # ignore-iface: This option controls how connections with I/O Controllers (IOC) # are made. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/etc/stas/stafd.conf new/nvme-stas-1.0/etc/stas/stafd.conf --- old/nvme-stas-1.0~rc5/etc/stas/stafd.conf 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/etc/stas/stafd.conf 2022-04-05 20:01:26.000000000 +0200 @@ -33,11 +33,11 @@ # kato: Keep Alive Timeout (KATO): This field specifies the timeout value # for the Keep Alive feature in seconds. The default value for this -# field is 120 seconds (2 minutes). +# field is 30 seconds. # Type: Unsigned integer # Range: 0..N # Unit: Seconds -#kato=120 +#kato=30 # persistent-connections: Whether connections to Discovery Controllers (DC) # are persistent. If stafd is stopped, the connections diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/man/standard-conf.xml new/nvme-stas-1.0/man/standard-conf.xml --- old/nvme-stas-1.0~rc5/man/standard-conf.xml 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/man/standard-conf.xml 2022-04-05 20:01:26.000000000 +0200 @@ -60,7 +60,9 @@ <para> Keep Alive Timeout (KATO) in seconds. Takes an unsigned integer. This field specifies the timeout value for the Keep - Alive feature in seconds. Defaults to 120 seconds. + Alive feature in seconds. Defaults to 30 seconds for + Discovery Controller connections and 120 seconds for I/O + Controller connections. </para> </listitem> </varlistentry> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/meson.build new/nvme-stas-1.0/meson.build --- old/nvme-stas-1.0~rc5/meson.build 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/meson.build 2022-04-05 20:01:26.000000000 +0200 @@ -9,7 +9,7 @@ project( 'nvme-stas', meson_version: '>= 0.52.0', - version: '1.0rc5', + version: '1.0', license: 'Apache-2.0', default_options: [ 'buildtype=release', @@ -151,7 +151,7 @@ ] foreach module: modules_to_test if pylint.found() - test('pylint ' + module[0], pylint, args: ['--errors-only', '--rcfile=' + rcfile, module[1]], env: test_env) + test('pylint ' + module[0], pylint, args: ['--rcfile=' + rcfile, module[1]], env: test_env) endif if pyflakes.found() test('pyflakes ' + module[0], pyflakes, args: [module[1]], env: test_env) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/stacctl.py new/nvme-stas-1.0/stacctl.py --- old/nvme-stas-1.0~rc5/stacctl.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/stacctl.py 2022-04-05 20:01:26.000000000 +0200 @@ -7,6 +7,8 @@ # # Authors: Martin Belanger <martin.belan...@dell.com> # +''' STorage Appliance Connector Control Utility +''' import sys import json import pprint @@ -15,24 +17,30 @@ from dasbus.connection import SystemMessageBus from staslib import defs -def tron(args): +def tron(args): # pylint: disable=unused-argument + ''' @brief Trace ON + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STACD_DBUS_NAME, defs.STACD_DBUS_PATH) iface.tron = True # pylint: disable=assigning-non-slot -def troff(args): +def troff(args): # pylint: disable=unused-argument + ''' @brief Trace OFF + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STACD_DBUS_NAME, defs.STACD_DBUS_PATH) iface.tron = False # pylint: disable=assigning-non-slot -def _extract_cid(ctrl): +def _extract_cid(ctrl): # pylint: disable=missing-function-docstring return ctrl['transport'], ctrl['traddr'], ctrl['trsvcid'], ctrl['host-traddr'], ctrl['host-iface'], ctrl['subsysnqn'] -def status(args): +def status(args): # pylint: disable=unused-argument + ''' @brief retrieve stacd's status information + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STACD_DBUS_NAME, defs.STACD_DBUS_PATH) info = json.loads(iface.process_info()) - info['controllers'] = json.loads(iface.list_controllers(True)) + info['controllers'] = iface.list_controllers(True) for controller in info['controllers']: transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn = _extract_cid(controller) controller.update(json.loads(iface.controller_info(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn))) @@ -40,9 +48,12 @@ print(pprint.pformat(info, width=120)) def ls(args): + ''' @brief list the I/O controller's that stacd is + connected (or trying to connect) to. + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STACD_DBUS_NAME, defs.STACD_DBUS_PATH) - info = json.loads(iface.list_controllers(args.detailed)) + info = iface.list_controllers(args.detailed) print(pprint.pformat(info, width=120)) PARSER = ArgumentParser(description=f'{defs.STAC_DESCRIPTION} ({defs.STAC_ACRONYM})') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/stacd.py new/nvme-stas-1.0/stacd.py --- old/nvme-stas-1.0~rc5/stacd.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/stacd.py 2022-04-05 20:01:26.000000000 +0200 @@ -13,6 +13,7 @@ from argparse import ArgumentParser from staslib import defs +# pylint: disable=consider-using-f-string DBUS_IDL = ''' <node> <interface name="%s.debug"> @@ -35,13 +36,13 @@ <interface name="%s"> <method name="list_controllers"> <arg direction="in" type="b" name="detailed"/> - <arg direction="out" type="s" name="controller_list_json"/> + <arg direction="out" type="aa{ss}" name="controller_list"/> </method> </interface> </node> ''' % (defs.STACD_DBUS_NAME, defs.STACD_DBUS_NAME) -def parse_args(conf_file:str): +def parse_args(conf_file:str): # pylint: disable=missing-function-docstring parser = ArgumentParser(description=f'{defs.STAC_DESCRIPTION} ({defs.STAC_ACRONYM}). Must be root to run this program.') parser.add_argument('-f', '--conf-file', action='store', help='Configuration file (default: %(default)s)', default=conf_file, type=str, metavar='FILE') parser.add_argument('-s', '--syslog', action='store_true', help='Send messages to syslog instead of stdout. Use this when running %(prog)s as a daemon. (default: %(default)s)', default=False) @@ -57,7 +58,7 @@ sys.exit(0) if ARGS.idl: - with open(ARGS.idl, 'w') as f: + with open(ARGS.idl, 'w') as f: # pylint: disable=unspecified-encoding print(f'{DBUS_IDL}', file=f) sys.exit(0) @@ -92,7 +93,7 @@ NVME_ROOT.log_level("debug" if (ARGS.tron or CNF.tron) else "err") NVME_HOST = nvme.host(NVME_ROOT, SYS_CNF.hostnqn, SYS_CNF.hostid, SYS_CNF.hostsymname) # Singleton -def set_loglevel(tron): +def set_loglevel(tron): # pylint: disable=missing-function-docstring stas.trace_control(tron) NVME_ROOT.log_level("debug" if tron else "err") @@ -156,11 +157,15 @@ return json.dumps(info) def controller_info(self, transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) -> str: # pylint: disable=too-many-arguments,no-self-use + ''' @brief D-Bus method used to return information about a controller + ''' controller = STAC.get_controller(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) return json.dumps(controller.info()) if controller else '{}' def list_controllers(self, detailed) -> str: # pylint: disable=no-self-use - return json.dumps([ controller.details() if detailed else controller.controller_id_dict() for controller in STAC.get_controllers() ]) + ''' @brief Return the list of I/O controller IDs + ''' + return [ controller.details() if detailed else controller.controller_id_dict() for controller in STAC.get_controllers() ] #=========================================================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/stafctl.py new/nvme-stas-1.0/stafctl.py --- old/nvme-stas-1.0~rc5/stafctl.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/stafctl.py 2022-04-05 20:01:26.000000000 +0200 @@ -7,59 +7,79 @@ # # Authors: Martin Belanger <martin.belan...@dell.com> # +''' STorage Appliance Finder Control Utility +''' import sys import json import pprint +from argparse import ArgumentParser import dasbus.error from dasbus.connection import SystemMessageBus -from argparse import ArgumentParser from staslib import defs -def tron(args): +def tron(args): # pylint: disable=unused-argument + ''' @brief Trace ON + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_PATH) iface.tron = True # pylint: disable=assigning-non-slot -def troff(args): +def troff(args): # pylint: disable=unused-argument + ''' @brief Trace OFF + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_PATH) iface.tron = False # pylint: disable=assigning-non-slot -def _extract_cid(ctrl): +def _extract_cid(ctrl): # pylint: disable=missing-function-docstring return ctrl['transport'], ctrl['traddr'], ctrl['trsvcid'], ctrl['host-traddr'], ctrl['host-iface'], ctrl['subsysnqn'] -def status(args): +def status(args): # pylint: disable=unused-argument + ''' @brief retrieve stafd's status information + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_PATH) info = json.loads(iface.process_info()) - info['controllers'] = json.loads(iface.list_controllers(True)) + info['controllers'] = iface.list_controllers(True) for controller in info['controllers']: transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn = _extract_cid(controller) - controller['log_pages'] = json.loads(iface.get_log_pages(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn)) + controller['log_pages'] = iface.get_log_pages(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) controller.update(json.loads(iface.controller_info(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn))) print(pprint.pformat(info, width=120)) def ls(args): + ''' @brief list the discovery controller's that stafd is + connected (or trying to connect) to. + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_PATH) - info = json.loads(iface.list_controllers(args.detailed)) + info = iface.list_controllers(args.detailed) print(pprint.pformat(info, width=120)) def dlp(args): + ''' @brief retrieve a controller's discovery log pages from stafd + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_PATH) - info = json.loads(iface.get_log_pages(args.transport, args.traddr, args.trsvcid, args.host_traddr, args.host_iface, args.nqn)) + info = iface.get_log_pages(args.transport, args.traddr, args.trsvcid, args.host_traddr, args.host_iface, args.nqn) print(pprint.pformat(info, width=120)) def adlp(args): + ''' @brief retrieve all of the controller's discovery log pages from stafd + ''' bus = SystemMessageBus() iface = bus.get_proxy(defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_PATH) info = json.loads(iface.get_all_log_pages(args.detailed)) print(pprint.pformat(info, width=120)) PARSER = ArgumentParser(description=f'{defs.STAF_DESCRIPTION} ({defs.STAF_ACRONYM})') -PARSER.add_argument('-v', '--version', action='store_true', help='Print version, then exit', default=False) +PARSER.add_argument( + '-v', '--version', + action='store_true', + help='Print version, then exit', + default=False +) SUBPARSER = PARSER.add_subparsers(title='Commands') @@ -73,20 +93,67 @@ PRSR.set_defaults(func=status) PRSR = SUBPARSER.add_parser('ls', help='List discovery controllers') -PRSR.add_argument('-d', '--detailed', action='store_true', help='Print detailed info (default: "%(default)s")', default=False) +PRSR.add_argument( + '-d', '--detailed', + action='store_true', + help='Print detailed info (default: "%(default)s")', + default=False +) PRSR.set_defaults(func=ls) PRSR = SUBPARSER.add_parser('dlp', help='Show discovery log pages') -PRSR.add_argument('-t', '--transport', metavar='<trtype>', action='store', help='NVMe-over-Fabrics fabric type (default: "%(default)s")', choices=['tcp', 'rdma', 'fc', 'loop'], default='tcp') -PRSR.add_argument('-a', '--traddr', metavar='<traddr>', action='store', help='Discovery Controller\'s network address', required=True) -PRSR.add_argument('-s', '--trsvcid', metavar='<trsvcid>', action='store', help='Transport service id (for IP addressing, e.g. tcp, rdma, this field is the port number)', required=True) -PRSR.add_argument('-w', '--host-traddr', metavar='<traddr>', action='store', help='Network address used on the host to connect to the Controller (default: "%(default)s")', default='') -PRSR.add_argument('-f', '--host-iface', metavar='<iface>', action='store', help='This field specifies the network interface used on the host to connect to the Controller (default: "%(default)s")', default='') -PRSR.add_argument('-n', '--nqn', metavar='<nqn>', action='store', help='This field specifies the discovery controller\'s NQN. When not specified this option defaults to "%(default)s"', default='nqn.2014-08.org.nvmexpress.discovery') +PRSR.add_argument( + '-t', '--transport', + metavar='<trtype>', + action='store', + help='NVMe-over-Fabrics fabric type (default: "%(default)s")', + choices=['tcp', 'rdma', 'fc', 'loop'], + default='tcp' +) +PRSR.add_argument( + '-a', '--traddr', + metavar='<traddr>', + action='store', + help='Discovery Controller\'s network address', + required=True +) +PRSR.add_argument( + '-s', '--trsvcid', + metavar='<trsvcid>', + action='store', + help='Transport service id (for IP addressing, e.g. tcp, rdma, this field is the port number)', + required=True +) +PRSR.add_argument( + '-w', '--host-traddr', + metavar='<traddr>', + action='store', + help='Network address used on the host to connect to the Controller (default: "%(default)s")', + default='' +) +PRSR.add_argument( + '-f', '--host-iface', + metavar='<iface>', + action='store', + help='This field specifies the network interface used on the host to connect to the Controller (default: "%(default)s")', + default='' +) +PRSR.add_argument( + '-n', '--nqn', + metavar='<nqn>', + action='store', + help='This field specifies the discovery controller\'s NQN. When not specified this option defaults to "%(default)s"', + default='nqn.2014-08.org.nvmexpress.discovery' +) PRSR.set_defaults(func=dlp) PRSR = SUBPARSER.add_parser('adlp', help='Show all discovery log pages') -PRSR.add_argument('-d', '--detailed', action='store_true', help='Print detailed info (default: "%(default)s")', default=False) +PRSR.add_argument( + '-d', '--detailed', + action='store_true', + help='Print detailed info (default: "%(default)s")', + default=False +) PRSR.set_defaults(func=adlp) ARGS = PARSER.parse_args() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/stafd.py new/nvme-stas-1.0/stafd.py --- old/nvme-stas-1.0~rc5/stafd.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/stafd.py 2022-04-05 20:01:26.000000000 +0200 @@ -13,6 +13,7 @@ from argparse import ArgumentParser from staslib import defs +# pylint: disable=consider-using-f-string DBUS_IDL = ''' <node> <interface name="%s.debug"> @@ -35,7 +36,7 @@ <interface name="%s"> <method name="list_controllers"> <arg direction="in" type="b" name="detailed"/> - <arg direction="out" type="s" name="controller_list_json"/> + <arg direction="out" type="aa{ss}" name="controller_list"/> </method> <method name="get_log_pages"> <arg direction="in" type="s" name="transport"/> @@ -44,7 +45,7 @@ <arg direction="in" type="s" name="host_traddr"/> <arg direction="in" type="s" name="host_iface"/> <arg direction="in" type="s" name="subsysnqn"/> - <arg direction="out" type="s" name="log_pages_json"/> + <arg direction="out" type="aa{ss}" name="log_pages"/> </method> <method name="get_all_log_pages"> <arg direction="in" type="b" name="detailed"/> @@ -63,7 +64,7 @@ </node> ''' % (defs.STAFD_DBUS_NAME, defs.STAFD_DBUS_NAME) -def parse_args(conf_file:str): +def parse_args(conf_file:str): # pylint: disable=missing-function-docstring parser = ArgumentParser(description=f'{defs.STAF_DESCRIPTION} ({defs.STAF_ACRONYM}). Must be root to run this program.') parser.add_argument('-f', '--conf-file', action='store', help='Configuration file (default: %(default)s)', default=conf_file, type=str, metavar='FILE') parser.add_argument('-s', '--syslog', action='store_true', help='Send messages to syslog instead of stdout. Use this when running %(prog)s as a daemon. (default: %(default)s)', default=False) @@ -79,7 +80,7 @@ sys.exit(0) if ARGS.idl: - with open(ARGS.idl, 'w') as f: + with open(ARGS.idl, 'w') as f: # pylint: disable=unspecified-encoding print(f'{DBUS_IDL}', file=f) sys.exit(0) @@ -115,7 +116,7 @@ NVME_ROOT.log_level("debug" if (ARGS.tron or CNF.tron) else "err") NVME_HOST = nvme.host(NVME_ROOT, SYS_CNF.hostnqn, SYS_CNF.hostid, SYS_CNF.hostsymname) # Singleton -def set_loglevel(tron): +def set_loglevel(tron): # pylint: disable=missing-function-docstring stas.trace_control(tron) NVME_ROOT.log_level("debug" if tron else "err") @@ -234,7 +235,7 @@ self._get_log_op.run_async() #-------------------------------------------------------------------------- - def _on_registration_success(self, op_obj, data): + def _on_registration_success(self, op_obj, data): # pylint: disable=unused-argument ''' @brief Function called when we successfully register with the Discovery Controller. See self._register_op object for details. @@ -314,10 +315,9 @@ __dbus_xml__ = DBUS_IDL @dasbus.server.interface.dbus_signal - def log_pages_changed(self, transport:str, traddr:str, trsvcid:str, host_traddr:str, host_iface:str, subsysnqn:str, device:str): + def log_pages_changed(self, transport:str, traddr:str, trsvcid:str, host_traddr:str, host_iface:str, subsysnqn:str, device:str): # pylint: disable=too-many-arguments ''' @brief Signal sent when log pages have changed. ''' - pass @property def tron(self): @@ -346,14 +346,20 @@ return json.dumps(info) def controller_info(self, transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) -> str: # pylint: disable=no-self-use,too-many-arguments + ''' @brief D-Bus method used to return information about a controller + ''' controller = STAF.get_controller(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) return json.dumps(controller.info()) if controller else '{}' def get_log_pages(self, transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) -> str: # pylint: disable=no-self-use,too-many-arguments + ''' @brief D-Bus method used to retrieve the discovery log pages from one controller + ''' controller = STAF.get_controller(transport, traddr, trsvcid, host_traddr, host_iface, subsysnqn) - return json.dumps(controller.log_pages()) if controller else '[]' + return controller.log_pages() if controller else '[]' def get_all_log_pages(self, detailed) -> str: # pylint: disable=no-self-use + ''' @brief D-Bus method used to retrieve the discovery log pages from all controllers + ''' log_pages = list() for controller in STAF.get_controllers(): log_pages.append({'discovery-controller': controller.details() if detailed else controller.controller_id_dict(), @@ -363,7 +369,7 @@ def list_controllers(self, detailed) -> str: # pylint: disable=no-self-use ''' @brief Return the list of discovery controller IDs ''' - return json.dumps([ controller.details() if detailed else controller.controller_id_dict() for controller in STAF.get_controllers() ]) + return [ controller.details() if detailed else controller.controller_id_dict() for controller in STAF.get_controllers() ] #=========================================================================== @@ -410,10 +416,17 @@ return GLib.SOURCE_CONTINUE def log_pages_changed(self, controller, device): + ''' @brief Function invoked when a controller's cached log pages + have changed. This will emit a D-Bus signal to inform + other applications that the cached log pages have changed. + ''' self._dbus_iface.log_pages_changed.emit(controller.tid.transport, controller.tid.traddr, controller.tid.trsvcid, controller.tid.host_traddr, controller.tid.host_iface, controller.tid.subsysnqn, device) def referrals_changed(self): + ''' @brief Function invoked when a controller's cached referrals + have changed. + ''' LOG.debug('Staf.referrals_changed()') self._cfg_soak_tmr.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/stasadm.py new/nvme-stas-1.0/stasadm.py --- old/nvme-stas-1.0~rc5/stasadm.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/stasadm.py 2022-04-05 20:01:26.000000000 +0200 @@ -7,6 +7,7 @@ # # Authors: Martin Belanger <martin.belan...@dell.com> # +''' STorage Appliance Services Admin Tool ''' import os import sys import uuid @@ -22,9 +23,9 @@ hashlib = None -def read_from_file(fname, size): +def read_from_file(fname, size): # pylint: disable=missing-function-docstring try: - with open(fname) as f: + with open(fname) as f: # pylint: disable=unspecified-encoding data = f.read(size) if len(data) == size: return data @@ -59,8 +60,8 @@ if not data: return None - m = hmac.new(app_id, uuid.UUID(data).bytes, hashlib.sha256) - id128_bytes = m.digest()[0:16] + 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(): @@ -87,8 +88,16 @@ 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: + with open(fname, 'w') as f: # pylint: disable=unspecified-encoding print(string, file=f) if conf_file: @@ -110,24 +119,32 @@ else: config.remove_option(section, option) - with open(conf_file, 'w') as f: + 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(): +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='/etc/stas/sys.conf', type=str, metavar='FILE') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/staslib/avahi.py new/nvme-stas-1.0/staslib/avahi.py --- old/nvme-stas-1.0~rc5/staslib/avahi.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/staslib/avahi.py 2022-04-05 20:01:26.000000000 +0200 @@ -35,7 +35,7 @@ return the_dict #******************************************************************************* -class Avahi(): +class Avahi(): # pylint: disable=too-many-instance-attributes ''' @brief Avahi Server proxy. Set up the D-Bus connection to the Avahi daemon and register to be notified when services of a certain type (stype) are discovered or lost. @@ -113,6 +113,8 @@ self._avahi_watcher.connect_once_available() def kill(self): + ''' @brief Clean up object + ''' self._logger.debug('Avahi.kill()') self._kick_avahi_tmr.kill() @@ -136,10 +138,12 @@ self._sysbus = None def info(self) -> dict: + ''' @brief return debug info about this object + ''' services = dict() for service, obj in self._services.items(): interface, protocol, name, stype, domain = service - key = '({}, {}, {}.{}, {})'.format(socket.if_indextoname(interface), Avahi.protos.get(protocol, 'unknown'), name, domain, stype) + key = '({}, {}, {}.{}, {})'.format(socket.if_indextoname(interface), Avahi.protos.get(protocol, 'unknown'), name, domain, stype) # pylint: disable=consider-using-f-string services[key] = obj.get('data', {}) info = { @@ -298,11 +302,13 @@ self._change_cb() - def _service_identified(self, _connection, _sender_name:str, _object_path:str, _interface_name:str, _signal_name:str, args:typing.Tuple[int, int, str, str, str, str, int, str, int, list, int], *_user_data): + def _service_identified(self, _connection, _sender_name:str, _object_path:str, _interface_name:str, + _signal_name:str, args:typing.Tuple[int, int, str, str, str, str, int, str, int, list, int], *_user_data): (interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags) = args txt = txt2dict(txt) self._logger.debug('Avahi._service_identified() - interface=%s (%s), protocol=%s, stype=%s, domain=%s, flags=%s %-14s name=%s, host=%s, aprotocol=%s, address=%s, port=%s, txt=%s', - interface, socket.if_indextoname(interface), Avahi.protocol_as_string(protocol), stype, domain, flags, '(' + Avahi.result_flags_as_string(flags) + '),', name, host, Avahi.protocol_as_string(aprotocol), address, port, txt) + interface, socket.if_indextoname(interface), Avahi.protocol_as_string(protocol), stype, domain, flags, '(' + Avahi.result_flags_as_string(flags) + '),', + name, host, Avahi.protocol_as_string(aprotocol), address, port, txt) service = (interface, protocol, name, stype, domain) if service in self._services: @@ -315,7 +321,8 @@ } self._change_cb() - def _failure_handler(self, _connection, _sender_name:str, _object_path:str, interface_name:str, _signal_name:str, args:typing.Tuple[str], *_user_data): + def _failure_handler(self, _connection, _sender_name:str, _object_path:str, interface_name:str, + _signal_name:str, args:typing.Tuple[str], *_user_data): (error,) = args if 'ServiceResolver' not in interface_name or 'TimeoutError' not in error: # ServiceResolver may fire a timeout event after being Free'd(). This seems to be normal. self._logger.error('Avahi._failure_handler() - name=%s, error=%s', interface_name, error) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/staslib/defs.py new/nvme-stas-1.0/staslib/defs.py --- old/nvme-stas-1.0~rc5/staslib/defs.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/staslib/defs.py 2022-04-05 20:01:26.000000000 +0200 @@ -8,6 +8,8 @@ ''' @brief This file gets automagically configured by meson at build time. ''' +from staslib.version import KernelVersion + VERSION = '@VERSION@' LICENSE = '@LICENSE@' PROJECT_NAME = '@PROJECT_NAME@' @@ -29,5 +31,5 @@ STAFD_EXECUTABLE = '@STAFD_EXECUTABLE@' STAFD_CONFIG_FILE = '@STAFD_CONFIG_FILE@' -KERNEL_IFACE_MIN_VERSION = '@KERNEL_IFACE_MIN_VERSION@' -KERNEL_TP8013_MIN_VERSION = '@KERNEL_TP8013_MIN_VERSION@' +KERNEL_IFACE_MIN_VERSION = KernelVersion('@KERNEL_IFACE_MIN_VERSION@') +KERNEL_TP8013_MIN_VERSION = KernelVersion('@KERNEL_TP8013_MIN_VERSION@') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/staslib/glibudev.py new/nvme-stas-1.0/staslib/glibudev.py --- old/nvme-stas-1.0~rc5/staslib/glibudev.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/staslib/glibudev.py 2022-04-05 20:01:26.000000000 +0200 @@ -78,7 +78,7 @@ self.emit('device-event', device) -class MonitorObserver(gobject.GObject, _ObserverMixin): +class MonitorObserver(gobject.GObject, _ObserverMixin): # pylint: disable=too-few-public-methods """ An observer for device events integrating into the :mod:`glib` mainloop. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/staslib/meson.build new/nvme-stas-1.0/staslib/meson.build --- old/nvme-stas-1.0~rc5/staslib/meson.build 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/staslib/meson.build 2022-04-05 20:01:26.000000000 +0200 @@ -14,7 +14,7 @@ ) configured_files = [ defs_py ] -unconfigured_files = [ '__init__.py', 'avahi.py', 'glibudev.py', 'stas.py' ] +unconfigured_files = [ '__init__.py', 'avahi.py', 'glibudev.py', 'stas.py', 'version.py' ] python3.install_sources( unconfigured_files + configured_files, @@ -33,14 +33,15 @@ #=============================================================================== if libnvme_found modules_to_test = [ - [ 'staslib.avahi', 'avahi.py' ], - [ 'staslib.stas', 'stas.py' ], + [ 'staslib.avahi', 'avahi.py' ], + [ 'staslib.stas', 'stas.py' ], + [ 'staslib.version', 'version.py' ], ] foreach module: modules_to_test file = join_paths(meson.current_source_dir(), module[1]) if pylint.found() - test('pylint ' + module[0], pylint, args: ['--errors-only', '--rcfile=' + rcfile, file], env: test_env) + test('pylint ' + module[0], pylint, args: ['--rcfile=' + rcfile, file], env: test_env) endif if pyflakes.found() test('pyflakes ' + module[0], pyflakes, args: [file], env: test_env) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/staslib/stas.py new/nvme-stas-1.0/staslib/stas.py --- old/nvme-stas-1.0~rc5/staslib/stas.py 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/staslib/stas.py 2022-04-05 20:01:26.000000000 +0200 @@ -26,10 +26,12 @@ from .glibudev import MonitorObserver # pylint: disable=relative-beyond-top-level from gi.repository import Gio, GLib, GObject -from distutils.version import LooseVersion from libnvme import nvme +from staslib.version import KernelVersion from staslib import defs +DC_KATO_DEFAULT = 30 # seconds + #******************************************************************************* def check_if_allowed_to_continue(): ''' @brief Let's perform some basic checks before going too far. There are @@ -69,7 +71,7 @@ # Go back to standard syslog handler import logging.handlers # pylint: disable=import-outside-toplevel handler = logging.handlers.SysLogHandler(address="/dev/log") - handler.setFormatter(logging.Formatter('{}: %(message)s'.format(identifier))) + handler.setFormatter(logging.Formatter('{}: %(message)s'.format(identifier))) # pylint: disable=consider-using-f-string level = LG.INFO else: @@ -166,7 +168,7 @@ self._config = self.read_conf_file() @property - def conf_file(self): + def conf_file(self): # pylint: disable=missing-function-docstring return self._conf_file @property @@ -271,7 +273,7 @@ ''' return ['_nvme-disc._tcp'] if self.zeroconf_enabled() else list() - def zeroconf_enabled(self): + def zeroconf_enabled(self): # pylint: disable=missing-function-docstring return self.__get_value('Service Discovery', 'zeroconf')[0] == 'enabled' def read_conf_file(self): @@ -296,8 +298,8 @@ return value if value is not None else list() CNF = None # Singleton -def get_configuration(conf_file:str): - global CNF # pylint: disable=global-statement +def get_configuration(conf_file:str): # pylint: disable=missing-function-docstring + global CNF # pylint: disable=global-statement CNF = Configuration(conf_file) return CNF @@ -314,7 +316,7 @@ ''' self._config = self.read_conf_file() - def as_dict(self): + def as_dict(self): # pylint: disable=missing-function-docstring return { 'hostnqn': self.hostnqn, 'hostid': self.hostid, @@ -404,11 +406,11 @@ return None file = default_file - with open(file) as f: + with open(file) as f: # pylint: disable=unspecified-encoding return f.readline().split()[0] __SYS_CNF = None -def get_sysconf(): +def get_sysconf(): # pylint: disable=missing-function-docstring global __SYS_CNF # pylint: disable=global-statement if __SYS_CNF is None: __SYS_CNF = SysConfiguration('/etc/stas/sys.conf') # Singleton @@ -416,13 +418,13 @@ #******************************************************************************* -KERNEL_VERSION = platform.release() +KERNEL_VERSION = KernelVersion(platform.release()) class NvmeOptions(): + ''' Object used to read and cache contents of file /dev/nvme-fabrics. + Note that this file was not readable prior to Linux 5.16. + ''' def __init__(self): - ''' Read and cache contents of file /dev/nvme-fabrics. - Note that this file was not readable prior to Linux 5.16. - ''' # Supported options can be determined by looking at the kernel version # or by reading '/dev/nvme-fabrics'. The ability to read the options # from '/dev/nvme-fabrics' was only introduced in kernel 5.17, but may @@ -430,8 +432,8 @@ # version meets the minimum version for that option, then we don't # even need to read '/dev/nvme-fabrics'. self._supported_options = { - 'discovery': LooseVersion(KERNEL_VERSION) >= LooseVersion(defs.KERNEL_TP8013_MIN_VERSION), - 'host_iface': LooseVersion(KERNEL_VERSION) >= LooseVersion(defs.KERNEL_IFACE_MIN_VERSION), + 'discovery': KERNEL_VERSION >= defs.KERNEL_TP8013_MIN_VERSION, + 'host_iface': KERNEL_VERSION >= defs.KERNEL_IFACE_MIN_VERSION, } # If some of the options are False, we need to check wether they can be @@ -440,7 +442,7 @@ # backported to that kernel. if not all(self._supported_options.values()): # At least one option is False. try: - with open('/dev/nvme-fabrics') as f: + with open('/dev/nvme-fabrics') as f: # pylint: disable=unspecified-encoding options = [ option.split('=')[0].strip() for option in f.readlines()[0].rstrip('\n').split(',') ] except PermissionError: # Must be root to read this file raise @@ -467,7 +469,7 @@ return self._supported_options['host_iface'] __NVME_OPTIONS = None -def get_nvme_options(): +def get_nvme_options(): # pylint: disable=missing-function-docstring global __NVME_OPTIONS # pylint: disable=global-statement if __NVME_OPTIONS is None: __NVME_OPTIONS = NvmeOptions() @@ -477,7 +479,7 @@ class GTimer: ''' @brief Convenience class to wrap GLib timers ''' - def __init__(self, interval_sec:float=0, user_cback=lambda: GLib.SOURCE_REMOVE, *user_data, priority=GLib.PRIORITY_DEFAULT): + def __init__(self, interval_sec:float=0, user_cback=lambda: GLib.SOURCE_REMOVE, *user_data, priority=GLib.PRIORITY_DEFAULT): # pylint: disable=keyword-arg-before-vararg self._source = None self._interval_sec = float(interval_sec) self._user_cback = user_cback @@ -539,14 +541,20 @@ self._source.set_ready_time(0) # Expire now! def set_callback(self, user_cback, *user_data): + ''' @brief set the callback function to invoke when timer expires + ''' self._user_cback = user_cback self._user_data = user_data def set_timeout(self, new_interval_sec:float): + ''' @brief set the timer's duration + ''' if new_interval_sec >= 0: self._interval_sec = float(new_interval_sec) def get_timeout(self): + ''' @brief get the timer's duration + ''' return self._interval_sec def time_remaining(self) -> float: @@ -617,7 +625,9 @@ self._registry.pop(sys_name, None) break - def get_attributes(self, sys_name:str, attr_ids): + def get_attributes(self, sys_name:str, attr_ids) -> dict: + ''' @brief Get all the attributes associated with device @sys_name + ''' attrs = { attr_id: '' for attr_id in attr_ids } if sys_name: udev = self.get_nvme_device(sys_name) @@ -656,6 +666,10 @@ return None def find_nvme_ioc_device(self, tid): + ''' @brief Find the nvme device associated with the specified + I/O Controller. + @return The device if a match is found, None otherwise. + ''' for device in self._context.list_devices(subsystem='nvme', NVME_TRADDR=tid.traddr, NVME_TRSVCID=tid.trsvcid, NVME_TRTYPE=tid.transport): # Note: Prior to 5.18 linux didn't expose the cntrltype through # the sysfs. So, this may return None on older kernels. @@ -710,6 +724,8 @@ #******************************************************************************* def cid_from_dlpe(dlpe, host_traddr, host_iface): + ''' @brief Take a Discovery Log Page Entry and return a Controller ID as a dict. + ''' return { 'transport': dlpe['trtype'], 'traddr': dlpe['traddr'], @@ -721,6 +737,8 @@ #******************************************************************************* def blacklisted(blacklisted_ctrl_list, controller): + ''' @brief Check if @controller is black-listed. + ''' for blacklisted_ctrl in blacklisted_ctrl_list: test_results = [ val == controller.get(key, None) for key, val in blacklisted_ctrl.items() ] if all(test_results): @@ -729,6 +747,8 @@ #******************************************************************************* def remove_blacklisted(controllers:list): + ''' @brief Remove black-listed controllers from the list of controllers. + ''' blacklisted_ctrl_list = CNF.get_blacklist() if blacklisted_ctrl_list: LOG.debug('remove_blacklisted() - blacklisted_ctrl_list = %s', blacklisted_ctrl_list) @@ -737,6 +757,8 @@ #******************************************************************************* def remove_invalid_addresses(controllers:list): + ''' @brief Remove controllers with invalid addresses from the list of controllers. + ''' valid_controllers = list() for controller in controllers: # First, let's make sure that traddr is @@ -786,41 +808,41 @@ self._subsysnqn = cid.get('subsysnqn') self._key = (self._transport, self._traddr, self._trsvcid, self._host_traddr, self._host_iface, self._subsysnqn) self._hash = hash(self._key) - self._id = f'({self._transport}, {self._traddr}, {self._trsvcid}{", " + self._subsysnqn if self._subsysnqn else ""}{", " + self._host_iface if self._host_iface else ""}{", " + self._host_traddr if self._host_traddr else ""})' + self._id = f'({self._transport}, {self._traddr}, {self._trsvcid}{", " + self._subsysnqn if self._subsysnqn else ""}{", " + self._host_iface if self._host_iface else ""}{", " + self._host_traddr if self._host_traddr else ""})' # pylint: disable=line-too-long @property - def key(self): + def key(self): # pylint: disable=missing-function-docstring return self._key @property - def hash(self): + def hash(self): # pylint: disable=missing-function-docstring return self._hash @property - def transport(self): + def transport(self): # pylint: disable=missing-function-docstring return self._transport @property - def traddr(self): + def traddr(self): # pylint: disable=missing-function-docstring return self._traddr @property - def trsvcid(self): + def trsvcid(self): # pylint: disable=missing-function-docstring return self._trsvcid @property - def host_traddr(self): + def host_traddr(self): # pylint: disable=missing-function-docstring return self._host_traddr @property - def host_iface(self): + def host_iface(self): # pylint: disable=missing-function-docstring return self._host_iface @property - def subsysnqn(self): + def subsysnqn(self): # pylint: disable=missing-function-docstring return self._subsysnqn - def as_dict(self): + def as_dict(self): # pylint: disable=missing-function-docstring return { 'transport': self.transport, 'traddr': self.traddr, @@ -958,6 +980,9 @@ #******************************************************************************* class AsyncOperationWithRetry: # pylint: disable=too-many-instance-attributes + ''' Object used to manage an asynchronous GLib operation. The operation + can be cancelled or retried. + ''' def __init__(self, on_success_callback, on_failure_callback, operation, *op_args): ''' @param on_success_callback: Callback method invoked when @operation completes successfully @param on_failure_callback: Callback method invoked when @operation fails @@ -991,7 +1016,7 @@ def __str__(self): return str(self.as_dict()) - def as_dict(self): + def as_dict(self): # pylint: disable=missing-function-docstring info = { 'fail count': self._fail_cnt, } @@ -1005,10 +1030,14 @@ return info def cancel(self): + ''' @brief cancel async operation + ''' if not self._cancellable.is_cancelled(): self._cancellable.cancel() def kill(self): + ''' @brief kill and clean up this object + ''' self._release_resources() def run_async(self, *args): @@ -1021,6 +1050,10 @@ async_caller.communicate(self._cancellable, self._on_operation_complete, *args) def retry(self, interval_sec, *args): + ''' @brief Tell this object that the async operation is to be retried + in @interval_sec seconds. + + ''' if self._retry_tmr is None: self._retry_tmr = GTimer() self._retry_tmr.set_callback(self._on_retry_timeout, *args) @@ -1060,7 +1093,9 @@ #******************************************************************************* -class Controller: +class Controller: # pylint: disable=too-many-instance-attributes + ''' @brief Base class used to manage the connection to a controller. + ''' CONNECT_RETRY_PERIOD_SEC = 60 def __init__(self, root, host, tid:TransportId, discovery_ctrl=False): self._root = root @@ -1160,7 +1195,6 @@ host_traddr=self.tid.host_traddr if self.tid.host_traddr else None, host_iface=host_iface) self._ctrl.discovery_ctrl_set(self._discovery_ctrl) - self._ctrl.persistent_set(True) # Audit existing nvme devices. If we find a match, then # we'll just borrow that device instead of creating a new one. @@ -1177,6 +1211,16 @@ 'data_digest': CNF.data_digest } if CNF.kato is not None: cfg['keep_alive_tmo'] = CNF.kato + elif self._discovery_ctrl: + # All the connections to Controllers (I/O and Discovery) are + # persistent. Persistent connections MUST configure the KATO. + # The kernel already assigns a default 2-minute KATO to I/O + # controller connections, but it doesn't assign one to + # Discovery controller (DC) connections. Here we set the default + # DC connection KATO to match the default set by nvme-cli on + # persistent DC connections (i.e. 30 sec). + cfg['keep_alive_tmo'] = DC_KATO_DEFAULT + LOG.debug('Controller._try_to_connect() - %s Connecting to nvme control with cfg=%s', self.id, cfg) self._connect_op = AsyncOperationWithRetry(self._on_connect_success, self._on_connect_fail, self._ctrl.connect, self._host, cfg) @@ -1215,26 +1259,30 @@ LOG.debug('Controller._on_connect_fail() - %s Received event on dead object. %s', self.id, err) @property - def id(self) -> str: + def id(self) -> str: # pylint: disable=missing-function-docstring return str(self.tid) @property - def tid(self): + def tid(self): # pylint: disable=missing-function-docstring return self._tid @property - def device(self) -> str: + def device(self) -> str: # pylint: disable=missing-function-docstring return self._device if self._device else '' def controller_id_dict(self) -> dict: + ''' @brief return the controller ID as a dict. + ''' cid = self.tid.as_dict() cid['device'] = self.device return cid def details(self) -> dict: + ''' @brief return detailed debug info about this controller + ''' details = self.controller_id_dict() details.update(UDEV.get_attributes(self.device, ('hostid', 'hostnqn', 'model', 'serial'))) - details['connect attempts'] = self._connect_attempts + details['connect attempts'] = str(self._connect_attempts) details['retry connect timer'] = str(self._retry_connect_tmr) return details @@ -1257,6 +1305,8 @@ self._connect_op.cancel() def disconnect(self, disconnected_cb): + ''' @brief initiate a disconnect request with the controller + ''' LOG.info('%s | %s - Disconnect initiated', self.id, self.device) self._kill_ops() # Defer callback to the next main loop's idle period. @@ -1270,6 +1320,8 @@ #******************************************************************************* class Service: + ''' @brief Base class used to manage a STorage Appliance Service + ''' def __init__(self, reload_hdlr): self._loop = GLib.MainLoop() self._cancellable = Gio.Cancellable() @@ -1339,6 +1391,8 @@ return self._controllers.values() def get_controller(self, transport:str, traddr:str, trsvcid:str, host_traddr:str, host_iface:str, subsysnqn:str): # pylint: disable=too-many-arguments + ''' @brief get the specified controller object from the list of controllers + ''' cid = { 'transport': transport, 'traddr': traddr, @@ -1350,6 +1404,8 @@ return self._controllers.get(TransportId(cid)) def remove_controller(self, tid, device): + ''' @brief remove the specified controller object from the list of controllers + ''' LOG.debug('Service.remove_controller() - %s %s', tid, device) controller = self._controllers.pop(tid, None) if controller is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/staslib/version.py new/nvme-stas-1.0/staslib/version.py --- old/nvme-stas-1.0~rc5/staslib/version.py 1970-01-01 01:00:00.000000000 +0100 +++ new/nvme-stas-1.0/staslib/version.py 2022-04-05 20:01:26.000000000 +0200 @@ -0,0 +1,63 @@ +# 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.belan...@dell.com> +# +''' distutils (and hence LooseVersion) is being deprecated. None of the + suggested replacements (e.g. from pkg_resources import parse_version) quite + work with Linux kernel versions the way LooseVersion does. + + It was suggested to simply lift the LooseVersion code and vendor it in, + which is what this module is about. +''' + +import re + +class KernelVersion(): + ''' Code loosely lifted from distutils's LooseVersion + ''' + component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + + def __init__(self, string:str): + self.string = string + self.version = self.__parse(string) + + def __str__ (self): + return self.string + + def __repr__ (self): + return f'KernelVersion ("{self}")' + + def __eq__(self, other): + return self.version == self.__version(other) + + def __lt__(self, other): + return self.version < self.__version(other) + + def __le__(self, other): + return self.version <= self.__version(other) + + def __gt__(self, other): + return self.version > self.__version(other) + + def __ge__(self, other): + return self.version >= self.__version(other) + + @staticmethod + def __version(obj): + return obj.version if isinstance(obj, KernelVersion) else KernelVersion.__parse(obj) + + @staticmethod + def __parse(string): + components = [] + for item in KernelVersion.component_re.split(string): + if item and item != '.': + try: + components.append(int(item)) + except ValueError: + pass + + return components diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/subprojects/libnvme.wrap new/nvme-stas-1.0/subprojects/libnvme.wrap --- old/nvme-stas-1.0~rc5/subprojects/libnvme.wrap 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/subprojects/libnvme.wrap 2022-04-05 20:01:26.000000000 +0200 @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/linux-nvme/libnvme.git -revision = 131ee681a0e394b291cd407442886dbdf9fe124c +revision = 008a2dacdc0d29f57ec7cfd5bd6e89bf4d531081 [provide] libnvme = libnvme_dep diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nvme-stas-1.0~rc5/test/pylint.rc new/nvme-stas-1.0/test/pylint.rc --- old/nvme-stas-1.0~rc5/test/pylint.rc 2022-03-24 17:54:53.000000000 +0100 +++ new/nvme-stas-1.0/test/pylint.rc 2022-04-05 20:01:26.000000000 +0200 @@ -57,7 +57,7 @@ # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rd iv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call +disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rd iv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,use-list-literal,use-dict-literal,bad-option-value,R0801 # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -222,10 +222,10 @@ indent-string=' ' # Maximum number of characters on a single line. -max-line-length=100 +max-line-length=200 # Maximum number of lines in a module -max-module-lines=1000 +max-module-lines=2000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. @@ -241,7 +241,6 @@ # else. single-line-if-stmt=no - [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. @@ -301,7 +300,7 @@ function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ +good-names=i,j,k,ex,Run,_,op,ls,f,ip,id # Include a hint for the correct naming format with invalid-name include-naming-hint=no ++++++ nvme-stas.obsinfo ++++++ --- /var/tmp/diff_new_pack.u1rCZY/_old 2022-04-06 21:52:38.018785078 +0200 +++ /var/tmp/diff_new_pack.u1rCZY/_new 2022-04-06 21:52:38.018785078 +0200 @@ -1,5 +1,5 @@ name: nvme-stas -version: 1.0~rc5 -mtime: 1648140893 -commit: 52da2fc25d52d3eaaf8cbb8d697631ca348bf713 +version: 1.0 +mtime: 1649181686 +commit: dec6a5fff6bb1528ac9d840f432012a10e3b3217