Merge pull request #12575 from chrisroberts/fix-coreos

Fix coreos network configuration
This commit is contained in:
Chris Roberts 2021-11-05 10:39:26 -07:00 committed by GitHub
commit 66b39d45f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 355 additions and 130 deletions

View File

@ -4,7 +4,7 @@ module VagrantPlugins
module GuestAtomic
class Guest < Vagrant.plugin("2", :guest)
def detect?(machine)
machine.communicate.test("grep 'ostree=' /proc/cmdline")
machine.communicate.test("grep 'ostree=.*atomic' /proc/cmdline")
end
end
end

View File

@ -1,4 +1,5 @@
require "tempfile"
require "yaml"
require_relative "../../../../lib/vagrant/util/template_renderer"
@ -8,9 +9,73 @@ module VagrantPlugins
class ConfigureNetworks
extend Vagrant::Util::GuestInspection::Linux
NETWORK_MANAGER_CONN_DIR = "/etc/NetworkManager/system-connections".freeze
DEFAULT_ENVIRONMENT_IP = "127.0.0.1".freeze
def self.configure_networks(machine, networks)
comm = machine.communicate
return configure_networks_cloud_init(machine, networks) if comm.test("command -v cloud-init")
interfaces = machine.guest.capability(:network_interfaces)
nm_dev = {}
comm.execute("nmcli -t c show") do |type, data|
if type == :stdout
_, id, _, dev = data.strip.split(":")
nm_dev[dev] = id
end
end
comm.sudo("rm #{File.join(NETWORK_MANAGER_CONN_DIR, 'vagrant-*.conf')}",
error_check: false)
networks.each_with_index do |network, i|
network[:device] = interfaces[network[:interface]]
addr = IPAddr.new(network[:ip])
mask = addr.mask(network[:netmask])
if !network[:mac_address]
comm.execute("cat /sys/class/net/#{network[:device]}/address") do |type, data|
if type == :stdout
network[:mac_address] = data
end
end
end
f = Tempfile.new("vagrant-coreos-network")
{
connection: {
type: "ethernet",
id: network[:device],
"interface-name": network[:device]
},
ethernet: {
"mac-address": network[:mac_address]
},
ipv4: {
method: "manual",
addresses: "#{network[:ip]}/#{mask.prefix}",
gateway: network.fetch(:gateway, mask.to_range.first.succ),
},
}.each_pair do |section, content|
f.puts "[#{section}]"
content.each_pair do |key, value|
f.puts "#{key}=#{value}"
end
end
f.close
comm.sudo("nmcli d disconnect '#{network[:device]}'", error_check: false)
comm.sudo("nmcli c delete '#{nm_dev[network[:device]]}'", error_check: false)
dst = File.join("/var/tmp", "vagrant-#{network[:device]}.conf")
final = File.join(NETWORK_MANAGER_CONN_DIR, "vagrant-#{network[:device]}.conf")
comm.upload(f.path, dst)
comm.sudo("chown root:root '#{dst}'")
comm.sudo("chmod 0600 '#{dst}'")
comm.sudo("mv '#{dst}' '#{final}'")
comm.sudo("nmcli c load '#{final}'")
comm.sudo("nmcli d connect '#{network[:device]}'")
f.delete
end
end
def self.configure_networks_cloud_init(machine, networks)
cloud_config = {}
# Locate configured IP addresses to drop in /etc/environment
# for export. If no addresses found, fall back to default

View File

@ -12,12 +12,10 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks" do
let(:guest) { double("guest") }
let(:config) { double("config", vm: vm) }
let(:vm) { double("vm") }
# let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
let(:comm) { double("comm") }
let(:env) do
double("env", machine: machine, active_machines: [machine])
end
let(:interfaces) { ["eth0", "eth1", "lo"] }
before do
allow(machine).to receive(:communicate).and_return(comm)
@ -25,127 +23,219 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks" do
end
describe ".configure_networks" do
let(:network_1) do
{
interface: 0,
type: "dhcp",
}
end
let(:netconfig_1) do
[:public_interface, {}]
end
let(:network_2) do
{
interface: 1,
type: "static",
ip: "33.33.33.10",
netmask: "255.255.0.0",
gateway: "33.33.0.1",
}
end
let(:netconfig_2) do
[:public_network, {ip: "33.33.33.10", netmask: 16}]
end
let(:network_3) do
{
interface: 2,
type: "static",
ip: "192.168.120.22",
netmask: "255.255.255.0",
gateway: "192.168.120.1"
}
end
let(:netconfig_3) do
[:private_network, {ip: "192.168.120.22", netmask: 24}]
end
let(:networks) { [network_1, network_2, network_3] }
let(:network_configs) { [netconfig_1, netconfig_2, netconfig_3] }
let(:vm) { double("vm") }
let(:default_env_ip) { described_class.const_get(:DEFAULT_ENVIRONMENT_IP) }
before do
allow(guest).to receive(:capability).with(:network_interfaces).
and_return(interfaces)
allow(vm).to receive(:networks).and_return(network_configs)
allow(comm).to receive(:upload)
allow(comm).to receive(:sudo)
end
it "should upload network configuration file" do
expect(comm).to receive(:upload)
described_class.configure_networks(machine, networks)
end
it "should configure public ipv4 address" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{netconfig_2.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
it "should configure the private ipv4 address" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{netconfig_3.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
it "should configure network interfaces" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
interfaces.each { |i| expect(content).to include("Name=#{i}") }
end
described_class.configure_networks(machine, networks)
end
it "should configure DHCP interface" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("DHCP=yes")
end
described_class.configure_networks(machine, networks)
end
it "should configure static IP addresses" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
network_configs.map(&:last).find_all { |c| c[:ip] }.each { |c|
expect(content).to include("Address=#{c[:ip]}")
context "with network manager" do
let(:network_1) do
{
interface: 1,
type: "static",
ip: "10.0.0.3",
netmask: "255.255.255.0",
mac_address: "00:00:00:00:00:00",
gateway: "10.0.0.2",
}
end
described_class.configure_networks(machine, networks)
end
context "when no public network is defined" do
let(:networks) { [network_1, network_3] }
let(:network_configs) { [netconfig_1, netconfig_3] }
it "should set public IP to the default environment IP" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{default_env_ip}")
let(:network_2) do
{
interface: 2,
type: "static",
ip: "192.168.3.3",
netmask: "255.255.0.0",
}
end
let(:nm_list) do
[
"Wired connection 1:UUID_for_eth1:ethernet:eth1\n",
"Wired connection 2:UUID_for_eth2:ethernet:eth2\n"
]
end
let(:interfaces) { ["eth0", "eth1", "eth2"] }
let(:networks) do
[
network_1,
network_2,
]
end
let(:tempfile) do
double("tempfile",
close: nil,
delete: nil,
path: temp_path,
).tap do |f|
allow(f).to receive(:puts)
end
end
let(:temp_path) { "/dev/null" }
before do
allow(guest).to receive(:capability).
with(:network_interfaces).
and_return(interfaces)
allow(comm).to receive(:upload)
allow(comm).to receive(:sudo)
allow(comm).to receive(:execute)
allow(Tempfile).to receive(:new).and_return(tempfile)
expect(comm).to receive(:execute).
with("nmcli -t c show") { |&block|
nm_list.each { |line|
block.call(:stdout, line)
}
}
allow(comm).to receive(:test).
with("command -v cloud-init").
and_return(false)
end
it "should test for cloud-init" do
expect(comm).to receive(:test).
with("command -v cloud-init").
and_return(false)
described_class.configure_networks(machine, networks)
end
it "should set the private IP to the private network" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{netconfig_3.last[:ip]}")
end
it "should remove any previous vagrant configuration" do
expect(comm).to receive(:sudo).
with(/rm .*vagrant-.*conf/, error_check: false)
described_class.configure_networks(machine, networks)
end
it "should get MAC address from guest if not provided" do
expect(comm).to receive(:execute).
with(/cat .*eth2\/address/)
described_class.configure_networks(machine, networks)
end
it "should not get MAC address from guest when provided" do
expect(comm).not_to receive(:execute).
with(/cat .*eth1\/address/)
described_class.configure_networks(machine, networks)
end
it "should provide a default gateway when one is not provided" do
expect(tempfile).to receive(:puts).
with("gateway=192.168.0.1")
described_class.configure_networks(machine, networks)
end
it "should use gateway value when provided" do
expect(tempfile).to receive(:puts).
with("gateway=10.0.0.2")
described_class.configure_networks(machine, networks)
end
it "should disconnect device in network manager" do
expect(comm).to receive(:sudo).
with("nmcli d disconnect 'eth1'", error_check: false)
expect(comm).to receive(:sudo).
with("nmcli d disconnect 'eth2'", error_check: false)
described_class.configure_networks(machine, networks)
end
it "should delete connection from network manager" do
expect(comm).to receive(:sudo).
with("nmcli c delete 'UUID_for_eth1'", error_check: false)
expect(comm).to receive(:sudo).
with("nmcli c delete 'UUID_for_eth2'", error_check: false)
described_class.configure_networks(machine, networks)
end
it "should upload configuration files" do
expect(comm).to receive(:upload).twice
described_class.configure_networks(machine, networks)
end
it "should change file ownership to root" do
expect(comm).to receive(:sudo).
with(/chown root:root .*/)
described_class.configure_networks(machine, networks)
end
it "should modify file permissions to remove read access" do
expect(comm).to receive(:sudo).
with(/chmod 0600 .*/)
described_class.configure_networks(machine, networks)
end
it "should delete local temporary files" do
expect(tempfile).to receive(:delete)
described_class.configure_networks(machine, networks)
end
it "should load the configuration files into network manager" do
expect(comm).to receive(:sudo).
with(/nmcli c load .*conf/).twice
described_class.configure_networks(machine, networks)
end
it "should connect the devices in network manager" do
expect(comm).to receive(:sudo).
with("nmcli d connect 'eth1'")
expect(comm).to receive(:sudo).
with("nmcli d connect 'eth2'")
described_class.configure_networks(machine, networks)
end
end
context "when no private network is defined" do
let(:networks) { [network_1, network_2] }
let(:network_configs) { [netconfig_1, netconfig_2] }
context "with cloud-init" do
let(:interfaces) { ["eth0", "eth1", "lo"] }
let(:network_1) do
{
interface: 0,
type: "dhcp",
}
end
let(:netconfig_1) do
[:public_interface, {}]
end
let(:network_2) do
{
interface: 1,
type: "static",
ip: "33.33.33.10",
netmask: "255.255.0.0",
gateway: "33.33.0.1",
}
end
let(:netconfig_2) do
[:public_network, {ip: "33.33.33.10", netmask: 16}]
end
let(:network_3) do
{
interface: 2,
type: "static",
ip: "192.168.120.22",
netmask: "255.255.255.0",
gateway: "192.168.120.1"
}
end
let(:netconfig_3) do
[:private_network, {ip: "192.168.120.22", netmask: 24}]
end
let(:networks) { [network_1, network_2, network_3] }
let(:network_configs) { [netconfig_1, netconfig_2, netconfig_3] }
let(:vm) { double("vm") }
let(:default_env_ip) { described_class.const_get(:DEFAULT_ENVIRONMENT_IP) }
it "should set public IP to the public network" do
before do
allow(guest).to receive(:capability).with(:network_interfaces).
and_return(interfaces)
allow(vm).to receive(:networks).and_return(network_configs)
allow(comm).to receive(:upload)
allow(comm).to receive(:sudo)
allow(comm).to receive(:test).
with("command -v cloud-init").
and_return(true)
end
it "should upload network configuration file" do
expect(comm).to receive(:upload)
described_class.configure_networks(machine, networks)
end
it "should configure public ipv4 address" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{netconfig_2.last[:ip]}")
@ -153,35 +243,105 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks" do
described_class.configure_networks(machine, networks)
end
it "should set the private IP to the public IP" do
it "should configure the private ipv4 address" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{netconfig_2.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
end
context "when no public or private network is defined" do
let(:networks) { [network_1] }
let(:network_configs) { [netconfig_1] }
it "should set public IP to the default environment IP" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{default_env_ip}")
expect(content).to include("COREOS_PRIVATE_IPV4=#{netconfig_3.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
it "should set the private IP to the default environment IP" do
it "should configure network interfaces" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{default_env_ip}")
interfaces.each { |i| expect(content).to include("Name=#{i}") }
end
described_class.configure_networks(machine, networks)
end
it "should configure DHCP interface" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("DHCP=yes")
end
described_class.configure_networks(machine, networks)
end
it "should configure static IP addresses" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
network_configs.map(&:last).find_all { |c| c[:ip] }.each { |c|
expect(content).to include("Address=#{c[:ip]}")
}
end
described_class.configure_networks(machine, networks)
end
context "when no public network is defined" do
let(:networks) { [network_1, network_3] }
let(:network_configs) { [netconfig_1, netconfig_3] }
it "should set public IP to the default environment IP" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{default_env_ip}")
end
described_class.configure_networks(machine, networks)
end
it "should set the private IP to the private network" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{netconfig_3.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
end
context "when no private network is defined" do
let(:networks) { [network_1, network_2] }
let(:network_configs) { [netconfig_1, netconfig_2] }
it "should set public IP to the public network" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{netconfig_2.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
it "should set the private IP to the public IP" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{netconfig_2.last[:ip]}")
end
described_class.configure_networks(machine, networks)
end
end
context "when no public or private network is defined" do
let(:networks) { [network_1] }
let(:network_configs) { [netconfig_1] }
it "should set public IP to the default environment IP" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PUBLIC_IPV4=#{default_env_ip}")
end
described_class.configure_networks(machine, networks)
end
it "should set the private IP to the default environment IP" do
expect(comm).to receive(:upload) do |src, dst|
content = File.read(src)
expect(content).to include("COREOS_PRIVATE_IPV4=#{default_env_ip}")
end
described_class.configure_networks(machine, networks)
end
end
end
end
end