class VagrantPlugins::ProviderLibvirt::Action::CreateNetworks

Prepare all networks needed for domain connections.

Public Class Methods

new(app, env) click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 20
def initialize(app, env)
  mess = 'vagrant_libvirt::action::create_networks'
  @logger = Log4r::Logger.new(mess)
  @app = app

  @available_networks = []
  @options = {}
  @libvirt_client = env[:machine].provider.driver.connection.client
end

Public Instance Methods

call(env) click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 30
def call(env)
  if env[:machine].provider_config.qemu_use_session
    # Get a list of all (active and inactive) Libvirt networks. This
    # triggers a side effect to ensure networking is fully available
    # for VMs using sessions. It is likely that this should be done
    # to determine the correct virtual device for the management
    # network for sessions instead of assuming the default of virbr0.
    @available_networks = libvirt_networks(
      env[:machine].provider.driver.system_connection
    )

    @app.call(env)
    return
  end

  # only one vm at a time should try to set up networks
  # otherwise they'll have inconsistent views of current state
  # and conduct redundant operations that cause errors
  @@lock.synchronize do
    # Iterate over networks If some network is not
    # available, create it if possible. Otherwise raise an error.
    configured_networks(env, @logger).each do |options|
      # Only need to create private networks
      next if options[:iface_type] != :private_network ||
              options.fetch(:tunnel_type, nil)
      @logger.debug "Searching for network with options #{options}"

      # should fix other methods so this doesn't have to be instance var
      @options = options

      # Get a list of all (active and inactive) Libvirt networks. This
      # list is used throughout this class and should be easier to
      # process than Libvirt API calls.
      @available_networks = libvirt_networks(
        env[:machine].provider.driver.connection.client
      )

      current_network = @available_networks.detect { |network| network[:name] == @options[:network_name] }

      # Prepare a hash describing network for this specific interface.
      @interface_network = {
        name:             nil,
        ip_address:       nil,
        netmask:          @options[:netmask],
        network_address:  nil,
        bridge_name:      nil,
        domain_name:      nil,
        ipv6_address:     options[:ipv6_address] || nil,
        ipv6_prefix:      options[:ipv6_prefix] || nil,
        created:          current_network.nil? ? false : true,
        active:           current_network.nil? ? false : current_network[:active],
        autostart:        options[:autostart] || false,
        guest_ipv6:       @options[:guest_ipv6] || 'yes',
        libvirt_network:  current_network.nil? ? nil : current_network[:libvirt_network]
      }

      if @options[:ip]
        handle_ip_option(env)
      elsif @options[:type].to_s == 'dhcp'
        handle_dhcp_private_network(env)
      elsif @options[:network_name]
        handle_network_name_option(env)
      else
        raise Errors::CreateNetworkError, error_message: @options
      end

      autostart_network if @interface_network[:autostart]
      activate_network unless @interface_network[:active]
    end
  end

  @app.call(env)
end

Private Instance Methods

activate_network() click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 392
def activate_network
  @interface_network[:libvirt_network].create
rescue => e
  raise Errors::ActivateNetworkError, error_message: e.message
end
autostart_network() click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 386
def autostart_network
  @interface_network[:libvirt_network].autostart = true
rescue => e
  raise Errors::AutostartNetworkError, error_message: e.message
end
create_private_network(env) click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 312
def create_private_network(env)
  @network_name = @interface_network[:name]
  @network_bridge_name = @interface_network[:bridge_name]
  @network_address = @interface_network[:ip_address]
  @network_netmask = @interface_network[:netmask]
  @network_mtu = Integer(@options[:mtu]) if @options[:mtu]

  @guest_ipv6 = @interface_network[:guest_ipv6]

  @network_ipv6_address = @interface_network[:ipv6_address]
  @network_ipv6_prefix = @interface_network[:ipv6_prefix]

  @network_bridge_stp = @options[:bridge_stp].nil? || @options[:bridge_stp] ? 'on' : 'off'
  @network_bridge_delay = @options[:bridge_delay] ? @options[:bridge_delay] : 0

  @network_forward_mode = @options[:forward_mode]
  if @options[:forward_device]
    @network_forward_device = @options[:forward_device]
  end

  if @options[:dhcp_enabled]
    # Find out DHCP addresses pool range.
    network_address = "#{@interface_network[:network_address]}/#{(@interface_network[:netmask]).to_s}"
    net = @interface_network[:network_address] ? IPAddr.new(network_address) : nil

    # First is address of network, second is gateway (by default).
    # So start the range two addresses after network address by default.
    # TODO: Detect if this IP is not set on the interface.
    start_address = @options[:dhcp_start] || net.to_range.begin.succ

    # Default to last possible address. (Stop address must not be broadcast address.)
    stop_address = @options[:dhcp_stop] || (net.to_range.end & IPAddr.new('255.255.255.254'))

    @network_dhcp_enabled = true
    @network_dhcp_bootp_file = @options[:dhcp_bootp_file]
    @network_dhcp_bootp_server = @options[:dhcp_bootp_server]
    @network_range_start = start_address
    @network_range_stop = stop_address
  else
    @network_dhcp_enabled = false
  end

  if @options[:tftp_root]
    @tftp_root = @options[:tftp_root]
  end

  @network_domain_name = @options[:domain_name]

  begin
    xml = to_xml('private_network')
    @logger.debug {
      "Creating private network with XML:\n#{xml}"
    }
    @interface_network[:libvirt_network] = \
      @libvirt_client.define_network_xml(xml)
    @logger.debug 'created network'
  rescue => e
    raise Errors::CreateNetworkError, error_message: e.message
  end
end
generate_bridge_name() click to toggle source

Return the first available virbr interface name

# File lib/vagrant-libvirt/action/create_networks.rb, line 302
def generate_bridge_name
  @logger.debug 'generating name for bridge'
  count = 0
  while lookup_bridge_by_name(bridge_name = "virbr#{count}")
    count += 1
  end
  @logger.debug "found available bridge name #{bridge_name}"
  bridge_name
end
get_host_ip_addr(network) click to toggle source

Return provided address or first address of network otherwise

# File lib/vagrant-libvirt/action/create_networks.rb, line 297
def get_host_ip_addr(network)
  @options[:host_ip] ? IPAddr.new(@options[:host_ip]) : IPAddr.new(network).succ
end
handle_dhcp_private_network(env) click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 267
def handle_dhcp_private_network(env)
  net_address = @options[:libvirt__network_address]
  net_address = '172.28.128.0' unless net_address
  network = lookup_network_by_ip(net_address)

  @interface_network = network if network

  # Do we need to create new network?
  unless @interface_network[:created]
    @interface_network[:name] = @options[:network_name] ?
                                @options[:network_name] :
                                'vagrant-private-dhcp'
    @interface_network[:network_address] = net_address

    # Set IP address of network (actually bridge). It will be used as
    # gateway address for machines connected to this network.
    @interface_network[:ip_address] = get_host_ip_addr(net_address)

    # Generate a unique name for network bridge.
    @interface_network[:bridge_name] = generate_bridge_name

    # Create a private network.
    create_private_network(env)
    write_created_network(env)
  else
    write_created_network(env) unless @options[:always_destroy] == false
  end
end
handle_ip_option(env) click to toggle source

Handle only situations, when ip is specified. Variables @options and @available_networks should be filled before calling this function.

# File lib/vagrant-libvirt/action/create_networks.rb, line 139
def handle_ip_option(env)
  return unless @options[:ip]
  net_address = nil

  unless @options[:forward_mode] == 'veryisolated'
    net_address = network_address(@options[:ip], @options[:netmask])

    # Set IP address of network (actually bridge). It will be used as
    # gateway address for machines connected to this network.
    @interface_network[:ip_address] = get_host_ip_addr(net_address)
  end

  @interface_network[:network_address] = net_address

  # if network is veryisolated, search by name
  network = if @options[:libvirt__forward_mode] == 'veryisolated'
              lookup_network_by_name(@options[:network_name])
            elsif net_address
              # otherwise, search by ip (if set)
              lookup_network_by_ip(net_address)
            else
              # leaving this here to mimic prior behavior. If we get
              # here, something's probably broken.
              lookup_network_by_name(@options[:network_name])
            end
  @interface_network = network if network

  verify_dhcp if @interface_network[:created]

  if @options[:network_name]
    @logger.debug 'Checking that network name does not clash with ip'
    if @interface_network[:created]
      # Just check for mismatch error here - if name and ip from
      # config match together.
      if @options[:network_name] != @interface_network[:name] and @qemu_use_agent == false
        raise Errors::NetworkNameAndAddressMismatch,
              ip_address:   @options[:ip],
              network_name: @options[:network_name]
      end
    else
      # Network is not created, but name is set. We need to check,
      # whether network name from config doesn't already exist.
      if lookup_network_by_name @options[:network_name]
        raise Errors::NetworkNameAndAddressMismatch,
              ip_address:   @options[:ip],
              network_name: @options[:network_name]
      end

      # Network with 'name' doesn't exist. Set it as name for new
      # network.
      @interface_network[:name] = @options[:network_name]
    end
  end

  # Do we need to create new network?
  unless @interface_network[:created]

    # TODO: stop after some loops. Don't create infinite loops.

    # Is name for new network set? If not, generate a unique one.
    count = 0
    while @interface_network[:name].nil?
      @logger.debug 'generating name for network'

      # Generate a network name.
      network_name = env[:root_path].basename.to_s.dup
      network_name << count.to_s
      count += 1

      # Check if network name is unique.
      next if lookup_network_by_name(network_name)

      @interface_network[:name] = network_name
    end

    # Generate a unique name for network bridge.
    @interface_network[:bridge_name] = generate_bridge_name

    # Create a private network.
    create_private_network(env)
    write_created_network(env)
  else
    write_created_network(env) unless @options[:always_destroy] == false
  end
end
handle_network_name_option(env) click to toggle source

Handle network_name option, if ip was not specified. Variables @options and @available_networks should be filled before calling this function.

# File lib/vagrant-libvirt/action/create_networks.rb, line 228
def handle_network_name_option(env)
  return if @options[:ip] || \
            !@options[:network_name] || \
            !@options[:libvirt__forward_mode] == 'veryisolated'

  network = lookup_network_by_name(@options[:network_name])
  @interface_network = network if network

  if @options[:libvirt__forward_mode] == 'veryisolated'
    # if this interface has a network address, something's wrong.
    if @interface_network[:network_address]
      raise Errors::NetworkNotAvailableError,
            network_name: @options[:network_name]
    end
  else
    if !@interface_network
      raise Errors::NetworkNotAvailableError,
            network_name: @options[:network_name]
    else
      verify_dhcp
    end
  end

  # Do we need to create new network?
  unless @interface_network[:created]
    @interface_network[:name] = @options[:network_name]
    @interface_network[:ip_address] ||= @options[:host_ip]

    # Generate a unique name for network bridge.
    @interface_network[:bridge_name] = generate_bridge_name

    # Create a private network.
    create_private_network(env)
    write_created_network(env)
  else
    write_created_network(env) unless @options[:always_destroy] == false
  end
end
lookup_bridge_by_name(bridge_name) click to toggle source

Return hash of network for specified bridge, or nil if not found.

# File lib/vagrant-libvirt/action/create_networks.rb, line 118
def lookup_bridge_by_name(bridge_name)
  @logger.debug "looking up bridge named #{bridge_name}"
  @available_networks.find { |network| network[:bridge_name] == bridge_name }
end
lookup_network_by_ip(ip) click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 106
def lookup_network_by_ip(ip)
  @logger.debug "looking up network with ip == #{ip}"
  @available_networks.find { |network| network[:network_address] == ip }
end
lookup_network_by_name(network_name) click to toggle source

Return hash of network for specified name, or nil if not found.

# File lib/vagrant-libvirt/action/create_networks.rb, line 112
def lookup_network_by_name(network_name)
  @logger.debug "looking up network named #{network_name}"
  @available_networks.find { |network| network[:name] == network_name }
end
verify_dhcp() click to toggle source

Throw an error if dhcp setting for an existing network does not match what was configured in the vagrantfile since we always enable dhcp for the management network this ensures we won't start a vm vagrant can't reach Allow the situation where DHCP is not requested (:libvirt__dhcp_enabled == false) but where it is enabled on the virtual network

# File lib/vagrant-libvirt/action/create_networks.rb, line 129
def verify_dhcp
  if @interface_network[:dhcp_enabled] == true && @options[:dhcp_enabled] == false
    raise Errors::DHCPMismatch,
          network_name: @interface_network[:name],
          requested: @options[:dhcp_enabled] ? 'enabled' : 'disabled'
  end
end
write_created_network(env) click to toggle source
# File lib/vagrant-libvirt/action/create_networks.rb, line 373
def write_created_network(env)
  created_networks_file = env[:machine].data_dir + 'created_networks'

  message = 'Saving information about created network ' \
    "#{@interface_network[:name]}, UUID=#{@interface_network[:libvirt_network].uuid} " \
    "to file #{created_networks_file}."
  @logger.info(message)

  File.open(created_networks_file, 'a') do |file|
    file.puts @interface_network[:libvirt_network].uuid
  end
end