vaguerent/plugins/guests/fedora/cap/configure_networks.rb
Seth Vargo 5a4f345363
Use Util::Tempfile when configuring networks
This fixes a fairly large tempfile leak. Vagrant uses a template
renderer to write network configuration files locally to disk. Then,
that temporarily file is uploaded to the remote host and moved into
place. Since Vagrant is such a short-lived process, GC never came along
and cleaned up those tempfiles, resulting in many temporary files being
created through regular Vagrant usage.

The Util::Tempfile class uses a block to ensure the temporary file is
deleted when the block finishes. This API required small tweaks to the
usage, but provides more safety to ensure the files are deleted.
2016-05-28 23:22:34 -04:00

135 lines
5.5 KiB
Ruby

require "set"
require_relative "../../../../lib/vagrant/util/retryable"
require_relative "../../../../lib/vagrant/util/template_renderer"
require_relative "../../../../lib/vagrant/util/tempfile"
module VagrantPlugins
module GuestFedora
module Cap
class ConfigureNetworks
extend Vagrant::Util::Retryable
include Vagrant::Util
def self.configure_networks(machine, networks)
network_scripts_dir = machine.guest.capability("network_scripts_dir")
virtual = false
interface_names = Array.new
interface_names_by_slot = Array.new
machine.communicate.sudo("/usr/sbin/biosdevname &>/dev/null; echo $?") do |_, result|
# The above command returns:
# - '4' if /usr/sbin/biosdevname detects it is running in a virtual machine
# - '127' if /usr/sbin/biosdevname doesn't exist
virtual = true if ['4', '127'].include? result.chomp
end
if virtual
machine.communicate.sudo("ls -v /sys/class/net | egrep -v lo\\|docker") do |_, result|
interface_names = result.split("\n")
end
interface_names_by_slot = networks.map do |network|
"#{interface_names[network[:interface]]}"
end
else
machine.communicate.sudo("/usr/sbin/biosdevname -d | grep Kernel | cut -f2 -d: | sed -e 's/ //;'") do |_, result|
interface_names = result.split("\n")
end
interface_name_pairs = Array.new
interface_names.each do |interface_name|
machine.communicate.sudo("/usr/sbin/biosdevname --policy=all_ethN -i #{interface_name}") do |_, result|
interface_name_pairs.push([interface_name, result.gsub("\n", "")])
end
end
setting_interface_names = networks.map do |network|
"eth#{network[:interface]}"
end
interface_names_by_slot = interface_names.dup
interface_name_pairs.each do |interface_name, previous_interface_name|
if setting_interface_names.index(previous_interface_name) == nil
interface_names_by_slot.delete(interface_name)
end
end
end
# Read interface MAC addresses for later matching
mac_addresses = Array.new(interface_names.length)
interface_names.each_with_index do |ifname, index|
machine.communicate.sudo("cat /sys/class/net/#{ifname}/address") do |_, result|
mac_addresses[index] = result.strip
end
end
# Accumulate the configurations to add to the interfaces file as well
# as what interfaces we're actually configuring since we use that later.
interfaces = Set.new
networks.each do |network|
interface = nil
if network[:mac_address]
found_idx = mac_addresses.find_index(network[:mac_address])
# Ignore network if requested MAC address could not be found
next if found_idx.nil?
interface = interface_names[found_idx]
else
ifname_by_slot = interface_names_by_slot[network[:interface]-1]
# Don't overwrite if interface was already matched via MAC address
next if interfaces.include?(ifname_by_slot)
interface = ifname_by_slot
end
interfaces.add(interface)
network[:device] = interface
# Remove any previous vagrant configuration in this network
# interface's configuration files.
machine.communicate.sudo("touch #{network_scripts_dir}/ifcfg-#{interface}")
machine.communicate.sudo("sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' #{network_scripts_dir}/ifcfg-#{interface} > /tmp/vagrant-ifcfg-#{interface}")
machine.communicate.sudo("cat /tmp/vagrant-ifcfg-#{interface} > #{network_scripts_dir}/ifcfg-#{interface}")
machine.communicate.sudo("rm -f /tmp/vagrant-ifcfg-#{interface}")
# Render and upload the network entry file to a deterministic
# temporary location.
entry = TemplateRenderer.render("guests/fedora/network_#{network[:type]}",
options: network)
Tempfile.create("fedora-configure-networks") do |f|
f.write(entry)
f.fsync
f.close
machine.communicate.upload(f.path, "/tmp/vagrant-network-entry_#{interface}")
end
end
# Bring down all the interfaces we're reconfiguring. By bringing down
# each specifically, we avoid reconfiguring p7p (the NAT interface) so
# SSH never dies.
interfaces.each do |interface|
retryable(on: Vagrant::Errors::VagrantError, tries: 3, sleep: 2) do
machine.communicate.sudo(<<-SCRIPT, error_check: true)
cat /tmp/vagrant-network-entry_#{interface} >> #{network_scripts_dir}/ifcfg-#{interface}
if command -v nmcli &>/dev/null; then
if command -v systemctl &>/dev/null && systemctl -q is-enabled NetworkManager &>/dev/null; then
nmcli c reload #{interface}
elif command -v service &>/dev/null && service NetworkManager status &>/dev/null; then
nmcli c reload #{interface}
fi
fi
/sbin/ifdown #{interface}
/sbin/ifup #{interface}
rm -f /tmp/vagrant-network-entry_#{interface}
SCRIPT
end
end
end
end
end
end
end