mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
295 lines
12 KiB
Ruby
295 lines
12 KiB
Ruby
require 'log4r'
|
|
require 'vagrant/util/network_ip'
|
|
require 'vagrant/util/scoped_hash_override'
|
|
|
|
module VagrantPlugins
|
|
module ProviderLibvirt
|
|
module Action
|
|
# Create network interfaces for domain, before domain is running.
|
|
# Networks for connecting those interfaces should be already prepared.
|
|
class CreateNetworkInterfaces
|
|
include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
|
|
include VagrantPlugins::ProviderLibvirt::Util::NetworkUtil
|
|
include Vagrant::Util::NetworkIP
|
|
include Vagrant::Util::ScopedHashOverride
|
|
|
|
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
|
|
|
|
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|
|
|
# dont 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)
|
|
template_name = 'interface'
|
|
# 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)
|
|
@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,
|
|
driver_options,
|
|
@udp_tunnel)
|
|
else
|
|
to_xml(template_name)
|
|
end
|
|
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]
|
|
}.merge(network)
|
|
else
|
|
network[:type] = :dhcp
|
|
end
|
|
|
|
# do not run configure_networks for tcp tunnel interfaces
|
|
next if options.fetch(:tunnel_type, nil)
|
|
|
|
networks_to_configure << network
|
|
end
|
|
|
|
env[:ui].info I18n.t('vagrant.actions.vm.network.configuring')
|
|
env[:machine].guest.capability(
|
|
:configure_networks, networks_to_configure
|
|
)
|
|
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
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
|
|
|
|
def interface_xml(type, source_options, mac, device_name,
|
|
iface_number, model_type, driver_options,
|
|
udp_tunnel={})
|
|
Nokogiri::XML::Builder.new do |xml|
|
|
xml.interface(type: type || 'network') do
|
|
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.driver(driver_options)
|
|
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
|
|
|
|
def find_empty(array, start = 0, stop = @nic_adapter_count)
|
|
(start..stop).each do |i|
|
|
return i unless array[i]
|
|
end
|
|
nil
|
|
end
|
|
|
|
# Return network name according to interface options.
|
|
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
|
|
end
|
|
end
|
|
end
|
|
end
|