class VagrantPlugins::ProviderLibvirt::Action::CreateNetworkInterfaces

Create network interfaces for domain, before domain is running. Networks for connecting those interfaces should be already prepared.

Public Class Methods

new(app, env) click to toggle source
# File lib/vagrant-libvirt/action/create_network_interfaces.rb, line 17
def initialize(app, env)
  @logger = Log4r::Logger.new('vagrant_libvirt::action::create_network_interfaces')
  @management_network_name = env[:machine].provider_config.management_network_name
  config = env[:machine].provider_config
  @nic_model_type = config.nic_model_type || 'virtio'
  @nic_adapter_count = config.nic_adapter_count
  @app = app
end

Public Instance Methods

call(env) click to toggle source
# File lib/vagrant-libvirt/action/create_network_interfaces.rb, line 26
def call(env)
  # Get domain first.
  begin
    domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(
      env[:machine].id.to_s
    )
  rescue => e
    raise Errors::NoDomainError,
          error_message: e.message
  end

  # Setup list of interfaces before creating them.
  adapters = []

  # Vagrant gives you adapter 0 by default
  # Assign interfaces to slots.
  configured_networks(env, @logger).each do |options|
    # don't need to create interface for this type
    next if options[:iface_type] == :forwarded_port

    # TODO: fill first ifaces with adapter option specified.
    if options[:adapter]
      if adapters[options[:adapter]]
        raise Errors::InterfaceSlotNotAvailable
      end

      free_slot = options[:adapter].to_i
      @logger.debug "Using specified adapter slot #{free_slot}"
    else
      free_slot = find_empty(adapters)
      @logger.debug "Adapter not specified so found slot #{free_slot}"
      raise Errors::InterfaceSlotExhausted if free_slot.nil?
    end

    # We have slot for interface, fill it with interface configuration.
    adapters[free_slot] = options
    adapters[free_slot][:network_name] = interface_network(
      env[:machine].provider.driver.connection.client, adapters[free_slot]
    )
  end

  # Create each interface as new domain device.
  @macs_per_network = Hash.new(0)
  adapters.each_with_index do |iface_configuration, slot_number|
    @iface_number = slot_number
    @network_name = iface_configuration[:network_name]
    @source_options = {
      network: @network_name
    }
    @mac = iface_configuration.fetch(:mac, false)
    @model_type = iface_configuration.fetch(:model_type, @nic_model_type)
    @driver_name = iface_configuration.fetch(:driver_name, false)
    @driver_queues = iface_configuration.fetch(:driver_queues, false)
    @device_name = iface_configuration.fetch(:iface_name, false)
    @mtu = iface_configuration.fetch(:mtu, nil)
    @pci_bus = iface_configuration.fetch(:bus, nil)
    @pci_slot = iface_configuration.fetch(:slot, nil)
    template_name = 'interface'
    @type = nil
    @udp_tunnel = nil
    # Configuration for public interfaces which use the macvtap driver
    if iface_configuration[:iface_type] == :public_network
      @device = iface_configuration.fetch(:dev, 'eth0')
      @mode = iface_configuration.fetch(:mode, 'bridge')
      @type = iface_configuration.fetch(:type, 'direct')
      @model_type = iface_configuration.fetch(:model_type, @nic_model_type)
      @driver_name = iface_configuration.fetch(:driver_name, false)
      @driver_queues = iface_configuration.fetch(:driver_queues, false)
      @portgroup = iface_configuration.fetch(:portgroup, nil)
      @network_name = iface_configuration.fetch(:network_name, @network_name)
      template_name = 'public_interface'
      @logger.info("Setting up public interface using device #{@device} in mode #{@mode}")
      @ovs = iface_configuration.fetch(:ovs, false)
      @ovs_interfaceid = iface_configuration.fetch(:ovs_interfaceid, false)
      @trust_guest_rx_filters = iface_configuration.fetch(:trust_guest_rx_filters, false)
    # configuration for udp or tcp tunnel interfaces (p2p conn btwn guest OSes)
    elsif iface_configuration.fetch(:tunnel_type, nil)
      @type = iface_configuration.fetch(:tunnel_type)
      @tunnel_port = iface_configuration.fetch(:tunnel_port, nil)
      raise Errors::TunnelPortNotDefined if @tunnel_port.nil?
      if @type == 'udp'
        # default udp tunnel source to 127.0.0.1
        @udp_tunnel={
          address: iface_configuration.fetch(:tunnel_local_ip,'127.0.0.1'),
          port: iface_configuration.fetch(:tunnel_local_port)
        }
      end
      # default mcast tunnel to 239.255.1.1. Web search says this
      # 239.255.x.x is a safe range to use for general use mcast
      default_ip = if @type == 'mcast'
                     '239.255.1.1'
                   else
                     '127.0.0.1'
                   end
      @source_options = {
        address: iface_configuration.fetch(:tunnel_ip, default_ip),
        port: @tunnel_port
      }
      @tunnel_type = iface_configuration.fetch(:model_type, @nic_model_type)
      @driver_name = iface_configuration.fetch(:driver_name, false)
      @driver_queues = iface_configuration.fetch(:driver_queues, false)
      template_name = 'tunnel_interface'
      @logger.info("Setting up #{@type} tunnel interface using  #{@tunnel_ip} port #{@tunnel_port}")
    end

    message = "Creating network interface eth#{@iface_number}"
    message += " connected to network #{@network_name}."
    if @mac
      @mac = @mac.scan(/(\h{2})/).join(':')
      message += " Using MAC address: #{@mac}"
    end
    @logger.info(message)

    begin
      # FIXME: all options for network driver should be hash from Vagrantfile
      driver_options = {}
      driver_options[:name] = @driver_name if @driver_name
      driver_options[:queues] = @driver_queues if @driver_queues
      @udp_tunnel ||= {}
      xml = if template_name == 'interface' or
               template_name == 'tunnel_interface'
              interface_xml(@type,
                            @source_options,
                            @mac,
                            @device_name,
                            @iface_number,
                            @model_type,
                            @mtu,
                            driver_options,
                            @udp_tunnel,
                            @pci_bus,
                            @pci_slot)
            else
              to_xml(template_name)
            end
      @logger.debug {
        "Attaching Network Device with XML:\n#{xml}"
      }
      domain.attach_device(xml)
    rescue => e
      raise Errors::AttachDeviceError,
            error_message: e.message
    end

    # Re-read the network configuration and grab the MAC address
    if iface_configuration[:iface_type] == :public_network
      xml = Nokogiri::XML(domain.xml_desc)
      source = "@network='#{@network_name}'"
      if @type == 'direct'
          source = "@dev='#{@device}'"
      elsif @portgroup.nil?
        source = "@bridge='#{@device}'"
      end
      if not @mac
        macs = xml.xpath("/domain/devices/interface[source[#{source}]]/mac/@address")
        @mac = macs[@macs_per_network[source]]
        iface_configuration[:mac] = @mac.to_s
      end
      @macs_per_network[source] += 1
    end
  end

  # Continue the middleware chain.
  @app.call(env)

  if env[:machine].config.vm.box
    # Configure interfaces that user requested. Machine should be up and
    # running now.
    networks_to_configure = []

    adapters.each_with_index do |options, slot_number|
      # Skip configuring the management network, which is on the first interface.
      # It's used for provisioning and it has to be available during provisioning,
      # ifdown command is not acceptable here.
      next if slot_number.zero?
      next if options[:auto_config] === false
      @logger.debug "Configuring interface slot_number #{slot_number} options #{options}"

      network = {
        interface: slot_number,
        use_dhcp_assigned_default_route: options[:use_dhcp_assigned_default_route],
        mac_address: options[:mac]
      }

      if options[:ip]
        network = {
          type: :static,
          ip: options[:ip],
          netmask: options[:netmask],
          gateway: options[:gateway],
          route: options[:route]
        }.merge(network)
        if IPAddr.new(options[:ip]).ipv6?
          network[:type] = :static6
        end
      else
        network[:type] = :dhcp
      end

      networks_to_configure << network
    end

    unless networks_to_configure.empty?
      env[:ui].info I18n.t('vagrant.actions.vm.network.configuring')
      env[:machine].guest.capability(
        :configure_networks, networks_to_configure
      )
    end

  end
end

Private Instance Methods

find_empty(array, start = 0, stop = @nic_adapter_count) click to toggle source
# File lib/vagrant-libvirt/action/create_network_interfaces.rb, line 276
def find_empty(array, start = 0, stop = @nic_adapter_count)
  (start..stop).each do |i|
    return i unless array[i]
  end
  nil
end
interface_network(libvirt_client, options) click to toggle source

Return network name according to interface options.

# File lib/vagrant-libvirt/action/create_network_interfaces.rb, line 284
def interface_network(libvirt_client, options)
  # no need to get interface network for tcp tunnel config
  return 'tunnel_interface' if options.fetch(:tunnel_type, nil)

  if options[:network_name]
    @logger.debug 'Found network by name'
    return options[:network_name]
  end

  # Get list of all (active and inactive) Libvirt networks.
  available_networks = libvirt_networks(libvirt_client)

  return 'public' if options[:iface_type] == :public_network

  if options[:ip]
    address = network_address(options[:ip], options[:netmask])
    available_networks.each do |network|
      if address == network[:network_address]
        @logger.debug 'Found network by ip'
        return network[:name]
      end
    end
  end

  raise Errors::NetworkNotAvailableError, network_name: options[:ip]
end
interface_xml(type, source_options, mac, device_name, iface_number, model_type, mtu, driver_options, udp_tunnel={}, pci_bus, pci_slot) click to toggle source
# File lib/vagrant-libvirt/action/create_network_interfaces.rb, line 252
def interface_xml(type, source_options, mac, device_name,
                  iface_number, model_type, mtu, driver_options,
                  udp_tunnel={}, pci_bus, pci_slot)
  Nokogiri::XML::Builder.new do |xml|
    xml.interface(type: type || 'network') do
      xml.alias(name: "ua-net-#{iface_number}")
      xml.source(source_options) do
        xml.local(udp_tunnel) if type == 'udp'
      end
      xml.mac(address: mac) if mac
      xml.target(dev: target_dev_name(device_name, type, iface_number))
      xml.alias(name: "net#{iface_number}")
      xml.model(type: model_type.to_s)
      xml.mtu(size: Integer(mtu)) if mtu
      xml.driver(driver_options)
      xml.address(type: 'pci', bus: pci_bus, slot: pci_slot) if pci_bus and pci_slot
    end
  end.to_xml(
    save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
               Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
               Nokogiri::XML::Node::SaveOptions::FORMAT
  )
end
target_dev_name(device_name, type, iface_number) click to toggle source
# File lib/vagrant-libvirt/action/create_network_interfaces.rb, line 240
def target_dev_name(device_name, type, iface_number)
  if device_name
    device_name
  elsif type == 'network'
    "vnet#{iface_number}"
  else
    # TODO can we use same name vnet#ifnum?
    #"tnet#{iface_number}" FIXME plugin vagrant-libvirt trying to create second tnet0 interface
    "vnet#{iface_number}"
  end
end