yast/yast-network

View on GitHub
src/lib/network/network_autoyast.rb

Summary

Maintainability
A
1 hr
Test Coverage
# 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 "y2network/autoinst_profile/s390_devices_section"
require "y2network/autoinst/s390_devices_reader"
require "y2network/interface_config_builder"
require "y2network/s390_device_activator"
require "y2network/proposal_settings"

module Yast
  # Provides functionality for network AutoYaST client(s)
  #
  # This currently shouldn't replace *::Import methods. In other
  # words it is intended for functionality which cannot be handled
  # in 2nd stage properly. Typically:
  #   - merging configuration provided by linuxrc means and AY profile
  #     together
  #   - target network service setup (to avoid need of restarting the
  #     service during 2nd stage) and all other stuff which could lead
  #     to the need of restarting the service (e.g. device renaming)
  class NetworkAutoYast
    include Singleton
    include Logger

    def initialize
      # import has to be done here, there are some collisions otherwise
      Yast.import "Lan"
      Yast.import "Host"
    end

    # Merges existing config from system into given configuration map
    #
    # @param conf [Hash, nil] configuration map
    #
    # @return updated configuration map
    def merge_configs(conf)
      # read settings from installation
      Lan.Read(:cache)
      # export settings into AY map
      from_system = Lan.Export
      return from_system if conf.nil? || conf.empty?

      dns = from_system["dns"] || {}
      routing = from_system["routing"] || {}

      # copy the keys/values that are not existing in the XML
      # so we merge the inst-sys settings with the XML while XML
      # has higher priority
      conf["dns"] = merge_dns(dns, conf["dns"])
      conf["routing"] = merge_routing(routing, conf["routing"])

      conf
    end

    def selected_backend
      Y2Network::ProposalSettings.instance.network_service
    end

    # Writes the autoyast network configuration according to the already
    # imported configuration
    #
    # If the network was already written before the proposal it returns without
    # touching it
    #
    # @return [Boolean] true when written
    def configure_lan
      log.info("NetworkAutoYast: Lan configuration")

      Yast::Lan.yast_config.backend = selected_backend

      # We need to ensure the config translation is written to the target
      # system
      return false if (selected_backend == :wicked) && Lan.autoinst.before_proposal

      # force a write only as it is run at the end of the installation and it
      # is already chrooted in the target system where restarting services or
      # refreshing udev rules does not make sense at all
      Lan.Write(apply_config: false)
    end

    # Takes care of activate s390 devices from the profile declaration
    def activate_s390_devices(section = nil)
      profile_devices = section || ay_networking_section["s390-devices"] || {}
      devices_section = Y2Network::AutoinstProfile::S390DevicesSection
        .new_from_hashes(profile_devices)
      connections = Y2Network::Autoinst::S390DevicesReader.new(devices_section).config

      connections.each do |conn|
        builder = Y2Network::InterfaceConfigBuilder.for(conn.type, config: conn)
        activator = Y2Network::S390DeviceActivator.for(builder)
        if !activator.configured_interface.empty?
          log.info "Interface #{activator.configured_interface} is already active. " \
                   "Skipping the activation."
          next
        end

        log.info "Created interface #{activator.configured_interface}" if activator.configure
      rescue RuntimeError => e
        log.error("An error ocurred when trying to activate the s390 device: #{conn.inspect}")
        log.error("Error: #{e.inspect}")
      end

      true
    end

    # Initializates /etc/hosts according AY profile
    #
    # If the installer is running in 1st stage mode only, then the configuration
    # is also written
    def configure_hosts
      Host.Write(gui: false)
    end

    # Checks if the profile asks for keeping installation network configuration
    def keep_net_config?
      ret = Lan.autoinst.keep_install_network

      log.info("NetworkAutoYast: keep installation network: #{ret}")

      ret
    end

    # setter for networking section. Should be done during import.
    # @return [Hash] networking section hash
    attr_writer :ay_networking_section

  private

    # Merges two maps with dns related values.
    #
    # Value in second map has precendence over the value in first one in
    # case of key collision.
    #
    # bnc#796580 The problem with this is that due to compatibility with
    # older profiles, a missing element may have a different meaning than
    # "use what the filesystem/kernel currently uses".
    # In particular, a missing write_hostname means
    # "use the product default from DVD1/control.xml".
    # Other elements may have similar problems,
    # to be fixed post-PTF for maintenance.
    #
    # @param instsys_dns [Hash, nil] first map with DNS configuration
    # @param ay_dns [Hash, nil] second map with DNS configuration
    #
    # @return merged DNS maps or empty map
    def merge_dns(instsys_dns, ay_dns)
      ay_dns ||= {}
      instsys_dns ||= {}

      instsys_dns.delete_if { |k, _v| k == "write_hostname" }.merge(ay_dns)
    end

    # Merges two maps with routing related values
    #
    # Value in second map has precendence over the value in first one in
    # case of key collision.
    #
    # @param instsys_routing [Hash, nil] first map with routing configuration
    # @param ay_routing [Hash, nil] second map with routing configuration
    #
    # @return merged DNS maps or empty map
    def merge_routing(instsys_routing, ay_routing)
      ay_routing ||= {}
      instsys_routing ||= {}

      instsys_routing.merge(ay_routing)
    end

    # Returns networking section of current AY profile
    def ay_networking_section
      @ay_networking_section || {}
    end
  end
end