yast/yast-network

View on GitHub
src/lib/y2network/autoinst_profile/interface_section.rb

Summary

Maintainability
B
6 hrs
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 "installation/autoinst_profile/section_with_attributes"
require "y2network/autoinst_profile/alias_section"

module Y2Network
  module AutoinstProfile
    # This class represents an AutoYaST <interface> section under <interfaces>
    #
    #  <interface>
    #    <bootproto>static</bootproto>
    #    <broadcast>127.255.255.255</broadcast>
    #    <device>lo</device>
    #    <lladdr>02:02:02:02:02:02</lladdr>
    #    <firewall>no</firewall>
    #    <ipaddr>127.0.0.1</ipaddr>
    #    <netmask>255.0.0.0</netmask>
    #    <network>127.0.0.0</network>
    #    <prefixlen>8</prefixlen>
    #    <startmode>nfsroot</startmode>
    #    <usercontrol>no</usercontrol>
    #  </interface>
    #
    # @see InterfacesSection
    class InterfaceSection < ::Installation::AutoinstProfile::SectionWithAttributes
      def self.attributes
        [
          { name: :bootproto },
          { name: :broadcast },
          { name: :device },
          { name: :name }, # has precedence over device
          { name: :lladdr },
          { name: :description },
          { name: :ipaddr },
          { name: :remote_ipaddr },
          { name: :netmask },
          { name: :network }, # TODO: what it is? looks like ipaddr with applied prefix
          { name: :prefixlen }, # has precedence over netmask
          { name: :startmode },
          { name: :ifplugd_priority },
          { name: :usercontrol }, # no longer used, ignored
          { name: :dhclient_set_hostname },
          { name: :bonding_master },
          { name: :bonding_slave0 },
          { name: :bonding_slave1 },
          { name: :bonding_slave2 },
          { name: :bonding_slave3 },
          { name: :bonding_slave4 },
          { name: :bonding_slave5 },
          { name: :bonding_slave6 },
          { name: :bonding_slave7 },
          { name: :bonding_slave8 },
          { name: :bonding_slave9 },
          { name: :bonding_module_opts },
          { name: :aliases },
          { name: :mtu },
          { name: :ethtool_options },
          { name: :wireless }, # TODO: what it is?
          { name: :firewall }, # yes/no
          { name: :zone }, # firewall zone
          { name: :dhclient_set_down_link }, # TODO: what it do?
          { name: :dhclient_set_default_route }, # TODO: what it do?
          { name: :vlan_id },
          { name: :etherdevice },
          { name: :bridge }, # yes/no # why? bridge always have to be yes
          { name: :bridge_ports },
          { name: :bridge_stp }, # on/off
          { name: :bridge_forward_delay },
          { name: :wireless_ap },
          { name: :wireless_auth_mode },
          { name: :wireless_bitrate },
          { name: :wireless_ca_cert },
          { name: :wireless_channel },
          { name: :wireless_client_cert },
          { name: :wireless_client_key },
          { name: :wireless_client_key_password },
          { name: :wireless_default_key },
          { name: :wireless_eap_auth },
          { name: :wireless_eap_mode },
          { name: :wireless_essid },
          { name: :wireless_frequency },
          { name: :wireless_key }, # default wep key
          { name: :wireless_key0 },
          { name: :wireless_key1 },
          { name: :wireless_key2 },
          { name: :wireless_key3 },
          { name: :wireless_key_length },
          { name: :wireless_mode },
          { name: :wireless_nick },
          { name: :wireless_nwid },
          { name: :wireless_peap_version },
          { name: :wireless_power },
          { name: :wireless_wpa_anonid },
          { name: :wireless_wpa_identity },
          { name: :wireless_wpa_password },
          { name: :wireless_wpa_psk }
        ]
      end

      define_attr_accessors

      # @!attribute bootproto
      #  @return [String] boot protocol

      # @!attribute broadcast
      #  @return [String] broadcast ip address.

      # @!attribute device
      #  @return [String] device name. Deprecated. `name` should be used instead.

      # @!attribute name
      #  @return [String] device name.

      # @!attribute lladdr
      #  @return [String] ip address.

      # @!attribute ipaddr
      #  @return [String] ip address.

      # @!attribute remote_ipaddr
      #  @return [String] remote ip address for ptp connections.

      # @!attribute netmask
      #  @return [String] network mask. Deprecated `prefix` should be used instead.

      # @!attribute network
      #  @return [String] network ip after prefix applied. Deprecated as it can
      #    be computed from ipaddr and prefixlen.

      # @!attribute prefixlen
      #  @return [String] size of network prefix.

      # @!attribute startmode
      #  @return [String] when to start network.

      # @!attribute ifplugd_priority
      #  @return [String] priority for ifplugd startmode.

      # @!attribute usercontrol
      #  @return [String] no clue what it means, but it is ignored now.

      # @!attribute dhclient_set_hostname
      #  @return [String] if dhcp sets hostname. "yes" if sets, "no" not set and nil not specified

      # @!attribute bonding_master
      #  @return [String] ???

      # @!attribute bonding_slaveX
      #  @return [String] bonding port on position X

      # @!attribute bonding_module_opts
      #  @return [String] bonding options

      # @!attribute aliases
      #  @return [Array<AliasSection>] list of IP aliases

      # @!attribute mtu
      #  @return [String] MTU for interface

      # @!attribute ethtool_options
      #  @return [String] options for ethtool

      # @!attribute wireless
      #  @return [String] ???

      # @!attribute firewall
      #  @return [String] ???

      # @!attribute zone
      #  @return [String] firewall zone to which interface belongs

      # @!attribute dhclient_set_down_link
      #  @return [String] ???

      # @!attribute dhclient_set_default_route
      #  @return [String] ???

      # @!attribute vlan_id
      #  @return [String] id of vlan

      # @!attribute etherdevice
      #  @return [String] parent device of vlan

      # @!attribute bridge
      #  @return [String] "yes" if device is bridge

      # @!attribute bridge_ports
      #  @return [String] bridge ports separated by space

      # @!attribute bridge_stp
      #  @return [String] "on" if stp is enabled

      # @!attribute bridge_forward_delay
      #  @return [String] time of delay

      # @!attribute wireless_ap
      # @!attribute wireless_auth_mode
      # @!attribute wireless_bitrate
      # @!attribute wireless_ca_cert
      # @!attribute wireless_channel
      # @!attribute wireless_client_cert
      # @!attribute wireless_client_key
      # @!attribute wireless_client_key_password
      # @!attribute wireless_default_key
      # @!attribute wireless_eap_auth
      # @!attribute wireless_eap_mode
      # @!attribute wireless_essid
      # @!attribute wireless_frequency
      # @!attribute wireless_key
      # @!attribute wireless_keyX
      #  @return [String] key on position X
      # @!attribute wireless_key_length
      # @!attribute wireless_mode
      # @!attribute wireless_nick
      # @!attribute wireless_nwid
      # @!attribute wireless_peap_version
      # @!attribute wireless_power
      # @!attribute wireless_wpa_anonid
      # @!attribute wireless_wpa_identity
      # @!attribute wireless_wpa_password
      # @!attribute wireless_wpa_psk

      # Clones a network interface into an AutoYaST interface section
      #
      # @param connection_config [Y2Network::ConnectionConfig] Network connection config
      # @param parent [SectionWithAttributes,nil] Parent section
      # @return [InterfacesSection]
      def self.new_from_network(connection_config, parent = nil)
        result = new(parent)
        result.init_from_config(connection_config)
        result
      end

      def initialize(*_args)
        super

        # TODO: Initializing all the attributes to an empty string makes
        # hard to know whether the value was defined or not at all. We probably
        # should ommit this initialization
        self.class.attributes.each do |attr|
          # init everything to empty string
          public_send(:"#{attr[:name]}=", "")
        end

        self.aliases = []
      end

      # Overwrite base method to load also nested aliases
      def init_from_hashes(hash)
        hash = rename_key(hash, "bridge_forwarddelay", "bridge_forward_delay")
        super(hash)

        # When the name and the device attributes are given then the pre network-ng behavior will be
        # adopted using the name as the description and the device as the name (bsc#1192270).
        unless hash.fetch("name", "").empty? || hash.fetch("device", "").empty?
          self.name = hash["device"]
          self.description = hash["name"]
        end

        return unless hash["aliases"].is_a?(Hash)

        self.aliases = hash["aliases"].values.map { |h| AliasSection.new_from_hashes(h) }
      end

      # Method used by {.new_from_network} to populate the attributes when cloning a network
      # interface
      #
      # @param config [Y2Network::ConnectionConfig]
      # @return [Boolean]
      def init_from_config(config)
        # nil bootproto is valid use case (missing explicit setup) - wicked defaults to static then
        @bootproto = config.bootproto&.name
        @name = config.name
        @lladdr = config.lladdress
        if config.bootproto == BootProtocol::STATIC && config.ip
          # missing ip is valid scenario for wicked - so use empty string here
          @ipaddr = config.ip.address&.address.to_s
          @prefixlen = config.ip.address&.prefix.to_s
          @remote_ipaddr = config.ip.remote_address.address.to_s if config.ip.remote_address
          @broadcast = config.ip.broadcast.address.to_s if config.ip.broadcast
        end

        @dhclient_set_hostname = case config.dhclient_set_hostname
        when true then "yes"
        when false then "no"
        when nil then ""
        end
        @startmode = config.startmode.name
        @ifplugd_priority = config.startmode.priority.to_s if config.startmode.name == "ifplugd"
        @mtu = config.mtu.to_s if config.mtu
        @ethtool_options = config.ethtool_options if config.ethtool_options
        @zone = config.firewall_zone.to_s
        # see aliases for example output
        @aliases = config.ip_aliases.map { |ip| AliasSection.new_from_network(ip) }

        case config
        when ConnectionConfig::Vlan
          @vlan_id = config.vlan_id.to_s
          @etherdevice = config.parent_device
        when ConnectionConfig::Bridge
          @bridge = "yes"
          @bridge_ports = config.ports.join(" ")
          @bridge_stp = config.stp ? "on" : "off"
          @bridge_forward_delay = config.forward_delay.to_s
        when ConnectionConfig::Bonding
          @bonding_module_opts = config.options
          config.ports.each_with_index do |port, index|
            public_send(:"bonding_slave#{index}=", port)
          end
        when ConnectionConfig::Wireless
          init_from_wireless(config)
        end

        true
      end

      # @see SectionWithAttributes#to_hashes
      def to_hashes
        hash = super
        alias_sections = hash.delete("aliases")
        if alias_sections && !alias_sections.empty?
          hash["aliases"] = alias_sections.each_with_object({}).each_with_index do |(a, all), idx|
            all["alias#{idx}"] = a
          end
        end
        hash
      end

      # Helper to get wireless keys as array
      # @return [Array<String>]
      def wireless_keys
        keys = []
        (0..3).each do |i|
          key = public_send(:"wireless_key#{i}")
          keys << key unless key.empty?
        end

        keys
      end

      # Helper to get devices in the bonding as array
      # @return [Array<String>]
      def bonding_slaves
        ports = []

        (0..9).each do |i|
          port = public_send(:"bonding_slave#{i}")
          ports << port unless port.empty?
        end

        ports
      end

      # Returns the collection name
      #
      # @return [String] "interfaces"
      def collection_name
        "interfaces"
      end

      # Returns the section path
      #
      # @return [Installation::AutoinstProfile::ElementPath,nil] Section path or
      #   nil if the parent is not set
      def section_path
        return nil unless parent

        parent.section_path.join(index)
      end

    private

      def init_from_wireless(config)
        @wireless_mode = config.mode
        @wireless_ap = config.ap
        @wireless_bitrate = config.bitrate.to_s
        @wireless_ca_cert = config.ca_cert
        @wireless_channel = config.channel.to_s if config.channel
        @wireless_client_cert = config.client_cert
        @wireless_client_key = config.client_key
        @wireless_client_key_password = config.client_key_password
        @wireless_essid = config.essid
        @wireless_auth_mode = config.auth_mode.to_s
        @wireless_nick = config.nick
        @wireless_nwid = config.nwid
        @wireless_wpa_anonid = config.wpa_anonymous_identity
        @wireless_wpa_identity = config.wpa_identity
        @wireless_wpa_password = config.wpa_password
        @wireless_wpa_psk = config.wpa_psk
        config.keys.each_with_index do |key, index|
          public_send(:"wireless_key#{index}=", key)
        end
        @wireless_key = config.default_key.to_s
        @wireless_key_length = config.key_length.to_s
        # power dropped
        # peap version not supported yet
        # on other hand ap scan mode is not in autoyast
      end

      # Renames a key from the hash
      #
      # It returns a new hash with the key {old_name} renamed to {new_name}. The rename will
      # not happen if the {to} key already exists.
      #
      # @param hash [Hash]
      # @param old_name [String] Original key name
      # @param new_name [String] New key name
      # @return [Hash]
      def rename_key(hash, old_name, new_name)
        return hash if hash[new_name] || hash[old_name].nil?

        new_hash = hash.clone
        new_hash[new_name] = new_hash.delete(old_name)
        new_hash
      end
    end
  end
end