Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package yast2-iscsi-client for 
openSUSE:Factory checked in at 2022-04-14 17:24:52
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yast2-iscsi-client (Old)
 and      /work/SRC/openSUSE:Factory/.yast2-iscsi-client.new.1941 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "yast2-iscsi-client"

Thu Apr 14 17:24:52 2022 rev:137 rq:969661 version:4.5.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/yast2-iscsi-client/yast2-iscsi-client.changes    
2022-01-27 23:17:23.894799362 +0100
+++ 
/work/SRC/openSUSE:Factory/.yast2-iscsi-client.new.1941/yast2-iscsi-client.changes
  2022-04-14 17:25:21.683256090 +0200
@@ -1,0 +2,13 @@
+Mon Apr 11 12:04:00 UTC 2022 - Stefan Hundhammer <shundham...@suse.com>
+
+- Use $PATH, not absolute paths for calling external programs
+  to allow for distros with or without usr-merge (/sbin -> /usr/sbin)
+  (bsc#1196086)
+- 4.5.1
+
+-------------------------------------------------------------------
+Wed Apr 06 13:24:58 UTC 2022 - Ladislav Slez??k <lsle...@suse.cz>
+
+- Bump version to 4.5.0 (bsc#1198109)
+
+-------------------------------------------------------------------

Old:
----
  yast2-iscsi-client-4.4.3.tar.bz2

New:
----
  yast2-iscsi-client-4.5.1.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ yast2-iscsi-client.spec ++++++
--- /var/tmp/diff_new_pack.U1NdPm/_old  2022-04-14 17:25:24.587259484 +0200
+++ /var/tmp/diff_new_pack.U1NdPm/_new  2022-04-14 17:25:24.591259489 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           yast2-iscsi-client
-Version:        4.4.3
+Version:        4.5.1
 Release:        0
 Summary:        YaST2 - iSCSI Client Configuration
 License:        GPL-2.0-only

++++++ yast2-iscsi-client-4.4.3.tar.bz2 -> yast2-iscsi-client-4.5.1.tar.bz2 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-iscsi-client-4.4.3/.rubocop.yml 
new/yast2-iscsi-client-4.5.1/.rubocop.yml
--- old/yast2-iscsi-client-4.4.3/.rubocop.yml   2022-01-27 17:31:24.000000000 
+0100
+++ new/yast2-iscsi-client-4.5.1/.rubocop.yml   2022-04-12 13:34:59.000000000 
+0200
@@ -2,6 +2,14 @@
 inherit_from:
   /usr/share/YaST2/data/devtools/data/rubocop_yast_style.yml
 
+AllCops:
+  # Do not complain about safe navigation operator (&.)
+  TargetRubyVersion: 2.3
+
+# this needs more testing if we can have frozen string literals
+Style/FrozenStringLiteralComment:
+  Enabled: false
+
 # This configuration was generated by
 # `rubocop --auto-gen-config`
 # on 2018-11-06 09:37:19 +0100 using RuboCop version 0.41.2.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/package/yast2-iscsi-client.changes 
new/yast2-iscsi-client-4.5.1/package/yast2-iscsi-client.changes
--- old/yast2-iscsi-client-4.4.3/package/yast2-iscsi-client.changes     
2022-01-27 17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/package/yast2-iscsi-client.changes     
2022-04-12 13:34:59.000000000 +0200
@@ -1,4 +1,17 @@
 -------------------------------------------------------------------
+Mon Apr 11 12:04:00 UTC 2022 - Stefan Hundhammer <shundham...@suse.com>
+
+- Use $PATH, not absolute paths for calling external programs
+  to allow for distros with or without usr-merge (/sbin -> /usr/sbin)
+  (bsc#1196086)
+- 4.5.1
+
+-------------------------------------------------------------------
+Wed Apr 06 13:24:58 UTC 2022 - Ladislav Slez??k <lsle...@suse.cz>
+
+- Bump version to 4.5.0 (bsc#1198109)
+
+-------------------------------------------------------------------
 Thu Jan 27 16:25:30 UTC 2022 - Steffen Winterfeldt <snw...@suse.com>
 
 - adjust to ruby 3.0 (bsc#1195226)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/package/yast2-iscsi-client.spec 
new/yast2-iscsi-client-4.5.1/package/yast2-iscsi-client.spec
--- old/yast2-iscsi-client-4.4.3/package/yast2-iscsi-client.spec        
2022-01-27 17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/package/yast2-iscsi-client.spec        
2022-04-12 13:34:59.000000000 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           yast2-iscsi-client
-Version:        4.4.3
+Version:        4.5.1
 Release:        0
 Summary:        YaST2 - iSCSI Client Configuration
 License:        GPL-2.0-only
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/src/clients/inst_iscsi-client.rb 
new/yast2-iscsi-client-4.5.1/src/clients/inst_iscsi-client.rb
--- old/yast2-iscsi-client-4.4.3/src/clients/inst_iscsi-client.rb       
2022-01-27 17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/src/clients/inst_iscsi-client.rb       
2022-04-12 13:34:59.000000000 +0200
@@ -65,7 +65,7 @@
 
       SCR.Execute(
         path(".target.bash"),
-        "/usr/bin/mkdir -p /etc/iscsi; /usr/bin/touch 
/etc/iscsi/initiatorname.iscsi; /usr/bin/ln -s /etc/iscsi/initiatorname.iscsi 
/etc/initiatorname.iscsi"
+        "mkdir -p /etc/iscsi; touch /etc/iscsi/initiatorname.iscsi; ln -s 
/etc/iscsi/initiatorname.iscsi /etc/initiatorname.iscsi"
       )
       # check initiator name, create if not exists
       IscsiClientLib.checkInitiatorName
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/src/clients/iscsi-client_finish.rb 
new/yast2-iscsi-client-4.5.1/src/clients/iscsi-client_finish.rb
--- old/yast2-iscsi-client-4.4.3/src/clients/iscsi-client_finish.rb     
2022-01-27 17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/src/clients/iscsi-client_finish.rb     
2022-04-12 13:34:59.000000000 +0200
@@ -76,7 +76,7 @@
         # write open-iscsi database of automatic connected targets
         WFM.Execute(
           path(".local.bash"),
-          "/usr/bin/test -d /etc/iscsi/ && " \
+          "test -d /etc/iscsi/ && " \
             "mkdir -p #{Installation.destdir.shellescape}/etc/iscsi && " \
             "cp -a /etc/iscsi/* #{Installation.destdir.shellescape}/etc/iscsi/"
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/src/include/iscsi-client/dialogs.rb 
new/yast2-iscsi-client-4.5.1/src/include/iscsi-client/dialogs.rb
--- old/yast2-iscsi-client-4.4.3/src/include/iscsi-client/dialogs.rb    
2022-01-27 17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/src/include/iscsi-client/dialogs.rb    
2022-04-12 13:34:59.000000000 +0200
@@ -410,7 +410,7 @@
       Wizard.HideBackButton
       ret = CWM.Run(
         w,
-        { :abort => fun_ref(method(:ReallyAbort), "boolean ()") }
+        abort: fun_ref(method(:ReallyAbort), "boolean ()")
       )
       ret
     end
@@ -448,7 +448,7 @@
 
       ret = CWM.Run(
         w,
-        { :abort => fun_ref(method(:ReallyAbort), "boolean ()") }
+        abort: fun_ref(method(:ReallyAbort), "boolean ()")
       )
       deep_copy(ret)
     end
@@ -473,7 +473,7 @@
       )
       ret = CWM.Run(
         w,
-        { :abort => fun_ref(method(:ReallyAbort), "boolean ()") }
+        abort: fun_ref(method(:ReallyAbort), "boolean ()")
       )
       deep_copy(ret)
     end
@@ -515,7 +515,7 @@
 
       ret = CWM.Run(
         w,
-        { :abort => fun_ref(method(:ReallyAbort), "boolean ()") }
+        abort: fun_ref(method(:ReallyAbort), "boolean ()")
       )
       deep_copy(ret)
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/src/modules/IscsiClientLib.rb 
new/yast2-iscsi-client-4.5.1/src/modules/IscsiClientLib.rb
--- old/yast2-iscsi-client-4.4.3/src/modules/IscsiClientLib.rb  2022-01-27 
17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/src/modules/IscsiClientLib.rb  2022-04-12 
13:34:59.000000000 +0200
@@ -2,7 +2,7 @@
 
 # |***************************************************************************
 # |
-# | Copyright (c) [2012] Novell, Inc.
+# | Copyright (c) [2012-2022] SUSE LLC
 # | All Rights Reserved.
 # |
 # | This program is free software; you can redistribute it and/or
@@ -15,10 +15,10 @@
 # | GNU General Public License for more details.
 # |
 # | You should have received a copy of the GNU General Public License
-# | along with this program; if not, contact Novell, Inc.
+# | along with this program; if not, contact SUSE LLC
 # |
 # | To contact Novell about this file by physical or electronic mail,
-# | you may find current contact information at www.novell.com
+# | you may find current contact information at www.suse.com
 # |
 # |***************************************************************************
 require "yast"
@@ -33,7 +33,12 @@
 
     Yast.import "Arch"
 
-    OFFLOAD_SCRIPT = "/sbin/iscsi_offload".freeze
+    # Script to configure iSCSI offload engines for use with open-iscsi
+    #
+    # Relying on our secure $PATH (set in y2start), not making assumptions
+    # if the binary is in /sbin or in /usr/sbin (usr-merge in openSUSE!)
+    # (bsc#1196086, bsc#1196086)
+    OFFLOAD_SCRIPT = "iscsi_offload".freeze
 
     def main
       textdomain "iscsi-client"
@@ -60,8 +65,17 @@
       @iscsid_socket_stat = false
       # status of iscsiuio.socket
       @iscsiuio_socket_stat = false
-      # main configuration file (/etc/iscsi/iscsid.conf)
+
+      # Content of the main configuration file (/etc/iscsi/iscsid.conf)
+      #
+      # Due to the way YaST ini-agent works, this variable follows the 
structure
+      # {"kind"=>"section", "type"=>-1, "value"=> Array<Hash> }
+      # in which that latter array of hashes represents the relevant entries 
in the
+      # configuration file.
+      #
+      # Use {#getConfig} and {#setConfig} to deal with its content in a 
convenient way.
       @config = {}
+
       # iBFT (iSCSI Boot Firmware Table)
       @ibft = nil
       # InitiatorName file (/etc/iscsi/initiatorname.iscsi)
@@ -71,6 +85,13 @@
       # interface type for hardware offloading
       @offload_card = "default"
 
+      # Types of offload cards
+      # [<id>, <label>, <matching_modules>, <load_modules>]
+      #
+      # matching_modules => used to identify if a given netcard in the system 
belongs to this type.
+      # That's the case if any of the modules used by the card (according to 
hwinfo) matches with
+      # any module from this list
+      # load_modules => modules to load if the given type of card is used
       @offload = [
         ["default", _("default (Software)"), [], []],
         ["all", _("all"), [], []],
@@ -87,6 +108,26 @@
         ["qed", "qede/qedi", ["qede", "qedi"], ["qedi"]]
       ]
 
+      # Network cards in the system that can be used as offload card, grouped 
by its type.
+      # This is a hash like this:
+      # { <idx> => [ <card1>, <card2>, ...], ...}
+      #
+      # idx => index of the type of card at @offload
+      # cardX => Array with 4 elements [ <iface>, <mac>, <iscsi>, <ipaddr> ]
+      #   iface => device name of the interface (eg. eth0)
+      #   mac => hardware address
+      #   iscsi => name of the open-iscsi interface definition in the style 
<iface>-<module>
+      #   ipaddr => IPv4 address
+      #
+      # Eg.
+      # {
+      #   2 => [ ["eth4", "00:11:22:33:44:55:66", "eth4-bnx2i", "191.212.2.2"] 
],
+      #   6 => [
+      #          ["eth0", "00:...:33", "eth0-be2iscsi", "12.16.0.1"],
+      #          ["eth1", "00:...:11", "eth1-be2iscsi", "19.2.20.1"]
+      #        ]
+      # }
+      #
       @offload_valid = nil
 
       @iscsid_socket = nil
@@ -178,20 +219,18 @@
     # @return [String] complete command
     #
     def GetAdmCmd(params, do_log = true)
-      ret = "LC_ALL=POSIX /sbin/iscsiadm"
-      ret = Ops.add(Ops.add(ret, " "), params)
+      ret = "LC_ALL=POSIX iscsiadm #{params}"
       Builtins.y2milestone("GetAdmCmd: #{ret}") if do_log
       ret
     end
 
     def hidePassword(orig)
-      orig = deep_copy(orig)
-      hidden = {}
-      Builtins.foreach(orig) do |key, value|
-        value = "*****" if Builtins.issubstring(key, "PASS")
-        Ops.set(hidden, key, value)
+      hidden = deep_copy(orig)
+      hidden.each do |key, _value|
+        next unless key.include?("PASS")
+        hidden[key] = "*****"
       end
-      deep_copy(hidden)
+      hidden
     end
 
     # Look for iSCSI boot firmware table (available only on special 
hardware/netcards)
@@ -240,7 +279,7 @@
           return @ibft
         end
         ret = SCR.Execute(path(".target.bash_output"),
-          "/usr/bin/lsmod | /usr/bin/grep -q iscsi_ibft || /usr/sbin/modprobe 
iscsi_ibft")
+          "lsmod | grep -q iscsi_ibft || modprobe iscsi_ibft")
         log.info "check and modprobe iscsi_ibft: #{ret}"
 
         @ibft = nodeInfoToMap(getFirmwareInfo)
@@ -275,24 +314,30 @@
       nil
     end
 
-    # read configuration file
+    # Current configuration
+    #
+    # Returns an array with all the entries of the configuration, each entry
+    # represented by hash that follows the structure of the YaST init-agent.
+    # See {#createMap}
+    #
+    # @return [Array<Hash>]
     def getConfig
       # use cache if available
-      if Builtins.size(@config) == 0
-        @config = Convert.convert(
-          SCR.Read(path(".etc.iscsid.all")),
-          :from => "any",
-          :to   => "map <string, any>"
-        )
+      if @config.empty?
+        @config = SCR.Read(path(".etc.iscsid.all"))
         Builtins.y2debug("read config %1", @config)
       end
-      Ops.get_list(@config, "value", [])
+
+      @config.fetch("value", [])
     end
 
+    # Updates the in-memory representation of the configuration
+    #
+    # @see #getConfig
+    #
+    # @param new_config [Array<Hash>]
     def setConfig(new_config)
-      new_config = deep_copy(new_config)
-      Ops.set(@config, "value", new_config)
-
+      @config["value"] = deep_copy(new_config)
       nil
     end
 
@@ -301,7 +346,7 @@
       isns_info = { "use" => false, "address" => "", "port" => "3205" }
       # validateISNS checks for not empty address and port,
       # storeISNS adds values to config
-      Builtins.foreach(getConfig) do |row|
+      getConfig.each do |row|
         if row["name"] == "isns.address"
           isns_info["address"] = row["value"]
           isns_info["use"] = true
@@ -372,45 +417,40 @@
       }
     end
 
-    # add or modify given map
+    # Modifies the value of the entry with the given name, creating a new 
entry if none exists
+    #
+    # @param old_list [Array<Hash>] list of maps in the format used by 
ini-agent (see {#createMap})
+    # @param key [String] name of the entry
+    # @param value [Object] new value for the entry
+    # @return [Array<Hash>] modified list of maps
     def setOrAdd(old_list, key, value)
-      old_list = deep_copy(old_list)
-      new_list = []
-      found = false
-      Builtins.foreach(old_list) do |row|
-        if Ops.get_string(row, "name", "") == key
-          found = true
-          Ops.set(row, "value", value)
-        end
-        new_list = Builtins.add(new_list, row)
-      end
-      if !found
-        new_list = Builtins.add(
-          new_list,
-          createMap({ "KEY" => key, "VALUE" => value }, [])
-        )
+      new_list = deep_copy(old_list)
+
+      element = new_list.find { |row| row["name"] == key }
+      if element
+        element["value"] = value
+      else
+        new_list << createMap({ "KEY" => key, "VALUE" => value }, [])
       end
-      deep_copy(new_list)
+
+      new_list
     end
 
-    # delete record with given key
+    # Deletes the entry with the given key
+    #
+    # @param old_list [Array<Hash>] list of maps in the format used by 
ini-agent (see {#createMap})
+    # @param key [String] name of the entry to be deleted
+    # @return [Array<Hash>] modified list of maps
     def delete(old_list, key)
-      old_list = deep_copy(old_list)
       Builtins.y2milestone("Delete record for %1", key)
-      new_list = []
-      Builtins.foreach(old_list) do |row|
-        if Ops.get_string(row, "name", "") != key
-          new_list = Builtins.add(new_list, row)
-        end
-      end
-      deep_copy(new_list)
+      deep_copy(old_list).reject { |row| row["name"] == key }
     end
 
     # temporary change config for discovery authentication
     def saveConfig(user_in, pass_in, user_out, pass_out)
       Builtins.y2milestone("Save config")
       tmp_conf = deep_copy(@config)
-      tmp_val = Ops.get_list(tmp_conf, "value", [])
+      tmp_val = tmp_conf["value"] || []
 
       if (specified = !user_in.empty? && !pass_in.empty?)
         tmp_val = setOrAdd(
@@ -468,6 +508,8 @@
     # Called for data (output) of commands:
     #  iscsiadm -m node -P 1
     #  iscsiadm -m session -P 1
+    #
+    # @param data [Array<String>] output of the executed command, one array 
entry per line
     def ScanDiscovered(data)
       data = deep_copy(data)
       ret = []
@@ -539,13 +581,13 @@
     end
 
     def restart_iscsid_initial
-      retcode = SCR.Execute(path(".target.bash"), "/usr/bin/pgrep iscsid")
+      retcode = SCR.Execute(path(".target.bash"), "pgrep iscsid")
       Service.Stop("iscsid") if retcode == 0
       start_iscsid_initial
     end
 
     def start_iscsid_initial
-      SCR.Execute(path(".target.bash"), "/usr/bin/pgrep iscsid || 
/sbin/iscsid")
+      SCR.Execute(path(".target.bash"), "pgrep iscsid || iscsid")
       10.times do |i|
         Builtins.sleep(1 * 1000)
         cmd = SCR.Execute(path(".target.bash_output"), GetAdmCmd("-m session"))
@@ -602,7 +644,7 @@
         Builtins.y2milestone("%1 file exists, create backup", file)
         SCR.Execute(
           path(".target.bash"),
-          Builtins.sformat("/usr/bin/mv %1 
/etc/iscsi/initiatorname.yastbackup", file.shellescape)
+          Builtins.sformat("mv %1 /etc/iscsi/initiatorname.yastbackup", 
file.shellescape)
         )
       end
       ret = SCR.Write(
@@ -627,7 +669,7 @@
     def getReverseDomainName
       host_fq = Hostname.SplitFQ(
         Ops.get_string(
-          SCR.Execute(path(".target.bash_output"), "/usr/bin/hostname -f | 
/usr/bin/tr -d '\n'"),
+          SCR.Execute(path(".target.bash_output"), "hostname -f | tr -d '\n'"),
           "stdout",
           ""
         )
@@ -658,7 +700,7 @@
         SCR.Execute(
           path(".target.bash_output"),
           Builtins.sformat(
-            "/usr/bin/grep -v '^#' %1 | /usr/bin/grep InitiatorName | 
/usr/bin/cut -d'=' -f2 | /usr/bin/tr -d '\n'",
+            "grep -v '^#' %1 | grep InitiatorName | cut -d'=' -f2 | tr -d 
'\n'",
             file.shellescape
           )
         ),
@@ -678,7 +720,7 @@
           output = SCR.Execute(
             path(".target.bash_output"),
             Builtins.sformat(
-              "/sbin/iscsi-iname -p iqn.%1.%2:01 | tr -d '\n'",
+              "iscsi-iname -p iqn.%1.%2:01 | tr -d '\n'",
               "`date +%Y-%m`",
               getReverseDomainName.shellescape
             ),
@@ -1222,24 +1264,20 @@
       ret = "default"
       retcode = SCR.Execute(path(".target.bash_output"), GetAdmCmd("-m node -P 
1"))
       ifaces = []
-      if Builtins.size(Ops.get_string(retcode, "stderr", "")) == 0
-        Builtins.foreach(
-          ScanDiscovered(
-            Builtins.splitstring(Ops.get_string(retcode, "stdout", ""), "\n")
-          )
-        ) do |s|
+      if retcode["stderr"].empty?
+        ScanDiscovered(retcode["stdout"].split("\n")).each do |s|
           sl = Builtins.splitstring(s, "  ")
-          if Ops.greater_than(Builtins.size(Ops.get(sl, 2, "")), 0) &&
-              !Builtins.contains(ifaces, Ops.get(sl, 2, ""))
-            ifaces = Builtins.add(ifaces, Ops.get(sl, 2, ""))
-          end
+          iface_name = sl[2] || ""
+          next if iface_name.empty? || ifaces.include?(iface_name)
+
+          ifaces << iface_name
         end
       end
       Builtins.y2milestone("InitOffloadCard ifaces:%1", ifaces)
-      if Ops.greater_than(Builtins.size(ifaces), 1)
+      if ifaces.size > 1
         ret = "all"
-      elsif Builtins.contains(@iface_eth, Ops.get(ifaces, 0, ""))
-        ret = Ops.get(ifaces, 0, "default")
+      elsif @iface_eth.include?(ifaces.first)
+        ret = ifaces.first || "default"
       end
       Builtins.y2milestone("InitOffloadCard ret:%1", ret)
       ret
@@ -1298,213 +1336,22 @@
 
     def GetOffloadItems
       init = false
-      if @offload_valid == nil
+      if @offload_valid.nil?
         init = true
         InitIfaceFile()
-        @offload_valid = {}
-        cards = Convert.convert(
-          SCR.Read(path(".probe.netcard")),
-          :from => "any",
-          :to   => "list <map>"
-        )
-
-        hw_mods = Builtins.maplist(cards) do |c|
-          Builtins.y2milestone("GetOffloadItems card:%1", c)
-          tmp = Builtins.maplist(Ops.get_list(c, "drivers", [])) do |m|
-            Builtins.flatten(Ops.get_list(m, "modules", []))
-          end
-          r = {
-            "modules" => Builtins.maplist(tmp) { |ml| Ops.get_string(ml, 0, 
"") },
-            "iface"   => Ops.get_string(c, "dev_name", ""),
-            "macaddr" => Ops.get_string(
-              c,
-              ["resource", "hwaddr", 0, "addr"],
-              ""
-            )
-          }
-          Builtins.y2milestone("GetOffloadItems cinf:%1", r)
-          deep_copy(r)
-        end # maplist(cards)
-
-        idx = 0
-        Builtins.foreach(@offload) do |l|
-          mod = Convert.convert(
-            Builtins.sort(Ops.get_list(l, 2, [])),
-            :from => "list",
-            :to   => "list <string>"
-          )
-          if Ops.greater_than(Builtins.size(mod), 0)
-            Builtins.foreach(hw_mods) do |hw|
-              if Ops.greater_than(
-                Builtins.size(
-                  Builtins::Multiset.intersection(
-                    mod,
-                    Convert.convert(
-                      Builtins.sort(Ops.get_list(hw, "modules", [])),
-                      :from => "list",
-                      :to   => "list <string>"
-                    )
-                  )
-                ),
-                0
-              )
-                Builtins.y2milestone("GetOffloadItems l:%1", l)
-                Builtins.y2milestone("GetOffloadItems valid:%1", hw)
-                Ops.set(
-                  @offload_valid,
-                  idx,
-                  Builtins.add(
-                    Ops.get(@offload_valid, idx, []),
-                    [
-                      Ops.get_string(hw, "iface", ""),
-                      Ops.get_string(hw, "macaddr", ""),
-                      Ops.add(
-                        Ops.add(Ops.get_string(hw, "iface", ""), "-"),
-                        Ops.get_string(l, [3, 0], "")
-                      )
-                    ]
-                  )
-                )
-              end
-            end
-          end
-          idx = Ops.add(idx, 1)
-        end
-        offload_res = {}
-        cmd = ""
-        Builtins.foreach(@offload_valid) do |i2, eth|
-          Ops.set(
-            @offload_valid,
-            i2,
-            Builtins.filter(
-              Convert.convert(eth, :from => "list", :to => "list <list>")
-            ) do |l|
-              cmd = "#{OFFLOAD_SCRIPT} #{Ops.get_string(l, 0, "").shellescape} 
| grep ..:..:..:.." # grep for lines containing MAC address
-              Builtins.y2milestone("GetOffloadItems cmd:%1", cmd)
-              out = Convert.to_map(
-                SCR.Execute(path(".target.bash_output"), cmd)
-              )
-              # Example for output if offload is supported on interface:
-              # cmd: iscsi_offload eth2
-              # out: $["exit":0, "stderr":"", "stdout":"00:00:c9:b1:bc:7f ip 
\n"]
-              result = SCR.Execute(
-                path(".target.bash_output"),
-                "#{OFFLOAD_SCRIPT} #{Ops.get_string(l, 0, "").shellescape}"
-              )
-              Builtins.y2milestone(
-                "GetOffloadItems iscsi_offload out:%1",
-                result
-              )
-              Ops.set(offload_res, Ops.get_string(l, 0, ""), {})
-              Ops.set(
-                offload_res,
-                [Ops.get_string(l, 0, ""), "exit"],
-                Ops.get_integer(out, "exit", 1)
-              )
-              sl = []
-              if Ops.get_integer(out, "exit", 1) == 0
-                sl = Builtins.splitstring(
-                  Ops.get_string(out, "stdout", ""),
-                  " \n"
-                )
-                Ops.set(
-                  offload_res,
-                  [Ops.get_string(l, 0, ""), "hwaddr"],
-                  Ops.get(sl, 0, "")
-                )
-                Ops.set(
-                  offload_res,
-                  [Ops.get_string(l, 0, ""), "ntype"],
-                  Ops.get(sl, 1, "")
-                )
-              end
-              Ops.get_integer(out, "exit", 1) == 0 &&
-                Ops.greater_than(Builtins.size(Ops.get(sl, 0, "")), 0)
-            end
-          )
-        end
-        Builtins.y2milestone("GetOffloadItems offload_res:%1", offload_res)
-        Builtins.y2milestone("GetOffloadItems offload_valid:%1", 
@offload_valid)
-        Builtins.foreach(@offload_valid) do |i2, eth|
-          Ops.set(
-            @offload_valid,
-            i2,
-            Builtins.maplist(
-              Convert.convert(eth, :from => "list", :to => "list <list>")
-            ) do |l|
-              Ops.set(
-                l,
-                1,
-                Ops.get_string(
-                  offload_res,
-                  [Ops.get_string(l, 0, ""), "hwaddr"],
-                  ""
-                )
-              )
-              deep_copy(l)
-            end
-          )
-        end
-        Builtins.y2milestone("GetOffloadItems offload_valid:%1", 
@offload_valid)
-        Builtins.foreach(@offload_valid) do |i2, eth|
-          Ops.set(
-            @offload_valid,
-            i2,
-            Builtins.maplist(eth) do |l|
-              cmd = "LC_ALL=POSIX /usr/bin/ifconfig " + Ops.get_string(l, 0, 
"").shellescape # FIXME: ifconfig is deprecated
-              Builtins.y2milestone("GetOffloadItems cmd:%1", cmd)
-              out = SCR.Execute(path(".target.bash_output"), cmd)
-              Builtins.y2milestone("GetOffloadItems out:%1", out)
-              # Search for lines containing "init addr", means IPv4 address.
-              # Regarding the IPv6 support there are no changes needed here 
because
-              # the IP address is not used farther.
-              line = Ops.get(
-                Builtins.filter(
-                  Builtins.splitstring(Ops.get_string(out, "stdout", ""), "\n")
-                ) { |ln| Builtins.search(ln, "inet addr:") != nil },
-                0,
-                ""
-              )
-              Builtins.y2milestone("GetOffloadItems line:%1", line)
-              ipaddr = "unknown"
-              if Ops.greater_than(Builtins.size(line), 0)
-                line = Builtins.substring(
-                  line,
-                  Ops.add(Builtins.search(line, "inet addr:"), 10)
-                )
-                Builtins.y2milestone("GetOffloadItems line:%1", line)
-                ipaddr = Builtins.substring(
-                  line,
-                  0,
-                  Builtins.findfirstof(line, " \t")
-                )
-              end
-              l = Builtins.add(l, ipaddr)
-              deep_copy(l)
-            end
-          )
-        end
-        Builtins.y2milestone("GetOffloadItems offload_valid:%1", 
@offload_valid)
+        InitOffloadValid()
       end
+
       entries = {}
-      Builtins.foreach(@offload_valid) do |i2, eth|
-        Builtins.foreach(
-          Convert.convert(eth, :from => "list", :to => "list <list>")
-        ) do |l|
-          if Ops.greater_than(Builtins.size(Ops.get_string(l, 0, "")), 0)
-            s = Ops.get_string(l, 0, "")
-            if Ops.greater_than(Builtins.size(Ops.get_string(l, 1, "")), 0)
-              s = Ops.add(Ops.add(s, " - "), Ops.get_string(l, 1, ""))
-            end
-            s = Ops.add(
-              Ops.add(s, " - "),
-              Ops.get_string(@offload, [i2, 1], "")
-            )
-            Ops.set(entries, Ops.get_string(l, 2, ""), s)
-          end
+      @offload_valid.each do |i, cards|
+        cards.each do |card|
+          next if card[0].nil? || card[0].empty?
+
+          entries[card[2]] = card_label(card, @offload[i][1])
         end
       end
       Builtins.y2milestone("GetOffloadItems entries:%1", entries)
+
       @iface_eth = Builtins.sort(Builtins.maplist(entries) { |e, _val| e })
       Builtins.y2milestone("GetOffloadItems eth:%1", @iface_eth)
       if init
@@ -1512,28 +1359,18 @@
         Builtins.y2milestone("GetOffloadItems offload_card:%1", @offload_card)
       end
       ret = [
-        Item(
-          Id(Ops.get_string(@offload, [0, 0], "")),
-          Ops.get_string(@offload, [0, 1], ""),
-          @offload_card == Ops.get_string(@offload, [0, 0], "")
-        )
+        # Entry for "default"
+        Item(Id(@offload[0][0]), @offload[0][1], @offload_card == 
@offload[0][0])
       ]
-      if Ops.greater_than(Builtins.size(@offload_valid), 0)
-        ret = Builtins.add(
-          ret,
-          Item(
-            Id(Ops.get_string(@offload, [1, 0], "")),
-            Ops.get_string(@offload, [1, 1], ""),
-            @offload_card == Ops.get_string(@offload, [1, 0], "")
-          )
+      if @offload_valid.any?
+        # Entry for "all"
+        ret << Item(
+          Id(@offload[1][0]), @offload[1][1], @offload_card == @offload[1][0]
         )
       end
-      ret = Convert.convert(
-        Builtins.merge(ret, Builtins.maplist(@iface_eth) do |e|
-          Item(Id(e), Ops.get(entries, e, ""), @offload_card == e)
-        end),
-        :from => "list",
-        :to   => "list <term>"
+      # Entries for the valid cards
+      ret.concat(
+        @iface_eth.map { |e| Item(Id(e), entries[e], @offload_card == e) }
       )
       Builtins.y2milestone("GetOffloadItems ret:%1", ret)
       deep_copy(ret)
@@ -1672,6 +1509,172 @@
     publish :function => :GetDiscoveryCmd, :type => "string (string, string, 
map)"
     publish :function => :getCurrentNodeValues, :type => "map <string, any> ()"
     publish :function => :iBFT?, :type => "boolean (map <string, any>)"
+
+  private
+
+    def InitOffloadValid
+      @offload_valid = potential_offload_cards
+      card_names = @offload_valid.values.flatten(1).map(&:first)
+      offload_res = configure_offload_engines(card_names)
+
+      # Filter only those cards for which we have a hwaddr value in offload_res
+      @offload_valid.values.each do |cards|
+        cards.select! do |card|
+          card_res = offload_res[card[0]]
+          card_res["exit"].zero? && card_res["hwaddr"]
+        end
+      end
+      Builtins.y2milestone("GetOffloadItems offload_res:%1", offload_res)
+      Builtins.y2milestone("GetOffloadItems offload_valid:%1", @offload_valid)
+
+      # Sync the MAC with the hwaddr value from offload_res
+      @offload_valid.values.each do |cards|
+        cards.each do |card|
+          dev_name = card[0]
+          card[1] = offload_res[dev_name]["hwaddr"]
+        end
+      end
+      Builtins.y2milestone("GetOffloadItems offload_valid:%1", @offload_valid)
+
+      @offload_valid.values.each do |cards|
+        cards.each do |card|
+          card << ip_addr(card[0])
+        end
+      end
+      Builtins.y2milestone("GetOffloadItems offload_valid:%1", @offload_valid)
+    end
+
+    # List of modules for the given card description
+    #
+    # The card description must be a hash in the format returned by the
+    # probe.netcard agent (ie. libhd, a.k.a. hwinfo).
+    #
+    # Such a netcard hash contains a "drivers" entry which is an array in which
+    # every element represents a driver.
+    #
+    # Such a driver is represented by a hash with the following structure:
+    # {
+    #   "active"   => [Boolean] whether the corresponding modules are already 
loaded
+    #   "modprobe" => [Boolean] whether modprobe or insmod is to be used for 
loading
+    #   "modules   => [Array<Array<String>>] list of modules, with this 
structure:
+    #                 [ [<modname1>, <modargs1>], [<modname2>, <modargs2>], 
... ]
+    # }
+    #
+    # @param card [Hash] description of a netcard as returned by the 
probe.netcard agent
+    # @return [Array<String>] names of modules
+    def netcard_modules(card)
+      card["drivers"].flat_map { |d| d["modules"].map(&:first) }
+    end
+
+    # Cards in the system that match with the types described by @offload
+    #
+    # return [Hash] cards grouped by type, in the same format used by 
@offload_valid
+    #   (with a small exception, the cards don't include a field for the IP 
address)
+    def potential_offload_cards
+      # Store into hw_mods information about all the cards in the system
+      cards = SCR.Read(path(".probe.netcard"))
+      hw_mods = cards.map do |c|
+        Builtins.y2milestone("GetOffloadItems card:%1", c)
+        hw_mod = {
+          "modules" => netcard_modules(c),
+          "iface"   => c["dev_name"] || "",
+          "macaddr" => Ops.get_string(c, ["resource", "hwaddr", 0, "addr"], "")
+        }
+        Builtins.y2milestone("GetOffloadItems cinf:%1", hw_mod)
+        hw_mod
+      end
+
+      result = {}
+      @offload.each.with_index do |offload_entry, idx|
+        modules = offload_entry[2]
+        # Ignore this offload entry if it does not specify any module to do 
the match
+        next if modules.empty?
+
+        hw_mods.each do |hw|
+          # Ignore this card unless it has some module in common with the 
offload entry
+          next if (modules & hw["modules"]).empty?
+
+          Builtins.y2milestone("GetOffloadItems l:%1", offload_entry)
+          Builtins.y2milestone("GetOffloadItems valid:%1", hw)
+          result[idx] ||= []
+          result[idx] << [
+            hw["iface"],
+            hw["macaddr"], # In fact, this is going to be overwritten at a 
later point
+            "#{hw["iface"]}-#{offload_entry[3].first}"
+          ]
+        end
+      end
+
+      result
+    end
+
+    # Configures iSCSI offload engines for the given cards
+    #
+    # Tries to create an open-iscsi interface definition for each of the given 
cards.
+    #
+    # @param cards [Array<String>] list of interface names
+    # @return [Hash{String => Hash}] results of the operation, keys are the 
names of the interfaces
+    #   and values the corresponding result as a hash with three fields: 
"exit" (0 means the
+    #   definition was created), "hwaddr" and "ntype".
+    def configure_offload_engines(cards)
+      offload_res = {}
+
+      cards.each do |dev_name|
+        cmd = "#{OFFLOAD_SCRIPT} #{dev_name.shellescape} | grep ..:..:..:.." # 
grep for lines containing MAC address
+        Builtins.y2milestone("GetOffloadItems cmd:%1", cmd)
+        out = SCR.Execute(path(".target.bash_output"), cmd)
+        # Example for output if offload is supported on interface:
+        # cmd: iscsi_offload eth2
+        # out: $["exit":0, "stderr":"", "stdout":"00:00:c9:b1:bc:7f ip \n"]
+        cmd2 = "#{OFFLOAD_SCRIPT} #{dev_name.shellescape}"
+        result = SCR.Execute(path(".target.bash_output"), cmd2)
+        Builtins.y2milestone("GetOffloadItems iscsi_offload out:%1", result)
+
+        offload_res[dev_name] = {}
+        offload_res[dev_name]["exit"] = out["exit"]
+        next unless out["exit"].zero?
+
+        sl = Builtins.splitstring(out["stdout"], " \n")
+        offload_res[dev_name]["hwaddr"] = sl[0]
+        offload_res[dev_name]["ntype"] = sl[1]
+      end
+
+      offload_res
+    end
+
+    # Current IP address of the given network interface
+    def ip_addr(dev_name)
+      cmd = "LC_ALL=POSIX ifconfig #{dev_name.shellescape}" # FIXME: ifconfig 
is deprecated
+      Builtins.y2milestone("GetOffloadItems cmd:%1", cmd)
+      out = SCR.Execute(path(".target.bash_output"), cmd)
+      Builtins.y2milestone("GetOffloadItems out:%1", out)
+
+      # Search for lines containing "init addr", means IPv4 address.
+      # Regarding the IPv6 support there are no changes needed here because
+      # the IP address is not used farther.
+      line = out["stdout"].split("\n").find do |ln|
+        Builtins.search(ln, "inet addr:") != nil
+      end
+      line ||= ""
+      Builtins.y2milestone("GetOffloadItems line:%1", line)
+
+      ipaddr = "unknown"
+      if !line.empty?
+        line = Builtins.substring(line, Builtins.search(line, "inet addr:") + 
10)
+        Builtins.y2milestone("GetOffloadItems line:%1", line)
+        ipaddr = Builtins.substring(line, 0, Builtins.findfirstof(line, " \t"))
+      end
+
+      ipaddr
+    end
+
+    def card_label(card, type_label)
+      dev_name = card[0]
+      hwaddr = card[1]
+      hwaddr = nil if hwaddr&.empty?
+
+      [dev_name, hwaddr, type_label].compact.join(" - ")
+    end
   end
 
   IscsiClientLib = IscsiClientLibClass.new
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/test/data/chroot1/etc/iscsi/ifaces/iface.example 
new/yast2-iscsi-client-4.5.1/test/data/chroot1/etc/iscsi/ifaces/iface.example
--- 
old/yast2-iscsi-client-4.4.3/test/data/chroot1/etc/iscsi/ifaces/iface.example   
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/yast2-iscsi-client-4.5.1/test/data/chroot1/etc/iscsi/ifaces/iface.example   
    2022-04-12 13:34:59.000000000 +0200
@@ -0,0 +1,24 @@
+#
+# Example iSCSI interface config
+#
+# There must be a separate iscsi interface config file for each NIC, network
+# interface or port or iscsi HBA you want to bind sessions to.
+#
+# For hardware iscsi, this is created for you when you run iscsiadm.
+# For software iscsi, you must define these files yourself.
+#
+
+# Here are some example iface files
+# IPV4 sample config file with static IP address:
+# BEGIN RECORD 2.0-872
+# iface.iscsi_ifacename = qla4xxx-8
+# iface.ipaddress = 192.168.1.75
+# iface.hwaddress = 00:0e:1e:04:93:92
+# iface.transport_name = qla4xxx
+# iface.bootproto = static
+# iface.subnet_mask = 255.255.255.0
+# iface.gateway = 192.168.1.1
+# iface.state = enable
+# iface.vlan = <empty>
+# iface.iface_num = 0
+# END RECORD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-iscsi-client-4.4.3/test/iscsi_client_lib_test.rb 
new/yast2-iscsi-client-4.5.1/test/iscsi_client_lib_test.rb
--- old/yast2-iscsi-client-4.4.3/test/iscsi_client_lib_test.rb  2022-01-27 
17:31:24.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/test/iscsi_client_lib_test.rb  2022-04-12 
13:34:59.000000000 +0200
@@ -1,6 +1,7 @@
 #!/usr/bin/env rspec
 
 require_relative "test_helper"
+require_relative "mocking"
 Yast.import "IscsiClientLib"
 
 describe Yast::IscsiClientLib do
@@ -92,11 +93,11 @@
 
       expect(Yast::SCR).to receive(:Execute).with(
         Yast::Path.new(".target.bash"),
-        "LC_ALL=POSIX /sbin/iscsiadm -m discovery -I eth0 -I eth1 -t st -p 
magic"
+        "LC_ALL=POSIX iscsiadm -m discovery -I eth0 -I eth1 -t st -p magic"
       )
       expect(Yast::SCR).to receive(:Execute).with(
         Yast::Path.new(".target.bash"),
-        "LC_ALL=POSIX /sbin/iscsiadm -m discovery -I eth0 -I eth1 -t st -p 
portal\\ 1"
+        "LC_ALL=POSIX iscsiadm -m discovery -I eth0 -I eth1 -t st -p portal\\ 
1"
       )
 
       subject.autoyastWrite
@@ -618,6 +619,184 @@
         expect(ibft_data).to eq({})
       end
     end
+  end
+
+  describe ".GetOffloadItems" do
+    around(:each) do |example|
+      # The directory /etc/iscsi/ifaces/ is always scanned to look for 
interface definitions,
+      # let's chroot the YaST agents so we can use an /etc directory with 
mocked content
+      root = File.join(File.dirname(__FILE__), "data", "chroot1")
+      change_scr_root(root, &example)
+    end
+
+    before do
+      # The agent .probe.netcard is used to inspect the network cards in the 
system, this
+      # intercepts that call and mocks the result based on the scenario we 
want to simulate
+      allow(Yast::SCR).to receive(:Read).and_call_original
+      allow(Yast::SCR).to receive(:Read).with(Yast::Path.new(".probe.netcard"))
+        .and_return probe_netcard
+
+      # iscsiadm is always called, mock it to find no active ISCSI interfaces 
by default
+      mock_iscsiadm_mode([])
+    end
+
+    # The structure of the YaST UI terms is quite tricky, so let's define a 
couple of functions
+    # to inspect their content in a readable way.
+
+    # Id for the given combo-box entry
+    #
+    # @param item [Yast::Term] term used to represent an Item for a ComboBox
+    def ui_item_id(item)
+      item.params.first.params.first
+    end
+
+    # @see #ui_term_id
+    def ui_item_label(item)
+      item.params[1]
+    end
+
+    # Define some simple checks to be reused in several scenarios
+
+    RSpec.shared_examples "returns UI items" do
+      it "returns an array of UI items" do
+        items = subject.GetOffloadItems
+        expect(items).to be_an(Array)
+        expect(items).to all be_a(Yast::Term)
+        expect(items.map(&:value)).to all eq(:item)
+      end
+    end
+
+    RSpec.shared_examples "only default" do
+      it "provides 'default' as the only item" do
+        items = subject.GetOffloadItems
+        expect(items.size).to eq 1
+        expect(ui_item_label(items.first)).to eq "default (Software)"
+        expect(ui_item_id(items.first)).to eq "default"
+      end
+    end
+
+    context "with no network cards in the system" do
+      let(:probe_netcard) { [] }
+
+      include_examples "returns UI items"
+      include_examples "only default"
+    end
+
+    context "with network cards not expected to support offloading" do
+      let(:probe_netcard) do
+        [
+          probed_card("enp0s1", "r8152", "12:34:56:78:90:ab"),
+          probed_card("enp5s6", "tg3", "23:45:67:89:0a:bc")
+        ]
+      end
+
+      include_examples "returns UI items"
+      include_examples "only default"
+    end
+
+    context "with several network cards that could support offloading" do
+      let(:probe_netcard) do
+        [
+          probed_card("p6p1_1", "qede",  "12:34:56:78:90:ab"),
+          probed_card("em1",    "bnx2x", "23:45:67:89:0a:bc"),
+          probed_card("p6p2_1", "qede",  "34:56:78:90:ab:cd"),
+          probed_card("em2",    "bnx2x", "45:67:89:0a:bc:de"),
+          probed_card("em3",    "bnx2x", "56:78:90:ab:cd:ef")
+        ]
+      end
+
+      context "if none of the cards support offloading" do
+        before do
+          mock_iscsi_offload("em1", false)
+          mock_iscsi_offload("em2", false)
+          mock_iscsi_offload("em3", false)
+          mock_iscsi_offload("p6p1_1", false)
+          mock_iscsi_offload("p6p2_1", false)
+        end
+
+        include_examples "returns UI items"
+
+        # NOTE: this is likely an unwanted behavior caused because
+        # @offload_valid == {2=>[], 7=>[]}
+        # which should be considered as an empty list but it's not
+        it "includes only 'default' and 'all'" do
+          labels = subject.GetOffloadItems.map { |i| ui_item_label(i) }
+          expect(labels).to contain_exactly("default (Software)", "all")
+        end
+      end
+
+      context "if some cards indeed support offloading" do
+        before do
+          mock_iscsi_offload("em1",    true, "23:45:67:89:0a:bc")
+          mock_iscsi_offload("em2",    false)
+          mock_iscsi_offload("em3",    true, "56:78:90:ab:cd:ef")
+          mock_iscsi_offload("p6p1_1", false)
+          mock_iscsi_offload("p6p2_1", true, "34:56:78:90:ab:cd")
+
+          mock_ifconfig("em1")
+          mock_ifconfig("em3")
+          mock_ifconfig("p6p2_1")
+        end
+
+        include_examples "returns UI items"
+
+        it "includes 'default', 'all' and an entry for each offload card" do
+          items = subject.GetOffloadItems
+
+          labels = items.map { |i| ui_item_label(i) }
+          expect(labels).to contain_exactly(
+            "default (Software)", "all", "em1 - 23:45:67:89:0a:bc - 
bnx2/bnx2i/bnx2x",
+            "em3 - 56:78:90:ab:cd:ef - bnx2/bnx2i/bnx2x", "p6p2_1 - 
34:56:78:90:ab:cd - qede/qedi"
+          )
 
+          ids = items.map { |i| ui_item_id(i) }
+          expect(ids).to contain_exactly("default", "all", "em1-bnx2i", 
"em3-bnx2i", "p6p2_1-qedi")
+        end
+
+        context "and ifconfig is not found" do
+          # NOTE: testing the state of internal variables should be out of the 
scope of unit tests,
+          # but we want to prove a point here (see next context right below)
+          it "sets the IPs in @offload_valid to 'unknown'" do
+            subject.GetOffloadItems
+            cards = 
subject.instance_variable_get("@offload_valid").values.flatten(1)
+            expect(cards.map(&:last)).to eq ["unknown", "unknown", "unknown"]
+          end
+        end
+
+        context "and ifconfig is found" do
+          before do
+            mock_ifconfig("em1",    "192.10.9.8")
+            mock_ifconfig("em3",    "")
+            mock_ifconfig("p6p2_1", "10.11.12.13")
+          end
+
+          # NOTE: this is likely an unwanted behavior caused by the fact that 
the code expects the
+          # output of ifconfig version 1.X, not the one in recent versions of 
(open)SUSE.
+          it "sets the IPs in @offload_valid to 'unknown'" do
+            subject.GetOffloadItems
+            cards = 
subject.instance_variable_get("@offload_valid").values.flatten(1)
+            expect(cards.map(&:last)).to eq ["unknown", "unknown", "unknown"]
+          end
+        end
+
+        context "and no active card is reported by iscsiadm " do
+          before { mock_iscsiadm_mode([]) }
+
+          it "pre-selects the item 'default'" do
+            selected = subject.GetOffloadItems.find { |i| i.params[2] }
+            expect(ui_item_id(selected)).to eq "default"
+          end
+        end
+
+        context "and one card is already associated to the first target" do
+          before { mock_iscsiadm_mode(["em3-bnx2i"]) }
+
+          it "selects by default the current offload card" do
+            selected = subject.GetOffloadItems.find { |i| i.params[2] }
+            expect(ui_item_id(selected)).to eq "em3-bnx2i"
+          end
+        end
+      end
+    end
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yast2-iscsi-client-4.4.3/test/mocking.rb 
new/yast2-iscsi-client-4.5.1/test/mocking.rb
--- old/yast2-iscsi-client-4.4.3/test/mocking.rb        1970-01-01 
01:00:00.000000000 +0100
+++ new/yast2-iscsi-client-4.5.1/test/mocking.rb        2022-04-12 
13:34:59.000000000 +0200
@@ -0,0 +1,117 @@
+# encoding: utf-8
+
+# |***************************************************************************
+# |
+# | Copyright (c) [2022] SUSE LLC
+# | All Rights Reserved.
+# |
+# | This program is free software; you can redistribute it and/or
+# | modify it under the terms of version 2 of the GNU General Public License as
+# | published by the Free Software Foundation.
+# |
+# | This program is distributed in the hope that it will be useful,
+# | but WITHOUT ANY WARRANTY; without even the implied warranty of
+# | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
+# | GNU General Public License for more details.
+# |
+# | You should have received a copy of the GNU General Public License
+# | along with this program; if not, contact SUSE LLC
+# |
+# | To contact Novell about this file by physical or electronic mail,
+# | you may find current contact information at www.suse.com
+# |
+# |***************************************************************************
+
+# Convenience functions to mock stuff in the unit tests
+
+# Description of a card to be used for mocking
+#
+# @return [Hash] with the same structure returned by the agent .probe.netcard
+def probed_card(dev_name, driver, mac)
+  {
+    "dev_name" => dev_name, "dev_names" => [dev_name],
+    "driver" => driver, "driver_module" => driver,
+    "drivers" => [{ "active" => true, "modprobe" => true, "modules" => 
[[driver, ""]] }],
+    "resource" => { "hwaddr" => [{ "addr" => mac }] }
+  }
+end
+
+# Mocks a call done with the .target.bash_output agent
+def mock_bash_out(command, result)
+  path = Yast::Path.new(".target.bash_output")
+  allow(Yast::SCR).to receive(:Execute).with(path, command).and_return result
+end
+
+# Mocks the call to the iscsi_offload command for a given interface
+#
+# @param card [String] name of the interface
+# @param success [Boolean] whether offloading is supported for the card
+# @param mac [String, nil] if success is true, mac addr to be included in the 
command output
+def mock_iscsi_offload(card, success, mac = nil)
+  result = { "stderr" => "" }
+  if success
+    result["exit"] = 0
+    result["stdout"] = "#{mac} none\n"
+  else
+    result["exit"] = 2
+    result["stdout"] = "iSCSI offloading not supported on interface #{card}\n"
+  end
+
+  mock_bash_out(/iscsi_offload #{card}/, result)
+end
+
+# Mocks a call to ifconfig done to check the IP of a concrete interface
+#
+# The behavior depends on the value of the 'ipaddr' argument:
+#
+# - If nil it works as if ifconfig is not installed, that's the default 
because that tool is
+#   only available in the package "net-tools-deprecated".
+# - If blank ("") it works as if the interface has no configured IP.
+# - In other case, it emulates the output of ifconfig for a configured card.
+#
+# Note this emulates the output of ifconfig-2.X in which the IP is represented 
as
+# "inet X.X.X.X". At the moment of writing, the yast2-iscsi-client code seems 
to be only
+# adapted to parse the output of the old ifconfig-1.X (which used "inet addr 
X.X.X.X").
+#
+# @param dev_name [String] interface name
+# @param ipaddr [String, nil]
+def mock_ifconfig(dev_name, ipaddr = nil)
+  result = { "exit" => 0, "stdout" => "", "stderr" => "" }
+
+  if ipaddr
+    result["stdout"] << "#{dev_name}: 
flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n"
+    if !ipaddr.empty?
+      result["stdout"] << "      inet #{ipaddr}  netmask 255.255.255.0  
broadcast 192.168.0.255\n"
+    end
+    result["stdout"] << <<EOF
+      RX packets 927138  bytes 920436197 (877.7 MiB)
+      TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
+      device interrupt 17
+EOF
+  else
+    result["exit"] = 127
+    result["stderr"] = "sh: /usr/bin/ifconfig: No such file or directory\n"
+  end
+
+  mock_bash_out(/ifconfig #{dev_name}/, result)
+end
+
+# Mocks a call to "iscsiadm --mode node -P 1"
+#
+# @param ifaces [Array<String>] list of active iSCSI interfaces defined at 
/etc/iscsi/ifaces
+def mock_iscsiadm_mode(ifaces)
+  result = { "exit" => 0, "stdout" => "", "stderr" => "" }
+
+  if ifaces.empty?
+    result["exit"] = 21
+    result["stderr"] = "iscsiadm: No records found\n"
+  end
+
+  ifaces.each do |iface|
+    result["stdout"] << "Target: iqn.2013-10.de.suse:test_file1\n"
+    result["stdout"] << "\tPortal: 192.168.99.99:3260,1\n"
+    result["stdout"] << "\t\tIface Name: #{iface}\n"
+  end
+
+  mock_bash_out(/iscsiadm -m node -P 1/, result)
+end

Reply via email to