* Private network support.

* Creating private networks if ip address is set and network
  is not available.
* Guest network interfaces configuration.
This commit is contained in:
pradels 2013-05-09 20:32:06 +02:00
parent 60b8e41d08
commit 813a7c811e
9 changed files with 563 additions and 20 deletions

View File

@ -20,9 +20,10 @@ module VagrantPlugins
b2.use HandleBoxImage
b2.use CreateDomainVolume
b2.use CreateDomain
b2.use CreateNetworkInterfaces
b2.use TimedProvision
b2.use CreateNetworks
b2.use CreateNetworkInterfaces
b2.use StartDomain
b2.use WaitTillUp
b2.use SyncFolders
@ -101,6 +102,7 @@ module VagrantPlugins
b2.use ConnectLibvirt
b2.use DestroyDomain
b2.use DestroyNetworks
# Cleanup running instance data. Now only IP address is stored.
b2.use CleanupDataDir
@ -262,8 +264,10 @@ module VagrantPlugins
autoload :SetNameOfDomain, action_root.join("set_name_of_domain")
autoload :CreateDomainVolume, action_root.join("create_domain_volume")
autoload :CreateDomain, action_root.join("create_domain")
autoload :CreateNetworks, action_root.join("create_networks")
autoload :CreateNetworkInterfaces, action_root.join("create_network_interfaces")
autoload :DestroyDomain, action_root.join("destroy_domain")
autoload :DestroyNetworks, action_root.join("destroy_networks")
autoload :StartDomain, action_root.join("start_domain")
autoload :HaltDomain, action_root.join("halt_domain")
autoload :SuspendDomain, action_root.join("suspend_domain")

View File

@ -1,12 +1,18 @@
require 'log4r'
require 'vagrant/util/network_ip'
require 'vagrant/util/scoped_hash_override'
module VagrantPlugins
module Libvirt
module Action
# Create network interfaces for domain, before domain is running.
# Networks for connecting those interfaces should be already prepared.
class CreateNetworkInterfaces
include VagrantPlugins::Libvirt::Util::ErbTemplate
include VagrantPlugins::Libvirt::Util::LibvirtUtil
include Vagrant::Util::NetworkIP
include Vagrant::Util::ScopedHashOverride
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::create_network_interfaces")
@ -23,41 +29,59 @@ module VagrantPlugins
:error_message => e.message
end
# Setup list of interfaces before creating them
# Setup list of interfaces before creating them.
adapters = []
# Assign main interface for provisioning to first slot.
# Use network 'default' as network for ssh connecting and
# machine provisioning. This should be maybe configurable in
# Vagrantfile in future.
adapters[0] = 'default'
# machine provisioning.
#
# TODO Network name with DHCP for first interface should be
# configurable.
adapters[0] = {
:network_name => 'default'
}
# Assign interfaces to slots.
env[:machine].config.vm.networks.each do |type, options|
# Other types than bridged are not supported for now.
next if type != :bridged
# Only private network is supported now. Port forwarding and public
# network are not supported via libvirt API, so they are not
# implemented in this provider.
next if type != :private_network
network_name = 'default'
network_name = options[:bridge] if options[:bridge]
# Get options for this interface. Options can be specified in
# Vagrantfile in short format (:ip => ...), or provider format
# (:libvirt__network_name => ...).
options = scoped_hash_override(options, :libvirt)
options = { :netmask => '255.255.255.0' }.merge(options)
# TODO fill first ifaces with adapter option specified.
if options[:adapter]
if adapters[options[:adapter]]
raise Errors::InterfaceSlotNotAvailable
end
adapters[options[:adapter].to_i] = network_name
free_slot = options[:adapter].to_i
else
empty_slot = find_empty(adapters, start=1)
raise Errors::InterfaceSlotNotAvailable if empty_slot == nil
free_slot = find_empty(adapters, start=1)
raise Errors::InterfaceSlotNotAvailable if free_slot == nil
end
adapters[empty_slot] = network_name
end
# We have slot for interface, fill it with interface configuration.
adapters[free_slot] = options
adapters[free_slot][:network_name] = interface_network(
env[:libvirt_compute].client, adapters[free_slot])
end
# Create each interface as new domain device
adapters.each_with_index do |network_name, slot_number|
# Create each interface as new domain device.
adapters.each_with_index do |iface_configuration, slot_number|
@iface_number = slot_number
@network_name = network_name
@logger.info("Creating network interface eth#{@iface_number}")
@network_name = iface_configuration[:network_name]
message = "Creating network interface eth#{@iface_number}"
message << " connected to network #{@network_name}."
@logger.info(message)
begin
domain.attach_device(to_xml('interface'))
rescue => e
@ -66,7 +90,40 @@ module VagrantPlugins
end
end
# Continue the middleware chain.
@app.call(env)
# 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 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 == 0
network = {
:interface => slot_number,
#:mac => ...,
}
if options[:ip]
network = {
:type => :static,
:ip => options[:ip],
:netmask => options[:netmask],
}.merge(network)
else
network[:type] = :dhcp
end
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
private
@ -77,9 +134,25 @@ module VagrantPlugins
end
return nil
end
end
# Return network name according to interface options.
def interface_network(libvirt_client, options)
return options[:network_name] if options[:network_name]
# Get list of all (active and inactive) libvirt networks.
available_networks = libvirt_networks(libvirt_client)
if options[:ip]
address = network_address(options[:ip], options[:netmask])
available_networks.each do |network|
return network[:name] if address == network[:network_address]
end
end
# TODO Network default can be missing or named different.
return 'default'
end
end
end
end
end

View File

@ -0,0 +1,269 @@
require 'log4r'
require 'vagrant/util/network_ip'
require 'vagrant/util/scoped_hash_override'
require 'ipaddr'
module VagrantPlugins
module Libvirt
module Action
# Prepare all networks needed for domain connections.
class CreateNetworks
include Vagrant::Util::NetworkIP
include Vagrant::Util::ScopedHashOverride
include VagrantPlugins::Libvirt::Util::ErbTemplate
include VagrantPlugins::Libvirt::Util::LibvirtUtil
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::create_networks")
@app = app
@available_networks = []
@options = {}
@libvirt_client = env[:libvirt_compute].client
end
def call(env)
# Iterate over networks requested from config. If some network is not
# available, create it if possible. Otherwise raise an error.
env[:machine].config.vm.networks.each do |type, 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[:libvirt_compute].client)
# Now, we support private networks only. There are two other types
# public network and port forwarding, but there are problems with
# creating them via libvirt API, so this provider doesn't implement
# them.
next if type != :private_network
# Get options for this interface network. Options can be specified
# in Vagrantfile in short format (:ip => ...), or provider format
# (:libvirt__network_name => ...).
@options = scoped_hash_override(options, :libvirt)
@options = {
:netmask => '255.255.255.0',
}.merge(@options)
# 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,
:created => false,
:active => false,
:autostart => false,
:libvirt_network => nil,
}
if @options[:ip]
handle_ip_option(env)
elsif @options[:network_name]
handle_network_name_option
end
autostart_network if not @interface_network[:autostart]
activate_network if not @interface_network[:active]
end
@app.call(env)
end
private
# Return hash of network for specified name, or nil if not found.
def lookup_network_by_name(network_name)
@available_networks.each do |network|
return network if network[:name] == network_name
end
nil
end
# Return hash of network for specified bridge, or nil if not found.
def lookup_bridge_by_name(bridge_name)
@available_networks.each do |network|
return network if network[:bridge_name] == bridge_name
end
nil
end
# Handle only situations, when ip is specified. Variables @options and
# @available_networks should be filled before calling this function.
def handle_ip_option(env)
return if not @options[:ip]
net_address = network_address(@options[:ip], @options[:netmask])
@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.
net = IPAddr.new(net_address)
@interface_network[:ip_address] = net.to_range.begin.succ
# Is there an available network matching to configured ip
# address?
@available_networks.each do |available_network|
if available_network[:network_address] == \
@interface_network[:network_address]
@interface_network = available_network
break
end
end
if @options[:network_name]
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]
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?
if not @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 do
# 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.
count = 0
while @interface_network[:bridge_name] == nil do
bridge_name = 'virbr'
bridge_name << count.to_s
count += 1
next if lookup_bridge_by_name(bridge_name)
@interface_network[:bridge_name] = bridge_name
end
# Create a private network.
create_private_network(env)
end
end
# Handle network_name option, if ip was not specified. Variables
# @options and @available_networks should be filled before calling this
# function.
def handle_network_name_option
return if @options[:ip] or not @options[:network_name]
@interface_network = lookup_network_by_name(@options[:network_name])
if not @interface_network
raise Errors::NetworkNotAvailableError,
:network_name => @options[:network_name]
end
end
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]
if @options[:isolated]
@network_forward_mode = false
else
@network_forward_mode = 'nat'
if @options[:nat_interface]
@network_nat_interface = @options[:nat_interface]
end
end
if @options[:dhcp_enabled]
# Find out DHCP addresses pool range.
network_address = "#{@interface_network[:network_address]}/"
network_address << "#{@interface_network[:netmask]}"
net = IPAddr.new(network_address)
# First is address of network, second is gateway. Start the range two
# addresses after network address.
start_address = net.to_range.begin.succ.succ
# Stop address must not be broadcast.
stop_address = net.to_range.end & IPAddr.new('255.255.255.254')
@network_dhcp_enabled = true
@network_range_start = start_address
@network_range_stop = stop_address
else
@network_dhcp_enabled = false
end
begin
@interface_network[:libvirt_network] = \
@libvirt_client.define_network_xml(to_xml('private_network'))
rescue => e
raise Errors::CreateNetworkError,
:error_message => e.message
end
created_networks_file = env[:machine].data_dir + 'created_networks'
message = "Saving information about created network "
message << "#{@interface_network[:name]}, "
message << "UUID=#{@interface_network[:libvirt_network].uuid} "
message << "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
def autostart_network
begin
@interface_network[:libvirt_network].autostart = true
rescue => e
raise Errors::AutostartNetworkError,
:error_message => e.message
end
end
def activate_network
begin
@interface_network[:libvirt_network].create
rescue => e
raise Errors::ActivateNetworkError,
:error_message => e.message
end
end
end
end
end
end

View File

@ -0,0 +1,80 @@
require 'log4r'
require 'nokogiri'
module VagrantPlugins
module Libvirt
module Action
# Destroy all networks created for this specific domain. Skip
# removing if network has still active connections.
class DestroyNetworks
def initialize(app, env)
@logger = Log4r::Logger.new("vagrant_libvirt::action::destroy_networks")
@app = app
end
def call(env)
# If there were some networks created for this machine, in machines
# data directory, created_networks file holds UUIDs of each network.
created_networks_file = env[:machine].data_dir + 'created_networks'
# If created_networks file doesn't exist, there are no networks we
# need to remove.
return @app.call(env) if not File.exist?(created_networks_file)
# Iterate over each created network UUID and try to remove it.
created_networks = []
file = File.open(created_networks_file, 'r')
file.readlines.each do |network_uuid|
begin
libvirt_network = env[:libvirt_compute].client.lookup_network_by_uuid(
network_uuid)
rescue
next
end
# Maybe network doesn't exist anymore.
next if not libvirt_network
# Skip removing if network has still active connections.
xml = Nokogiri::XML(libvirt_network.xml_desc)
connections = xml.xpath('/network/@connections').first
if connections != nil
created_networks << network_uuid
next
end
# Shutdown network first.
begin
libvirt_network.destroy
rescue => e
end
# Undefine network.
begin
libvirt_network.undefine
rescue => e
raise Error::DestroyNetworkError,
:network_name => libvirt_network.name,
:error_message => e.message
end
end
file.close
# Update status of created networks after removing some/all of them.
if created_networks.length > 0
File.open(created_networks_file, 'w') do |file|
created_networks.each do |network_uuid|
file.puts network_uuid
end
end
else
File.delete(created_networks_file)
end
@app.call(env)
end
end
end
end
end

View File

@ -74,6 +74,30 @@ module VagrantPlugins
error_key(:interface_slot_not_available)
end
class NetworkNameAndAddressMismatch < VagrantLibvirtError
error_key(:network_name_and_address_mismatch)
end
class CreateNetworkError < VagrantLibvirtError
error_key(:create_network_error)
end
class DestroyNetworkError < VagrantLibvirtError
error_key(:destroy_network_error)
end
class NetworkNotAvailableError < VagrantLibvirtError
error_key(:network_not_available_error)
end
class AutostartNetworkError < VagrantLibvirtError
error_key(:autostart_network_error)
end
class ActivateNetworkError < VagrantLibvirtError
error_key(:activate_network_error)
end
class RsyncError < VagrantLibvirtError
error_key(:rsync_error)
end

View File

@ -0,0 +1,21 @@
<network ipv6='yes'>
<name><%= @network_name %></name>
<bridge name="<%= @network_bridge_name %>" />
<% if @network_forward_mode != false %>
<% if @network_nat_interface %>
<forward mode="<%= @network_forward_mode %>" dev="<%= @network_nat_interface %>" />
<% else %>
<forward mode="<%= @network_forward_mode %>" />
<% end %>
<% end %>
<ip address="<%= @network_address %>" netmask="<%= @network_netmask %>">
<% if @network_dhcp_enabled %>
<dhcp>
<range start="<%= @network_range_start %>" end="<%= @network_range_stop %>" />
</dhcp>
<% end %>
</ip>
</network>

View File

@ -4,6 +4,7 @@ module VagrantPlugins
autoload :ErbTemplate, 'vagrant-libvirt/util/erb_template'
autoload :Collection, 'vagrant-libvirt/util/collection'
autoload :Timer, 'vagrant-libvirt/util/timer'
autoload :LibvirtUtil, 'vagrant-libvirt/util/libvirt_util'
end
end
end

View File

@ -0,0 +1,57 @@
require 'nokogiri'
require 'vagrant/util/network_ip'
module VagrantPlugins
module Libvirt
module Util
module LibvirtUtil
include Vagrant::Util::NetworkIP
# Return a list of all (active and inactive) libvirt networks as a list
# of hashes with their name, network address and status (active or not).
def libvirt_networks(libvirt_client)
libvirt_networks = []
active = libvirt_client.list_networks
inactive = libvirt_client.list_defined_networks
# Iterate over all (active and inactive) networks.
active.concat(inactive).each do |network_name|
libvirt_network = libvirt_client.lookup_network_by_name(
network_name)
# Parse ip address and netmask from the network xml description.
xml = Nokogiri::XML(libvirt_network.xml_desc)
ip = xml.xpath('/network/ip/@address').first
ip = ip.value if ip
netmask = xml.xpath('/network/ip/@netmask').first
netmask = netmask.value if netmask
# Calculate network address of network from ip address and
# netmask.
if ip and netmask
network_address = network_address(ip, netmask)
else
network_address = nil
end
libvirt_networks << {
:name => network_name,
:ip_address => ip,
:netmask => netmask,
:network_address => network_address,
:bridge_name => libvirt_network.bridge_name,
:created => true,
:active => libvirt_network.active?,
:autostart => libvirt_network.autostart?,
:libvirt_network => libvirt_network,
}
end
libvirt_networks
end
end
end
end
end

View File

@ -101,6 +101,20 @@ en:
Error while attaching new device to domain. %{error_message}
no_ip_address_error: |-
No IP address found.
network_name_and_address_mismatch: |-
Address %{ip_address} does not match with network name %{network_name}.
Please fix your configuration and run vagrant again.
create_network_error: |-
Error occured while creating new network: %{error_message}.
network_not_available_error: |-
Network %{network_name} is not available. Specify available network
name, or an ip address if you want to create a new network.
activate_network_error: |-
Error while activating network: %{error_message}.
autostart_network_error: |-
Error while setting up autostart on network: %{error_message}.
destroy_network_error: |-
Error while removing network %{network_name}. %{error_message}.
states:
short_paused: |-