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