diff --git a/src/lib/network/edit_nic_name.rb b/src/lib/network/edit_nic_name.rb deleted file mode 100644 index b97263220..000000000 --- a/src/lib/network/edit_nic_name.rb +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may -# find current contact information at www.suse.com. - -require "yast" - -module Yast - Yast.import "UI" - Yast.import "LanItems" - Yast.import "Popup" - - # The class represents a simple dialog which allows user to input new NIC - # name. It also allows to select a device attribute (MAC, Bus id, ...) which will - # be used for device selection. - class EditNicName - include UIShortcuts - include I18n - - # @return [String] current udev name before modifying it - attr_reader :old_name - # @return [String] current udev match criteria - attr_reader :old_key - - # Constructor - # - # @param settings [Y2Network::InterfaceConfigBuilder] Interface configuration - def initialize(settings) - textdomain "network" - @settings = settings - interface = settings.interface - @old_name = interface.name - @old_key = interface.renaming_mechanism - @mac = interface.hardware.mac - @bus_id = interface.hardware.busid - end - - # Opens dialog for editing NIC name and runs event loop. - # - # @return [String] new NIC name - def run - open - - ret = nil - until [:cancel, :abort, :ok].include? ret - ret = UI.UserInput - - next if ret != :ok - - new_name = UI.QueryWidget(:dev_name, :Value) - udev_type = UI.QueryWidget(:udev_type, :CurrentButton) - - if CheckUdevNicName(new_name) - @settings.rename_interface(new_name, udev_type) - else - UI.SetFocus(:dev_name) - ret = nil - - next - end - end - - close - - new_name || old_name - end - - private - - # Opens dialog for editing NIC name - def open - UI.OpenDialog( - VBox( - Left( - HBox( - Label(_("Device Name:")), - InputField(Id(:dev_name), Opt(:hstretch), "", old_name) - ) - ), - VSpacing(0.5), - Frame( - _("Base Udev Rule On"), - RadioButtonGroup( - Id(:udev_type), - VBox( - # make sure there is enough space (#367239) - HSpacing(30), - Left( - RadioButton( - Id(:mac), - _("MAC address: %s") % @mac - ) - ), - Left( - RadioButton( - Id(:bus_id), - _("BusID: %s") % @bus_id - ) - ) - ) - ) - ), - VSpacing(0.5), - HBox( - PushButton(Id(:ok), Opt(:default), Label.OKButton), - PushButton(Id(:cancel), Label.CancelButton) - ) - ) - ) - - if old_key - UI.ChangeWidget(Id(:udev_type), :CurrentButton, old_key) - else - Builtins.y2error("Unknown udev rule.") - end - end - - # Closes the dialog - def close - UI.CloseDialog - end - - # Checks if given name can be accepted as nic's new one. - # - # Pops up an explanation if the name is invalid - # - # @return [boolean] false if name is invalid - def CheckUdevNicName(name) - # check if the name is assigned to another device already - if @settings.name_exists?(name) - Popup.Error(_("Configuration name already exists.")) - return false - end - - if !@settings.valid_name?(name) - Popup.Error(_("Invalid configuration name.")) - return false - end - - true - end - - # When an interface name has changed, it returns whether the user wants to - # update the interface name in the related routes or not. - # - # return [Boolean] whether the routes have to be updated or not - def update_routes?(previous_name) - return false unless Routing.device_routes?(previous_name) - - Popup.YesNoHeadline( - Label.WarningMsg, - # TRANSLATORS: Ask for fixing a possible conflict after renaming - # an interface, %s are the previous and current interface names - format(_("The interface %s has been renamed to %s. There are \n" \ - "some routes that still use the previous name.\n\n" \ - "Would you like to update them now?\n"), - "'#{previous_name}'", - "'#{LanItems.current_name}'") - ) - end - end -end diff --git a/src/lib/y2network/dialogs/rename_interface.rb b/src/lib/y2network/dialogs/rename_interface.rb new file mode 100644 index 000000000..379085c4a --- /dev/null +++ b/src/lib/y2network/dialogs/rename_interface.rb @@ -0,0 +1,96 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "cwm/popup" +require "y2network/widgets/custom_interface_name" +require "y2network/widgets/rename_hwinfo" + +module Y2Network + module Dialogs + # This dialog allows the user to rename a network interface + # + # Is works in a slightly different way depending on the interface. + # + # * For physical interfaces, it allows the user to select between using + # the MAC adddress or the Bus ID, which are present in the Hwinfo object + # associated to the interface. + # * For not connected interfaces ({FakeInterface}), as the hardware is not present, + # the user must specify the MAC or the Bus ID by hand (NOT IMPLEMENTED YET). + # * For virtual interfaces, like bridges, only the name can be chaned (no hardware + # info at all) (NOT IMPLEMENTED YET). + class RenameInterface < CWM::Popup + def initialize(builder) + textdomain "network" + + @builder = builder + @old_name = interface.name + end + + # Runs the dialog + def run + ret = super + return unless ret == :ok + renaming_mechanism = rename_hwinfo_widget.value unless virtual_interface? + @builder.rename_interface(name_widget.value, renaming_mechanism) + name_widget.value + end + + # @see CWM::CustomWidget + def contents + VBox( + Left(name_widget), + *hardware_info + ) + end + + private + + # Elements to display the hardware information + def hardware_info + virtual_interface? ? [Empty()] : [VSpacing(0.5), rename_hwinfo_widget] + end + + # Interface's name widget + # + # @return [Y2Network::Widgets::CustomInterfaceName] + def name_widget + @name_widget ||= Y2Network::Widgets::CustomInterfaceName.new(@builder) + end + + # Widget to select the hardware information to base the rename on + # + # @return [Y2Network::Widgets::RenameHwinfo] + def rename_hwinfo_widget + @rename_hwinfo_widget ||= Y2Network::Widgets::RenameHwinfo.new(@builder) + end + + # Returns the interface + # + # @return [Y2Network::Interface] + def interface + @builder.interface + end + + # Determines whether it is a virtual interface + def virtual_interface? + interface.is_a?(Y2Network::VirtualInterface) + end + end + end +end diff --git a/src/lib/y2network/hwinfo.rb b/src/lib/y2network/hwinfo.rb index 68d8674ae..6949bf00a 100644 --- a/src/lib/y2network/hwinfo.rb +++ b/src/lib/y2network/hwinfo.rb @@ -25,11 +25,68 @@ module Y2Network # FIXME: decide whether it should read hwinfo (on demand or at once) for a network # device and store only necessary info or just parse provided hash class Hwinfo + # TODO: this method should be private attr_reader :hwinfo - def initialize(name:) + class << self + # Creates a new instance containing hardware information for a given interface + # + # It retrieves the information from two sources: + # + # * hardware (through {Yast::LanItems} for the time being), + # * from existing udev rules. + # + # @todo Probably, this logic should be moved to a separate class. + # + # @param name [String] Interface's name + # @return [Hwinfo] + def for(name) + hwinfo_from_hardware(name) || hwinfo_from_udev(name) || Hwinfo.new + end + + private + + # Returns hardware information for the given device + # + # It relies on the {Yast::LanItems} module. + # + # @param name [String] Interface's name + # @return [Hwinfo,nil] Hardware info or nil if not found + def hwinfo_from_hardware(name) + hw = Yast::LanItems.Hardware.find { |h| h["dev_name"] == name } + return nil if hw.nil? + + raw_dev_port = Yast::SCR.Read( + Yast::Path.new(".target.string"), "/sys/class_net/#{name}/dev_port" + ).to_s.strip + hw["dev_port"] = raw_dev_port unless raw_dev_port.empty? + new(hw) + end + + # Returns hardware information for the given device + # + # It relies on udev rules. + # + # @param name [String] Interface's name + # @return [Hwinfo,nil] Hardware info or nil if not found + def hwinfo_from_udev(name) + udev_rule = UdevRule.find_for(name) + return Hwinfo.new if udev_rule.nil? + info = { + udev: udev_rule.bus_id, + mac: udev_rule.mac, + dev_port: udev_rule.dev_port + }.compact + new(info) + end + end + + # Constructor + # + # @param hwinfo [Hash] Hardware information + def initialize(hwinfo = {}) # FIXME: store only what's needed. - @hwinfo = load_hwinfo(name) + @hwinfo = Hash[hwinfo.map { |k, v| [k.to_s, v] }] if hwinfo end # Shortcuts for accessing hwinfo items. Each hwinfo item has own method for reading @@ -66,8 +123,8 @@ def initialize(name:) # @return [String,nil] [ { name: "dev_name", default: "" }, - { name: "mac", default: "" }, - { name: "busid", default: "" }, + { name: "mac", default: nil }, + { name: "busid", default: nil }, { name: "link", default: false }, { name: "driver", default: "" }, { name: "module", default: nil }, @@ -88,7 +145,7 @@ def initialize(name:) alias_method :name, :dev_name def exists? - !@hwinfo.nil? + !@hwinfo.empty? end # Device type description @@ -97,6 +154,14 @@ def description @hwinfo ? @hwinfo.fetch("name", "") : "" end + # Merges data from another Hwinfo object + # + # @param other [Hwinfo] Object to merge data from + def merge!(other) + @hwinfo.merge!(other.hwinfo) + self + end + # Returns the list of kernel modules # # The list of modules is internally represented as: @@ -112,20 +177,16 @@ def drivers modules.map { |m| Driver.new(*m) } end - private - - # for textdomain in network/hardware.rb - include Yast::I18n - - def load_hwinfo(name) - hw = Yast::LanItems.Hardware.find { |h| h["dev_name"] == name } - return nil if hw.nil? - - raw_dev_port = Yast::SCR.Read( - Yast::Path.new(".target.string"), "/sys/class_net/#{name}/dev_port" - ).to_s.strip - hw["dev_port"] = raw_dev_port unless raw_dev_port.empty? - hw + # Determines whether two objects are equivalent + # + # Ignores any element having a nil value. + # + # @param other [Hwinfo] Object to compare with + # @return [Boolean] + def ==(other) + hwinfo.compact == other.hwinfo.compact end + + alias_method :eql?, :== end end diff --git a/src/lib/y2network/interface.rb b/src/lib/y2network/interface.rb index 12085c76a..ab1de2c63 100644 --- a/src/lib/y2network/interface.rb +++ b/src/lib/y2network/interface.rb @@ -77,7 +77,7 @@ def initialize(name, type: InterfaceType::ETHERNET) @description = "" @type = type # @hardware and @name should not change during life of the object - @hardware = Hwinfo.new(name: name) + @hardware = Hwinfo.for(name) init(name) end @@ -120,6 +120,15 @@ def rename(new_name, mechanism) @renaming_mechanism = mechanism end + # Determines whether the interface can be renamed + # + # An interface can be renamed if it has a MAC address or a Bus ID. + # + # @return [Boolean] + def can_be_renamed? + hardware && !(hardware.mac.nil? && hardware.busid.nil?) + end + private def system_config(name) diff --git a/src/lib/y2network/interface_config_builder.rb b/src/lib/y2network/interface_config_builder.rb index 3ef278e87..b8811ac5c 100644 --- a/src/lib/y2network/interface_config_builder.rb +++ b/src/lib/y2network/interface_config_builder.rb @@ -121,7 +121,7 @@ def renamed_interface? # # @param new_name [String] New interface's name # @param renaming_mechanism [Symbol,nil] Mechanism to rename the interface - # (nil -no rename-, :mac or :bus_id) + # (no hardware based, :mac or :bus_id) def rename_interface(new_name, renaming_mechanism) @old_name ||= name self.name = new_name @@ -309,7 +309,7 @@ def aliases=(value) # gets interface name that will be assigned by udev def udev_name - # cannot cache as EditNicName dialog can change it + # cannot cache as it can be changed Yast::LanItems.current_udev_name end diff --git a/src/lib/y2network/sysconfig/interfaces_writer.rb b/src/lib/y2network/sysconfig/interfaces_writer.rb index d79a7aa1b..8bf6ada9d 100644 --- a/src/lib/y2network/sysconfig/interfaces_writer.rb +++ b/src/lib/y2network/sysconfig/interfaces_writer.rb @@ -58,6 +58,11 @@ def udev_rule_for(iface) def update_udevd Yast::Execute.on_target("/usr/bin/udevadm", "control", "--reload") Yast::Execute.on_target("/usr/bin/udevadm", "trigger", "--subsystem-match=net", "--action=add") + # wait so that ifcfgs written in NetworkInterfaces are newer + # (1-second-wise) than netcontrol status files, + # and rcnetwork reload actually works (bnc#749365) + Yast::Execute.on_target("/usr/bin/udevadm", "settle") + sleep(1) end end end diff --git a/src/lib/y2network/udev_rule.rb b/src/lib/y2network/udev_rule.rb index 572453e4e..80ec96edb 100644 --- a/src/lib/y2network/udev_rule.rb +++ b/src/lib/y2network/udev_rule.rb @@ -53,7 +53,15 @@ def find_for(device) # @param mac [String] MAC address def new_mac_based_rename(name, mac) new_network_rule( - [UdevRulePart.new("ATTR{address}", "=", mac), UdevRulePart.new("NAME", "=", name)] + [ + # Guard to not try to rename everything with the same MAC address (e.g. vlan devices + # inherit the MAC address from the underlying device). + UdevRulePart.new("KERNEL", "==", "eth*"), + # The port number of a NIC where the ports share the same hardware device. + UdevRulePart.new("ATTR{dev_id}", "==", "0x0"), + UdevRulePart.new("ATTR{address}", "==", mac), + UdevRulePart.new("NAME", "=", name) + ] ) end @@ -63,8 +71,8 @@ def new_mac_based_rename(name, mac) # @param bus_id [String] BUS ID (e.g., "0000:08:00.0") # @param dev_port [String] Device port def new_bus_id_based_rename(name, bus_id, dev_port = nil) - parts = [UdevRulePart.new("KERNELS", "=", bus_id)] - parts << UdevRulePart.new("ATTR{dev_port}", "=", dev_port) if dev_port + parts = [UdevRulePart.new("KERNELS", "==", bus_id)] + parts << UdevRulePart.new("ATTR{dev_port}", "==", dev_port) if dev_port parts << UdevRulePart.new("NAME", "=", name) new_network_rule(parts) end @@ -136,5 +144,46 @@ def add_part(key, operator, value) def to_s parts.map(&:to_s).join(", ") end + + # Returns the part with the given key + # + # @param key [String] Key name + def part_by_key(key) + parts.find { |p| p.key == key } + end + + # Returns the value for a given part + # + # @param key [String] Key name + # @return [String,nil] Value or nil if not found a part which such a key + def part_value_for(key) + part = part_by_key(key) + return nil unless part + part.value + end + + # Returns the MAC in the udev rule + # + # @return [String,nil] MAC address or nil if not found + # @see #part_value_for + def mac + part_value_for("ATTR{address}") + end + + # Returns the BUS ID in the udev rule + # + # @return [String,nil] BUS ID or nil if not found + # @see #part_value_for + def bus_id + part_value_for("KERNELS") + end + + # Returns the device port in the udev rule + # + # @return [String,nil] Device port or nil if not found + # @see #part_value_for + def dev_port + part_value_for("ATTR{dev_port}") + end end end diff --git a/src/lib/y2network/virtual_interface.rb b/src/lib/y2network/virtual_interface.rb index f9bcecda0..9d03ef93a 100644 --- a/src/lib/y2network/virtual_interface.rb +++ b/src/lib/y2network/virtual_interface.rb @@ -31,5 +31,14 @@ class VirtualInterface < Interface def self.from_connection(name, conn) new(name, type: conn.type) end + + # Determines whether the interface can be renamed + # + # A virtual interface can always be renamed. + # + # @return [Boolean] + def can_be_renamed? + true + end end end diff --git a/src/lib/y2network/widgets/custom_interface_name.rb b/src/lib/y2network/widgets/custom_interface_name.rb new file mode 100644 index 000000000..f8e8f898a --- /dev/null +++ b/src/lib/y2network/widgets/custom_interface_name.rb @@ -0,0 +1,88 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "cwm" +require "cwm/common_widgets" + +Yast.import "Popup" + +module Y2Network + module Widgets + class CustomInterfaceName < CWM::InputField + # Constructor + # + # @param builder [InterfaceConfigBuilder] Interface configuration builder object + def initialize(builder) + textdomain "network" + @builder = builder + @old_name = builder.name + end + + # @see CWM::AbstractWidget#label + def label + _("Device Name") + end + + # @see CWM::AbstractWidget#opt + def opt + [:hstretch] + end + + # @see CWM::AbstractWidget#init + def init + self.value = @builder.name + end + + # Saves the current value so it can be queried after the widget is closed + # @see CWM::AbstractWidget#init + def store + @value = value + end + + # Current value + # + # @return [String,nil] + def value + @value || super + end + + # The value is valid when it does not contain unexpected characters + # and it is not taken already. + # + # @return [Boolean] + # + # @see CWM::AbstractWidget#opt + # @see Y2Network::InterfaceConfigBuilder#name_exists? + # @see Y2Network::InterfaceConfigBuilder#valid_name? + def validate + if @old_name != value && @builder.name_exists?(value) + Yast::Popup.Error(_("Configuration name already exists.")) + return false + end + + if !@builder.valid_name?(value) + Yast::Popup.Error(_("Invalid configuration name.")) + return false + end + + true + end + end + end +end diff --git a/src/lib/y2network/widgets/rename_hwinfo.rb b/src/lib/y2network/widgets/rename_hwinfo.rb new file mode 100644 index 000000000..33cba0a1c --- /dev/null +++ b/src/lib/y2network/widgets/rename_hwinfo.rb @@ -0,0 +1,88 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "yast" +require "cwm" + +module Y2Network + module Widgets + # This class allows the user to select which hardware information + # should be taken into account when renaming a device + class RenameHwinfo < CWM::CustomWidget + # @return [Hwinfo,nil] Hardware information to consider + attr_reader :value + + # Constructor + # + # @param builder [InterfaceConfigBuilder] Interface configuration builder object + def initialize(builder) + textdomain "network" + @builder = builder + + interface = builder.interface + @hwinfo = interface.hardware + @mac = @hwinfo.mac + @bus_id = @hwinfo.busid + @renaming_mechanism = builder.renaming_mechanism || :mac + end + + # @see CWM::AbstractWidget + def init + Yast::UI.ChangeWidget(Id(:udev_type), :Value, @renaming_mechanism) + end + + # @see CWM::AbstractWidget + def store + @value = current_value + end + + def value + @value ||= current_value + end + + # @see CWM::CustomWidget + def contents + Frame( + _("Base Udev Rule On"), + RadioButtonGroup( + Id(:udev_type), + VBox( + # make sure there is enough space (#367239) + HSpacing(30), + *radio_buttons + ) + ) + ) + end + + private + + def current_value + Yast::UI.QueryWidget(Id(:udev_type), :Value) + end + + def radio_buttons + buttons = [] + buttons << Left(RadioButton(Id(:mac), _("MAC address: %s") % @mac)) if @mac + buttons << Left(RadioButton(Id(:bus_id), _("BusID: %s") % @bus_id)) if @bus_id + buttons + end + end + end +end diff --git a/src/lib/y2network/widgets/udev_rules.rb b/src/lib/y2network/widgets/udev_rules.rb index fb88c5aa7..de8849ad8 100644 --- a/src/lib/y2network/widgets/udev_rules.rb +++ b/src/lib/y2network/widgets/udev_rules.rb @@ -19,7 +19,7 @@ require "yast" require "cwm/custom_widget" -require "network/edit_nic_name" +require "y2network/dialogs/rename_interface" Yast.import "UI" @@ -36,23 +36,23 @@ def contents _("Udev Rules"), HBox( InputField(Id(:udev_rules_name), Opt(:hstretch, :disabled), _("Device Name"), ""), - PushButton(Id(:udev_rules_change), _("Change")) + @settings.interface.can_be_renamed? ? change_button : Empty() ) ) end def init - self.value = @settings.udev_name + self.value = @settings.name end def handle - self.value = Yast::EditNicName.new(@settings).run + self.value = Y2Network::Dialogs::RenameInterface.new(@settings).run nil end def store - # TODO: nothing to do as done in EditNicName which looks wrong + # TODO: nothing to do as done in RenameInterface which looks wrong end def value=(name) @@ -70,6 +70,12 @@ def help "example, eth1, wlan0 ) and assures a persistent device name upon reboot.\n" ) end + + private + + def change_button + PushButton(Id(:udev_rules_change), _("Change")) + end end end end diff --git a/test/network/edit_nic_name_test.rb b/test/network/edit_nic_name_test.rb deleted file mode 100755 index 1e842e6a4..000000000 --- a/test/network/edit_nic_name_test.rb +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env rspec - -# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may -# find current contact information at www.suse.com. - -require_relative "../test_helper" - -require "yast" -require "network/edit_nic_name" - -require "y2network/route" -require "y2network/routing" -require "y2network/routing_table" -require "y2network/interfaces_collection" -require "y2network/interface" -require "y2network/interface_config_builder" -require "y2network/config" - -Yast.import "LanItems" - -describe Yast::EditNicName do - let(:subject) { described_class.new(builder) } - let(:current_name) { "spec0" } - let(:new_name) { "new1" } - let(:existing_new_name) { "existing_new_name" } - let(:interface_hwinfo) { { "dev_name" => current_name, "permanent_mac" => "00:01:02:03:04:05" } } - - let(:route1) { Y2Network::Route.new } - let(:table1) { Y2Network::RoutingTable.new(routes: [route1]) } - let(:routing) { Y2Network::Routing.new(tables: table1) } - let(:iface) { Y2Network::Interface.new(current_name) } - let(:ifaces) { Y2Network::InterfacesCollection.new([iface]) } - let(:yast_config) do - Y2Network::Config.new(interfaces: ifaces, routing: routing, source: :sysconfig) - end - let(:builder) do - instance_double(Y2Network::InterfaceConfigBuilder, interface: iface, - name_exists?: false, valid_name?: true, rename_interface: nil) - end - - before do - allow(Y2Network::Config).to receive(:find).and_return(yast_config) - end - - describe "#run" do - # general mocking stuff is placed here - before(:each) do - # NetworkInterfaces are too low level. Everything needed should be mocked - stub_const("NetworkInterfaces", double(adapt_old_config!: nil)) - - # mock devices configuration - allow(Yast::LanItems).to receive(:ReadHardware).and_return([interface_hwinfo]) - allow(Yast::LanItems).to receive(:getNetworkInterfaces).and_return([current_name]) - allow(Yast::LanItems).to receive(:GetItemUdev) { "" } - allow(Yast::LanItems).to receive(:current_udev_name).and_return(current_name) - allow(Yast::LanItems).to receive(:GetItemUdev).with("ATTR{address}") { "00:01:02:03:04:05" } - allow(Yast::LanItems).to receive(:GetNetcardNames).and_return([current_name]) - - # LanItems initialization - - Yast::LanItems.Read - Yast::LanItems.FindAndSelect(current_name) - end - - context "when closed without any change" do - before(:each) do - # emulate Yast::UI work - allow(Yast::UI).to receive(:QueryWidget).with(:dev_name, :Value) { current_name } - allow(Yast::UI).to receive(:QueryWidget).with(:udev_type, :CurrentButton) { :mac } - allow(Yast::UI).to receive(:UserInput) { :ok } - allow(Yast::LanItems).to receive(:update_item_udev_rule!) - end - - it "returns current name when used Ok button" do - expect(subject.run).to be_equal current_name - end - - it "returns current name when used Cancel button" do - allow(Yast::UI).to receive(:UserInput) { :cancel } - - expect(subject.run).to be_equal current_name - end - end - - context "when name changed" do - before(:each) do - # emulate Yast::UI work - allow(Yast::UI).to receive(:QueryWidget).with(:dev_name, :Value) { new_name } - allow(Yast::UI).to receive(:QueryWidget).with(:udev_type, :CurrentButton) { :mac } - allow(Yast::UI).to receive(:UserInput) { :ok } - allow(subject).to receive(:update_routes?).and_return(false) - end - - context "and closed confirming the changes" do - it "returns the new name" do - expect(subject.run).to be_equal new_name - end - - it "asks for new user input when name already exists" do - allow(Yast::UI).to receive(:QueryWidget) - .with(:dev_name, :Value).and_return(existing_new_name, new_name) - allow(subject).to receive(:CheckUdevNicName).with(existing_new_name).and_return(false) - allow(subject).to receive(:CheckUdevNicName).with(new_name).and_return(true) - allow(Yast::UI).to receive(:SetFocus) - expect(builder).to receive(:rename_interface).with(new_name, :mac) - subject.run - end - - context "but used the same matching udev key" do - it "does not touch the current udev rule" do - expect(Yast::LanItems).to_not receive(:update_item_udev_rule!) - end - end - - xcontext "and there are some routes referencing the previous name" do - before do - allow(Yast::Routing).to receive(:device_routes?).with(current_name).and_return(true) - expect(subject).to receive(:update_routes?).with(current_name).and_call_original - allow(Yast::LanItems).to receive(:update_routes!).with(current_name) - end - - it "asks the user about updating the routes device name" do - expect(Yast::Popup).to receive(:YesNoHeadline) - - subject.run - end - - it "updates the routes if the user accepts to do it" do - expect(Yast::Popup).to receive(:YesNoHeadline).and_return(true) - expect(Yast::LanItems).to receive(:update_routes!).with(current_name) - - subject.run - end - - it "does not touch the routes if the user does not want to touch them" do - expect(Yast::Popup).to receive(:YesNoHeadline).and_return(false) - expect(Yast::LanItems).to_not receive(:update_routes!) - subject.run - end - end - - context "having modified the matching udev key" do - before(:each) do - # emulate UI work - allow(Yast::UI).to receive(:QueryWidget).with(:dev_name, :Value) { current_name } - allow(Yast::UI).to receive(:QueryWidget).with(:udev_type, :CurrentButton) { :bus_id } - end - - it "updates the current udev rule with the key used" do - expect(Yast::LanItems).to_not receive(:update_item_udev_rule!).with(:bus_id) - end - end - end - - context "and closed canceling the changes" do - it "returns current name when used Cancel button" do - allow(Yast::UI).to receive(:UserInput) { :cancel } - - expect(subject.run).to be_equal current_name - end - end - end - end -end diff --git a/test/y2network/dialogs/rename_interface_test.rb b/test/y2network/dialogs/rename_interface_test.rb new file mode 100644 index 000000000..115788326 --- /dev/null +++ b/test/y2network/dialogs/rename_interface_test.rb @@ -0,0 +1,98 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "cwm/rspec" + +require "y2network/dialogs/rename_interface" +require "y2network/interface_config_builder" +require "y2network/widgets/custom_interface_name" +require "y2network/widgets/rename_hwinfo" +require "y2network/physical_interface" + +describe Y2Network::Dialogs::RenameInterface do + subject { described_class.new(builder) } + + let(:builder) do + Y2Network::InterfaceConfigBuilder.for("eth").tap do |builder| + builder.name = "eth0" + end + end + + let(:name_widget) do + Y2Network::Widgets::CustomInterfaceName.new(builder) + end + + let(:rename_hwinfo_widget) do + Y2Network::Widgets::RenameHwinfo.new(builder) + end + + let(:interface) do + Y2Network::PhysicalInterface.new("eth0") + end + + let(:hardware) do + Y2Network::Hwinfo.new(mac: "01:23:45:67:89:ab", busid: "0000:08:00.0") + end + + let(:rename_hwinfo) do + Y2Network::Hwinfo.new(mac: "01:23:45:67:89:ab") + end + + let(:new_name) { "eth1" } + + let(:result) { :ok } + + include_examples "CWM::Dialog" + + before do + allow(builder).to receive(:interface).and_return(interface) + allow(subject).to receive(:cwm_show).and_return(result) + allow(Y2Network::Widgets::CustomInterfaceName).to receive(:new).and_return(name_widget) + allow(Y2Network::Widgets::RenameHwinfo).to receive(:new).and_return(rename_hwinfo_widget) + allow(name_widget).to receive(:value).and_return(new_name) + allow(rename_hwinfo_widget).to receive(:value).and_return(rename_hwinfo) + allow(interface).to receive(:hardware).and_return(hardware) + end + + describe "#run" do + before do + allow(Yast::UI).to receive(:UserInput).and_return(:ok) + end + + context "when the user accepts the change" do + let(:result) { :ok } + + it "renames the interface" do + expect(builder).to receive(:rename_interface) + .with(new_name, rename_hwinfo) + subject.run + end + end + + context "when the user clicks the cancel button" do + let(:result) { :cancel } + + it "doest not rename the interface" do + expect(builder).to_not receive(:rename_interface) + subject.run + end + end + end +end diff --git a/test/y2network/hwinfo_test.rb b/test/y2network/hwinfo_test.rb index 1cc6084f8..120c06370 100644 --- a/test/y2network/hwinfo_test.rb +++ b/test/y2network/hwinfo_test.rb @@ -21,7 +21,7 @@ require "y2network/hwinfo" describe Y2Network::Hwinfo do - subject(:hwinfo) { described_class.new(name: interface_name) } + subject(:hwinfo) { described_class.for(interface_name) } let(:hardware) do YAML.load_file(File.join(DATA_PATH, "hardware.yml")) @@ -31,6 +31,37 @@ before do allow(Yast::LanItems).to receive(:Hardware).and_return(hardware) + allow(Y2Network::UdevRule).to receive(:find_for).with(interface_name).and_return(udev_rule) + end + + let(:udev_rule) { nil } + + describe ".for" do + context "when there is info from hardware" do + it "returns a hwinfo object containing the info from hardware" do + hwinfo = described_class.for(interface_name) + expect(hwinfo.mac).to eq("52:54:00:68:54:fb") + end + end + + context "when there is no info from hardware" do + let(:hardware) { [] } + let(:udev_rule) { Y2Network::UdevRule.new_mac_based_rename(interface_name, "01:23:45:67:89:ab") } + + it "returns info from udev rules" do + hwinfo = described_class.for(interface_name) + expect(hwinfo.mac).to eq("01:23:45:67:89:ab") + end + + context "when there is no info from udev rules" do + let(:udev_rule) { nil } + + it "returns nil" do + hwinfo = described_class.for(interface_name) + expect(hwinfo.exists?).to eq(false) + end + end + end end describe "#exists?" do @@ -49,6 +80,18 @@ end end + describe "#merge!" do + subject(:hwinfo) { described_class.new(mac: "00:11:22:33:44:55:66", busid: "0000:08:00.0") } + let(:other) { described_class.new(mac: "01:23:45:78:90:ab", dev_port: "1") } + + it "merges data from another Hwinfo object" do + hwinfo.merge!(other) + expect(hwinfo.mac).to eq(other.mac) + expect(hwinfo.busid).to eq("0000:08:00.0") + expect(hwinfo.dev_port).to eq("1") + end + end + describe "#drivers" do it "returns the list of kernel modules names" do expect(hwinfo.drivers).to eq( @@ -82,4 +125,25 @@ end end end + + describe "#==" do + context "when both objects contain the same information" do + it "returns true" do + expect(described_class.new("dev_name" => "eth0")) + .to eq(described_class.new("dev_name" => "eth0")) + end + end + + context "when both objects contain different information" do + it "returns false" do + expect(described_class.new("dev_name" => "eth0")) + .to_not eq(described_class.new("dev_name" => "eth1")) + end + end + + it "ignores nil values" do + expect(described_class.new("dev_name" => "eth0", "other" => nil)) + .to eq(described_class.new("dev_name" => "eth0")) + end + end end diff --git a/test/y2network/interface_test.rb b/test/y2network/interface_test.rb index 62c3d90db..3f0d079c7 100644 --- a/test/y2network/interface_test.rb +++ b/test/y2network/interface_test.rb @@ -54,4 +54,36 @@ expect(interface.drivers).to eq([driver]) end end + + describe "#can_be_renamed?" do + let(:mac) { nil } + let(:busid) { nil } + let(:hwinfo) { instance_double(Y2Network::Hwinfo, mac: mac, busid: busid) } + + before do + allow(interface).to receive(:hardware).and_return(hwinfo) + end + + context "when no MAC or Bus ID information is available" do + it "returns false" do + expect(interface.can_be_renamed?).to eq(false) + end + end + + context "when the MAC address is present" do + let(:mac) { "01:23:45:67:89:ab" } + + it "returns true" do + expect(interface.can_be_renamed?).to eq(true) + end + end + + context "when the Bus ID is present" do + let(:busid) { "0000:08:00.0" } + + it "returns true" do + expect(interface.can_be_renamed?).to eq(true) + end + end + end end diff --git a/test/y2network/sysconfig/interfaces_writer_test.rb b/test/y2network/sysconfig/interfaces_writer_test.rb index f95d6a843..e6a036d81 100644 --- a/test/y2network/sysconfig/interfaces_writer_test.rb +++ b/test/y2network/sysconfig/interfaces_writer_test.rb @@ -48,7 +48,8 @@ expect(Y2Network::UdevRule).to receive(:write) do |rules| expect(rules.first.to_s).to eq( "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", " \ - "ATTR{type}==\"1\", ATTR{address}=\"01:23:45:67:89:ab\", NAME=\"eth0\"" + "ATTR{type}==\"1\", KERNEL==\"eth*\", ATTR{dev_id}==\"0x0\", " \ + "ATTR{address}==\"01:23:45:67:89:ab\", NAME=\"eth0\"" ) end subject.write(interfaces) @@ -62,7 +63,7 @@ expect(Y2Network::UdevRule).to receive(:write) do |rules| expect(rules.first.to_s).to eq( "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", " \ - "ATTR{type}==\"1\", KERNELS=\"00:1c.0\", ATTR{dev_port}=\"1\", NAME=\"eth0\"" + "ATTR{type}==\"1\", KERNELS==\"00:1c.0\", ATTR{dev_port}==\"1\", NAME=\"eth0\"" ) end subject.write(interfaces) @@ -83,6 +84,7 @@ it "refreshes udev" do expect(Yast::Execute).to receive(:on_target).with("/usr/bin/udevadm", "control", any_args) expect(Yast::Execute).to receive(:on_target).with("/usr/bin/udevadm", "trigger", any_args) + expect(Yast::Execute).to receive(:on_target).with("/usr/bin/udevadm", "settle") subject.write(interfaces) end end diff --git a/test/y2network/udev_rule_test.rb b/test/y2network/udev_rule_test.rb index c0541b99b..ee177955e 100644 --- a/test/y2network/udev_rule_test.rb +++ b/test/y2network/udev_rule_test.rb @@ -58,7 +58,8 @@ rule = described_class.new_mac_based_rename("eth0", "01:23:45:67:89:ab") expect(rule.to_s).to eq( "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{type}==\"1\", " \ - "ATTR{address}=\"01:23:45:67:89:ab\", NAME=\"eth0\"" + "KERNEL==\"eth*\", ATTR{dev_id}==\"0x0\", ATTR{address}==\"01:23:45:67:89:ab\", " \ + "NAME=\"eth0\"" ) end end @@ -68,7 +69,7 @@ rule = described_class.new_bus_id_based_rename("eth0", "0000:08:00.0", "1") expect(rule.to_s).to eq( "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{type}==\"1\", " \ - "KERNELS=\"0000:08:00.0\", ATTR{dev_port}=\"1\", NAME=\"eth0\"" + "KERNELS==\"0000:08:00.0\", ATTR{dev_port}==\"1\", NAME=\"eth0\"" ) end @@ -77,7 +78,7 @@ rule = described_class.new_bus_id_based_rename("eth0", "0000:08:00.0") expect(rule.to_s).to eq( "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{type}==\"1\", " \ - "KERNELS=\"0000:08:00.0\", NAME=\"eth0\"" + "KERNELS==\"0000:08:00.0\", NAME=\"eth0\"" ) end end @@ -106,4 +107,52 @@ ) end end + + describe "#mac" do + subject(:udev_rule) { described_class.new_mac_based_rename("eth0", "01:23:45:67:89:ab") } + + it "returns the MAC from the udev rule" do + expect(udev_rule.mac).to eq("01:23:45:67:89:ab") + end + + context "if no MAC address is present" do + subject(:udev_rule) { described_class.new } + + it "returns nil" do + expect(udev_rule.mac).to be_nil + end + end + end + + describe "#bus_id" do + subject(:udev_rule) { described_class.new_bus_id_based_rename("eth0", "0000:08:00.0") } + + it "returns the BUS ID from the udev rule" do + expect(udev_rule.bus_id).to eq("0000:08:00.0") + end + + context "if no BUS ID is present" do + subject(:udev_rule) { described_class.new } + + it "returns nil" do + expect(udev_rule.bus_id).to be_nil + end + end + end + + describe "#bus_id" do + subject(:udev_rule) { described_class.new_bus_id_based_rename("eth0", "0000:08:00.0", "1") } + + it "returns the device port from the udev rule" do + expect(udev_rule.dev_port).to eq("1") + end + + context "if no device port is present" do + subject(:udev_rule) { described_class.new } + + it "returns nil" do + expect(udev_rule.dev_port).to be_nil + end + end + end end diff --git a/test/y2network/virtual_interface_test.rb b/test/y2network/virtual_interface_test.rb new file mode 100644 index 000000000..a3a4710d2 --- /dev/null +++ b/test/y2network/virtual_interface_test.rb @@ -0,0 +1,31 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../test_helper" +require "y2network/virtual_interface" + +describe Y2Network::VirtualInterface do + subject(:interface) { described_class.new("br0") } + + describe "#can_be_renamed?" do + it "returns true" do + expect(interface.can_be_renamed?).to eq(true) + end + end +end diff --git a/test/y2network/widgets/custom_interface_name_test.rb b/test/y2network/widgets/custom_interface_name_test.rb new file mode 100644 index 000000000..e48921592 --- /dev/null +++ b/test/y2network/widgets/custom_interface_name_test.rb @@ -0,0 +1,79 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. +require_relative "../../test_helper" +require "cwm/rspec" + +require "y2network/widgets/custom_interface_name" +require "y2network/interface_config_builder" + +describe Y2Network::Widgets::CustomInterfaceName do + subject { described_class.new(builder) } + + let(:builder) do + instance_double( + Y2Network::InterfaceConfigBuilder, + name: "eth", + valid_name?: valid_name?, + name_exists?: name_exists? + ) + end + let(:valid_name?) { true } + let(:name_exists?) { false } + + include_examples "CWM::InputField" + + describe "#validate" do + + before do + allow(builder) + end + + context "when the name is valid" do + it "returns true" do + expect(subject.validate).to eq(true) + end + end + + context "when the name contains unexpected characters" do + let(:valid_name?) { false } + + it "returns false" do + expect(subject.validate).to eq(false) + end + + it "displays an error popup" do + expect(Yast::Popup).to receive(:Error).with(/Invalid configuration/) + subject.validate + end + end + + context "when the name is already taken" do + let(:name_exists?) { true } + + it "returns false" do + expect(subject.validate).to eq(false) + end + + it "displays an error popup" do + expect(Yast::Popup).to receive(:Error).with(/already exists/) + subject.validate + end + end + end +end diff --git a/test/y2network/widgets/rename_hwinfo_test.rb b/test/y2network/widgets/rename_hwinfo_test.rb new file mode 100644 index 000000000..db265437a --- /dev/null +++ b/test/y2network/widgets/rename_hwinfo_test.rb @@ -0,0 +1,67 @@ +# Copyright (c) [2019] 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 SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "../../test_helper" +require "cwm/rspec" + +require "y2network/widgets/rename_hwinfo" +require "y2network/interface_config_builder" +require "y2network/physical_interface" + +describe Y2Network::Widgets::RenameHwinfo do + subject { described_class.new(builder) } + + let(:builder) do + instance_double(Y2Network::InterfaceConfigBuilder, interface: interface, renaming_mechanism: mechanism) + end + + let(:interface) { Y2Network::PhysicalInterface.new("eth0") } + let(:hwinfo) { Y2Network::Hwinfo.new(mac: "01:23:45:67:89:ab", busid: "0000:08:00.0") } + let(:mechanism) { :mac } + + include_examples "CWM::CustomWidget" + + before do + allow(interface).to receive(:hardware).and_return(hwinfo) + allow(subject).to receive(:current_value).and_return(mechanism) + end + + describe "#value" do + before do + subject.init + subject.store + end + + context "when the MAC is selected as renaming method" do + it "returns :mac as the renaming mechanism" do + selected_mechanism, = subject.value + expect(selected_mechanism).to eq(:mac) + end + end + + context "when the BUS ID is selected as renaming method" do + let(:mechanism) { :bus_id } + + it "returns :bus_id as the renaming mechanism" do + selected_mechanism, = subject.value + expect(selected_mechanism).to eq(:bus_id) + end + end + end +end diff --git a/test/y2network/widgets/udev_rules_test.rb b/test/y2network/widgets/udev_rules_test.rb index 092912429..2bcf01fd1 100644 --- a/test/y2network/widgets/udev_rules_test.rb +++ b/test/y2network/widgets/udev_rules_test.rb @@ -21,13 +21,19 @@ require "cwm/rspec" require "y2network/widgets/udev_rules" +require "y2network/dialogs/rename_interface" +require "y2network/interface_config_builder" describe Y2Network::Widgets::UdevRules do - subject { described_class.new({}) } + subject { described_class.new(builder) } + + let(:builder) do + instance_double(Y2Network::InterfaceConfigBuilder, interface: double(can_be_renamed?: true)) + end before do allow(Yast::LanItems).to receive(:current_udev_name).and_return("hell666") - allow(Yast::EditNicName).to receive(:new).and_return(double(run: "heaven010")) + allow(Y2Network::Dialogs::RenameInterface).to receive(:new).and_return(double(run: "heaven010")) end include_examples "CWM::CustomWidget"