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
 

Reply via email to