yast/yast-network

View on GitHub
src/lib/y2network/wicked/connection_config_readers/base.rb

Summary

Maintainability
A
0 mins
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 "y2network/connection_config/ip_config"
require "y2network/ip_address"
require "y2network/boot_protocol"
require "y2network/startmode"

Yast.import "Host"

module Y2Network
  module Wicked
    module ConnectionConfigReaders
      # This is the base class for connection config readers.
      #
      # The derived classes should implement {#update_connection_config} method.
      # methods.
      class Base
        # @return [CFA::InterfaceFile] Interface's configuration file
        attr_reader :file

        # Constructor
        #
        # @param file [CFA::InterfaceFile] Interface's configuration file
        # @param issues_list [Y2Issues::List] List to register issues
        def initialize(file, issues_list)
          @file = file
          @issues_list = issues_list
        end

        # Builds a connection configuration object
        #
        # @return [Y2Network::ConnectionConfig::Base]
        def connection_config
          connection_class.new.tap do |conn|
            conn.bootproto = find_bootproto
            conn.description = file.name
            conn.interface = file.interface
            conn.ip = all_ips.find { |i| i.id.empty? }
            conn.ip_aliases = all_ips.reject { |i| i.id.empty? }
            conn.name = file.interface
            conn.lladdress = file.lladdr
            conn.startmode = find_startmode
            conn.startmode.priority = file.ifplugd_priority if conn.startmode.name == "ifplugd"
            conn.ethtool_options = file.ethtool_options
            conn.firewall_zone = file.zone
            if file.dhclient_set_hostname
              conn.dhclient_set_hostname = file.dhclient_set_hostname == "yes"
            end
            conn.hostnames = hostnames(conn)
            conn.mtu = file.mtu

            update_connection_config(conn)
          end
        end

      private

        # @return [Y2Issues::List] List to register issues
        attr_reader :issues_list

        DEFAULT_BOOTPROTO = BootProtocol::STATIC

        # Finds the boot protocol
        #
        # If it is not defined or it has an unknown value, it returns the
        # fallback value (BootProtocol::STATIC).
        #
        # @return [BootProtocol]
        def find_bootproto
          bootproto = BootProtocol.from_name(file.bootproto.to_s)
          return bootproto if bootproto

          issue_location = "file:#{file.path}:BOOTPROTO"
          issue = Y2Issues::InvalidValue.new(
            file.bootproto, fallback: BootProtocol::STATIC, location: issue_location
          )
          issues_list << issue

          BootProtocol::STATIC
        end

        DEFAULT_STARTMODE_NAME = "manual".freeze

        # Finds the start mode
        #
        # If it is not defined or it has an unknown value, it returns the
        # fallback value (manual).
        #
        # @return [Startmode]
        def find_startmode
          startmode = Startmode.create(file.startmode) if file.startmode
          return startmode if startmode

          issue_location = "file:#{file.path}:STARTMODE"
          fallback = Startmode.create(DEFAULT_STARTMODE_NAME)
          issue = Y2Issues::InvalidValue.new(
            file.startmode, fallback: fallback, location: issue_location
          )
          issues_list << issue
          fallback
        end

        # Returns the class of the connection configuration
        #
        # @return [Class]
        def connection_class
          class_name = self.class.to_s.split("::").last
          file_name = class_name.gsub(/(\w)([A-Z])/, "\\1_\\2").downcase
          require "y2network/connection_config/#{file_name}"
          Y2Network::ConnectionConfig.const_get(class_name)
        end

        # Sets connection config settings from the given file
        #
        # @note This method should be redefined by derived classes.
        #
        # @param _conn [Y2Network::ConnectionConfig::Base]
        def update_connection_config(_conn); end

        # Returns the IPs configuration from the file
        #
        # @return [Array<Y2Network::ConnectionConfig::IPAdress>] IP addresses configuration
        # @see Y2Network::ConnectionConfig::IPConfig
        def all_ips
          @all_ips ||= file.ipaddrs.each_with_object([]) do |(id, ip), all|
            next unless ip.is_a?(Y2Network::IPAddress)

            ip_address = build_ip(ip, id)

            all << Y2Network::ConnectionConfig::IPConfig.new(
              ip_address,
              id:             id,
              label:          file.labels[id],
              remote_address: file.remote_ipaddrs[id],
              broadcast:      file.broadcasts[id]
            )
          end
        end

        # Builds an IP address
        #
        # It takes an IP address and, optionally, a prefix or a netmask.
        #
        # @param ip      [Y2Network::IPAddress] IP address
        # @param id      [String] Hash key for the IP Address
        def build_ip(ip, id)
          ipaddr = ip.clone
          return ipaddr if ip.prefix?

          assign_ip_netmask(ipaddr, id)
          assign_ip_prefix(ipaddr, id)

          ipaddr
        end

        # @param ip      [Y2Network::IPAddress] IP address
        # @param id      [String] Hash key for the IP Address
        def assign_ip_netmask(ip, id)
          netmask = file.netmasks[id]
          return ip unless netmask

          begin
            ip.netmask = netmask
          rescue StandardError
            issue_location = "file:#{file.path}:NETMASK#{id}"
            issue = Y2Issues::InvalidValue.new(
              netmask, fallback: nil, location: issue_location
            )
            issues_list << issue
          end
        end

        # @param ip      [Y2Network::IPAddress] IP address
        # @param id      [String] Hash key for the IP Address
        def assign_ip_prefix(ip, id)
          prefix = file.prefixlens[id]
          return ip unless prefix

          address = ip.address.clone
          begin
            # Take advantage of the IPAddress address validations when the prefix is assigned not
            # allowing an invalid one (bsc#1206551)
            address.prefix = prefix
            ip.prefix = prefix
          rescue StandardError
            issue_location = "file:#{file.path}:PREFIXLEN#{id}"
            issue = Y2Issues::InvalidValue.new(
              prefix, fallback: nil, location: issue_location
            )
            issues_list << issue
          end
        end

        # Returns the hostnames for the given connection
        #
        # @return [Array<String>]
        def hostnames(conn)
          return [] unless conn.ip

          Yast::Host.Read
          aliases = Yast::Host.names(conn.ip.address.address.to_s).first
          aliases.to_s.split
        end
      end
    end
  end
end