yast/yast-network

View on GitHub
src/lib/y2network/virtualization_config.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 "yast"
require "y2network/interface_config_builder"

module Y2Network
  # This class is responsible for creating a bridge configuration for
  # virtualization from the interfaces that are connected and bridgeable.
  class VirtualizationConfig
    include Yast::Logger
    # @return [Y2Network::Config]
    attr_reader :config

    # Constructor
    #
    # @param config [Y2Network::Config]
    def initialize(config)
      @config = config
    end

    # Obtains the interfaces that are candidates for being bridgeable
    #
    # @return [Array<Y2Network::Interface]
    def bridgeable_candidates
      builder = Y2Network::InterfaceConfigBuilder.for("br")
      builder.name = config.interfaces.free_name("br")

      config.interfaces.select { |i| connected_and_bridgeable?(builder, i) }
    end

    # Iterates over the bridgeable candidates creating a bridge for each of
    # them. The connection is copied to the bridge when exist and the
    # interface is added as a bridge port
    #
    # @return [Boolean] true when a new bridge was created
    def create
      return false if bridgeable_candidates.empty?

      bridgeable_candidates.each do |interface|
        bridge_builder = bridge_builder_for(interface)

        connection = config.connections.by_name(interface.name)
        # The configuration of the connection being included is copied to the
        # bridge when exist
        bridge_builder.configure_from(connection) if connection

        builder = Y2Network::InterfaceConfigBuilder.for(interface.type, config: connection)
        builder.name = interface.name
        builder.configure_as_port
        builder.save

        # It adds the connection and the virtual interface
        bridge_builder.save

        # Move routes from the port member to the bridge (bsc#903889)
        move_routes(builder.name, bridge_builder.name)
      end

      true
    end

  private

    # Adds a new interface with the given name
    def add_device_to_routing(name)
      return if !config
      return if config.interfaces.any? { |i| i.name == name }

      config.interfaces << Y2Network::Interface.new(name)
    end

    # Assigns all the routes from one interface to another
    #
    # @param from [String] interface belonging the routes to be moved
    # @param to [String] target interface
    def move_routes(from, to)
      return unless config&.routing

      routing = config.routing
      add_device_to_routing(to)
      target_interface = config.interfaces.by_name(to)
      return unless target_interface

      routing.routes.select { |r| r.interface && r.interface.name == from }
        .each { |r| r.interface = target_interface }
    end

    # Convenience method that returns true if the interface given is connected
    # and can be added as a bridge port.
    #
    # @param bridge_builder [Y2Network::InterfaceConfigBuilders::Bridge]
    # @param interface [Y2Network::Interface] bridge candidate member
    # @return [Boolean] true if it is connected and bridgeable
    def connected_and_bridgeable?(bridge_builder, interface)
      if !bridge_builder.bridgeable_interfaces.map(&:name).include?(interface.name)
        log.info "The interface #{interface.name} cannot be proposed as bridge."
        return false
      end

      unless interface.connected?
        log.warn("The interface #{interface.inspect} does not have link")
        return false
      end

      if interface.type.wireless?
        log.warn("Not proposing WLAN interface for lan item: #{interface.inspect}")
        return false
      end
      true
    end

    # Convenience method for initializing a new bridge builder with the
    # interface given as a port member
    #
    # @param interface [Y2Network::Interface]
    def bridge_builder_for(interface)
      bridge_builder = Y2Network::InterfaceConfigBuilder.for("br")
      bridge_builder.name = config.interfaces.free_name("br")

      bridge_builder.ports = [interface.name]
      bridge_builder.startmode = "auto"
      bridge_builder.boot_protocol = "dhcp"

      bridge_builder
    end
  end
end