diff --git a/package/yast2-network.changes b/package/yast2-network.changes index 85458e0a75..a123f10b50 100644 --- a/package/yast2-network.changes +++ b/package/yast2-network.changes @@ -1,9 +1,20 @@ +------------------------------------------------------------------- +Wed Apr 7 11:27:26 UTC 2021 - Imobach Gonzalez Sosa + +- Write DNS servers to NetworkManager connection files when using + a static configuration (bsc#1181701). +- 4.4.0 + +------------------------------------------------------------------- +Tue Apr 6 08:54:07 UTC 2021 - Michal Filka + +- Drop isCurrentDHCP and isCurrentHotplug methods from LanItems. + ------------------------------------------------------------------- Mon Apr 5 15:21:47 UTC 2021 - David Diaz - Do not show the "Abort" button when the inst_lan client is called from another installation step (bsc#1183586). -- 4.4.0 ------------------------------------------------------------------- Mon Mar 29 11:52:08 UTC 2021 - Imobach Gonzalez Sosa diff --git a/src/lib/cfa/nm_connection.rb b/src/lib/cfa/nm_connection.rb index 7a4fb90ed0..16ce38afda 100644 --- a/src/lib/cfa/nm_connection.rb +++ b/src/lib/cfa/nm_connection.rb @@ -18,6 +18,10 @@ # find current contact information at www.suse.com. require "cfa/base_model" +require "pathname" +require "y2network/connection_config/wireless" + +Yast.import "WFM" module CFA # Class to handle NetworkManager connection configuration files @@ -32,6 +36,35 @@ class NmConnection < BaseModel "bond", "bridge", "connection", "ethernet", "ipv4", "ipv6", "vlan", "wifi", "wifi_security" ].freeze + # @return [String] File path + attr_reader :file_path + + class << self + # Returns the file corresponding to a connection + # + # @param conn [ConnectionConfig::Base] Connection configuration + # @return [NmConnection] + def for(conn) + path = SYSTEM_CONNECTIONS_PATH.join(file_basename_for(conn)).sub_ext(FILE_EXT) + new(path) + end + + private + + SYSTEM_CONNECTIONS_PATH = Pathname.new("/etc/NetworkManager/system-connections").freeze + FILE_EXT = ".nmconnection".freeze + + # Returns the file base name for the given connection + # + # @param conn [ConnectionConfig::Base] + # @return [String] + def file_basename_for(conn) + return conn.essid.to_s if conn.is_a?(Y2Network::ConnectionConfig::Wireless) && conn.essid + + conn.name + end + end + # Constructor # # @param path [String] File path @@ -39,7 +72,7 @@ class NmConnection < BaseModel def initialize(path, file_handler: nil) # FIXME: The Networkmanager lense writes the values surrounded by double # quotes which is not valid - super(AugeasParser.new("Puppet.lns"), path, file_handler: file_handler) + super(AugeasParser.new("Desktop.lns"), path, file_handler: file_handler) end # Returns the augeas tree for the given section @@ -80,6 +113,13 @@ def add_collection(section, name, values) end end + # Determines whether the file exist + # + # @return [Boolean] true if the file exist, false otherwise + def exist? + ::File.exist?(::File.join(Yast::WFM.scr_root, file_path)) + end + KNOWN_SECTIONS.each { |s| define_method(s) { section_for(s) } } end end diff --git a/src/lib/y2network/connection_configs_collection.rb b/src/lib/y2network/connection_configs_collection.rb index fe6839cb45..aed97d8a3e 100644 --- a/src/lib/y2network/connection_configs_collection.rb +++ b/src/lib/y2network/connection_configs_collection.rb @@ -72,6 +72,14 @@ def by_ids(*ids) select { |c| ids.include?(c.id) } end + # Returns connections with a given bootproto + # + # @param bootprotos [Array] Boot protocols + # @return [Array] Connection configs with the given boot protocol + def by_bootproto(*bootprotos) + select { |c| bootprotos.include?(c.bootproto) } + end + # Adds or updates a connection configuration # # @note It uses the name to do the matching. diff --git a/src/lib/y2network/network_manager/config_writer.rb b/src/lib/y2network/network_manager/config_writer.rb index b58cffba4d..31f7266d07 100644 --- a/src/lib/y2network/network_manager/config_writer.rb +++ b/src/lib/y2network/network_manager/config_writer.rb @@ -43,6 +43,25 @@ def write_connections(config, _old_config) end end + # Updates the DNS configuration + # + # In case a connection has a static configuration, the DNS nameservers are added + # to the configuration file (see bsc#1181701). + # + # @param config [Y2Network::Config] Current config object + # @param old_config [Y2Network::Config,nil] Config object with original configuration + def write_dns(config, old_config) + static = config.connections.by_bootproto(Y2Network::BootProtocol::STATIC) + return super if static.empty? || config.dns.nameservers.empty? + + ipv4_ns, ipv6_ns = config.dns.nameservers.partition(&:ipv4?) + ipv4_dns = ipv4_ns.map(&:to_s).join(";") + ipv6_dns = ipv6_ns.map(&:to_s).join(";") + static.each do |conn| + add_dns_to_conn(conn, ipv4_dns, ipv6_dns) + end + end + # Finds routes for a given connection # # @param conn [ConnectionConfig::Base] Connection configuration @@ -51,6 +70,21 @@ def write_connections(config, _old_config) def routes_for(conn, routes) routes.select { |r| r.interface&.name == conn.name } end + + # Add the DNS settings to the nmconnection file corresponding to the give conn + # + # @param conn [Connectionconfig::Base] Connection configuration + # @param ipv4_dns [String] Value for the 'dns' key in the ipv4 section + # @param ipv6_dns [String] Value for the 'dns' key in the ipv6 section + def add_dns_to_conn(conn, ipv4_dns, ipv6_dns) + file = CFA::NmConnection.for(conn) + return unless file.exist? + + file.load + file.ipv4["dns"] = ipv4_dns unless ipv4_dns.empty? + file.ipv6["dns"] = ipv6_dns unless ipv6_dns.empty? + file.save + end end end end diff --git a/src/lib/y2network/network_manager/connection_config_writer.rb b/src/lib/y2network/network_manager/connection_config_writer.rb index dc07793629..746b98f68a 100644 --- a/src/lib/y2network/network_manager/connection_config_writer.rb +++ b/src/lib/y2network/network_manager/connection_config_writer.rb @@ -28,9 +28,6 @@ module NetworkManager class ConnectionConfigWriter include Yast::Logger - SYSTEM_CONNECTIONS_PATH = Pathname.new("/etc/NetworkManager/system-connections").freeze - FILE_EXT = ".nmconnection".freeze - # @param conn [ConnectionConfig::Base] Connection configuration to be # written # @param old_conn [ConnectionConfig::Base] Original connection @@ -39,12 +36,15 @@ class ConnectionConfigWriter def write(conn, old_conn = nil, opts = {}) return if conn == old_conn - path = SYSTEM_CONNECTIONS_PATH.join(file_basename_for(conn)).sub_ext(FILE_EXT) - file = CFA::NmConnection.new(path) + file = CFA::NmConnection.for(conn) handler_class = find_handler_class(conn.type) return nil if handler_class.nil? - ensure_permissions(path) unless ::File.exist?(path) + if file.exist? + file.load + else + ensure_permissions(file.file_path) + end handler_class.new(file).write(conn, opts) file.save @@ -74,16 +74,6 @@ def find_handler_class(type) "Connection handler could not be loaded: #{e.message}" nil end - - # Returns the file base name for the given connection - # - # @param conn [ConnectionConfig::Base] - # @return [String] - def file_basename_for(conn) - return conn.essid.to_s if conn.is_a?(ConnectionConfig::Wireless) - - conn.name - end end end end diff --git a/test/cfa/nm_connection_test.rb b/test/cfa/nm_connection_test.rb index 908c7945f7..5162bd80df 100644 --- a/test/cfa/nm_connection_test.rb +++ b/test/cfa/nm_connection_test.rb @@ -20,6 +20,7 @@ require_relative "../test_helper" require "cfa/nm_connection" require "cfa/memory_file" +require "y2network/connection_config/ethernet" describe CFA::NmConnection do def file_path(filename) @@ -29,6 +30,46 @@ def file_path(filename) subject { described_class.new(conn_file) } let(:conn_file) { file_path("some_wifi.nmconnection") } + describe ".for" do + let(:conn) do + Y2Network::ConnectionConfig::Ethernet.new.tap do |eth0| + eth0.name = "eth0" + eth0.interface = "eth0" + end + end + + it "uses the interface as path basename" do + file = described_class.for(conn) + expect(file.file_path.basename.to_s).to eq("eth0.nmconnection") + end + + context "when a wireless connection is given" do + let(:essid) { "MY_WIRELESS" } + + let(:conn) do + Y2Network::ConnectionConfig::Wireless.new.tap do |wlo1| + wlo1.name = "wlo1" + wlo1.interface = "wlo1" + wlo1.essid = essid + end + end + + it "uses the ESSID as path basename" do + file = described_class.for(conn) + expect(file.file_path.basename.to_s).to eq("MY_WIRELESS.nmconnection") + end + + context "and the ESSID is not set" do + let(:essid) { nil } + + it "uses the interface as path basename" do + file = described_class.for(conn) + expect(file.file_path.basename.to_s).to eq("wlo1.nmconnection") + end + end + end + end + describe "#connection" do before { subject.load } @@ -44,4 +85,22 @@ def file_path(filename) end end end + + describe "#exist?" do + context "when the file exists" do + let(:conn_file) { file_path("some_wifi.nmconnection") } + + it "returns true" do + expect(subject.exist?).to eq(true) + end + end + + context "when the file does not exist" do + let(:conn_file) { file_path("missing.nmconnection") } + + it "returns false" do + expect(subject.exist?).to eq(false) + end + end + end end diff --git a/test/cmdline_test.rb b/test/cmdline_test.rb index b8ab8b32e6..fdf6187bb4 100755 --- a/test/cmdline_test.rb +++ b/test/cmdline_test.rb @@ -19,9 +19,8 @@ # To contact SUSE LLC about this file by physical or electronic mail, you may # find current contact information at www.suse.com. -require "y2network/interface_config_builder" - require_relative "test_helper" +require "y2network/interface_config_builder" class DummyClass < Yast::Module def initialize diff --git a/test/support/config_writer_examples.rb b/test/support/config_writer_examples.rb index ada1f460be..eb2e374198 100644 --- a/test/support/config_writer_examples.rb +++ b/test/support/config_writer_examples.rb @@ -24,6 +24,7 @@ require "y2network/connection_configs_collection" require "y2network/interfaces_collection" require "y2network/hostname" +require "y2network/boot_protocol" RSpec.shared_examples "ConfigWriter" do subject(:writer) { described_class.new } @@ -50,6 +51,7 @@ Y2Network::ConnectionConfig::Ethernet.new.tap do |conn| conn.interface = "eth0" conn.name = "eth0" + conn.bootproto = Y2Network::BootProtocol::DHCP end end diff --git a/test/y2network/network_manager/config_writer_test.rb b/test/y2network/network_manager/config_writer_test.rb index 7a526c27b3..445380cf20 100644 --- a/test/y2network/network_manager/config_writer_test.rb +++ b/test/y2network/network_manager/config_writer_test.rb @@ -24,6 +24,8 @@ require "y2network/connection_configs_collection" require "y2network/interface" require "y2network/interfaces_collection" +require "y2network/boot_protocol" +require "cfa/nm_connection" describe Y2Network::NetworkManager::ConfigWriter do subject(:writer) { described_class.new } @@ -42,6 +44,7 @@ let(:config) do old_config.copy.tap do |cfg| cfg.add_or_update_connection_config(eth0_conn) + cfg.dns = dns end end @@ -49,11 +52,24 @@ Y2Network::ConnectionConfig::Ethernet.new.tap do |conn| conn.interface = "eth0" conn.name = "eth0" - conn.bootproto = :static + conn.bootproto = Y2Network::BootProtocol::DHCP conn.ip = ip end end + let(:nameserver1) { IPAddr.new("192.168.1.1") } + let(:nameserver2) { IPAddr.new("10.0.0.1") } + let(:nameserver3) { IPAddr.new("2000:e1dd:f002:0120:0000:0000:0000:0001") } + let(:nameserver4) { IPAddr.new("2000:e1dd:f002:0120:0000:0000:0000:0002") } + + let(:dns) do + Y2Network::DNS.new( + nameservers: [ + nameserver1, nameserver2, nameserver3, nameserver4 + ] + ) + end + let(:ip) { Y2Network::ConnectionConfig::IPConfig.new(address: IPAddr.new("192.168.122.2")) } let(:eth0) { Y2Network::Interface.new("eth0") } @@ -64,7 +80,6 @@ before do allow(Y2Network::NetworkManager::ConnectionConfigWriter).to receive(:new) .and_return(conn_config_writer) - allow(writer).to receive(:write_dns) allow(writer).to receive(:write_drivers) allow(writer).to receive(:write_hostname) allow(writer).to receive(:write_routing) @@ -74,5 +89,34 @@ expect(conn_config_writer).to receive(:write).with(eth0_conn, nil, routes: [], parent: nil) writer.write(config) end + + context "writes DNS configuration" do + context "when a connection has static configuration" do + let(:eth0_conn) do + Y2Network::ConnectionConfig::Ethernet.new.tap do |conn| + conn.interface = "eth0" + conn.name = "eth0" + conn.bootproto = Y2Network::BootProtocol::STATIC + conn.ip = ip + end + end + + let(:file) do + CFA::NmConnection.new(File.join(DATA_PATH, "some_wifi.nmconnection")) + end + + before do + allow(CFA::NmConnection).to receive(:for).and_return(file) + allow(file).to receive(:save) + end + + it "includes DNS configuration in the configuration file" do + writer.write(config) + expect(file.ipv4["dns"]).to eq("#{nameserver1};#{nameserver2}") + expect(file.ipv6["dns"]).to eq("#{nameserver3};#{nameserver4}") + end + end + end + end end diff --git a/test/y2network/network_manager/connection_config_writer_test.rb b/test/y2network/network_manager/connection_config_writer_test.rb index 5195372bb4..7381139f9c 100644 --- a/test/y2network/network_manager/connection_config_writer_test.rb +++ b/test/y2network/network_manager/connection_config_writer_test.rb @@ -52,12 +52,14 @@ Y2Network::ConnectionConfig::IPConfig.new(Y2Network::IPAddress.from_string("10.100.0.1/24")) end - let(:path) { "/etc/NetworkManager/system-connections/eth0.nmconnection" } + let(:path) { Pathname.new("/etc/NetworkManager/system-connections/eth0.nmconnection") } let(:file) do - instance_double(CFA::NmConnection, save: nil) + instance_double(CFA::NmConnection, save: nil, load: nil, file_path: path, exist?: file_exist?) end + let(:file_exist?) { true } + describe "#write" do let(:handler) do instance_double( @@ -69,10 +71,8 @@ allow(writer).to receive(:require).and_call_original allow(Y2Network::NetworkManager::ConnectionConfigWriters::Ethernet).to receive(:new) .and_return(handler) - allow(CFA::NmConnection).to receive(:new).and_return(file) + allow(CFA::NmConnection).to receive(:for).with(conn).and_return(file) allow(writer).to receive(:ensure_permissions) - allow(::File).to receive(:exist?).with(Pathname.new(path)).and_return(true) - allow(::File).to receive(:exist?).and_call_original end it "uses the appropiate handler" do @@ -81,48 +81,10 @@ end context "when the file does not exist" do - before do - allow(::File).to receive(:exist?).with(Pathname.new(path)).and_return(false) - end + let(:file_exist?) { false } it "ensures the file is created with the the correct permissions" do - expect(writer).to receive(:ensure_permissions).with(Pathname.new(path)) - writer.write(conn) - end - end - - it "uses the connection name as base file name" do - expect(CFA::NmConnection).to receive(:new) do |path| - expect(path.basename).to eq(Pathname.new("eth0.nmconnection")) - end.and_return(file) - writer.write(conn) - end - - context "when writing a wireless connection" do - let(:conn) do - Y2Network::ConnectionConfig::Wireless.new.tap do |wlo1| - wlo1.name = "wlo1" - wlo1.interface = "wlo1" - wlo1.ip = ip_config - wlo1.essid = "MY_WIRELESS" - end - end - - let(:handler) do - instance_double( - Y2Network::NetworkManager::ConnectionConfigWriters::Wireless, write: nil - ) - end - - before do - allow(Y2Network::NetworkManager::ConnectionConfigWriters::Wireless).to receive(:new) - .and_return(handler) - end - - it "uses the ESSID as the base file name" do - expect(CFA::NmConnection).to receive(:new) do |path| - expect(path.basename).to eq(Pathname.new("MY_WIRELESS.nmconnection")) - end.and_return(file) + expect(writer).to receive(:ensure_permissions).with(path) writer.write(conn) end end