Merge pull request #10752 from chrisroberts/f-coreos-guest-caps
Update guest capabilities for coreos
This commit is contained in:
commit
8e9578cf5d
|
@ -23,6 +23,25 @@ module Vagrant
|
||||||
comm.test("systemctl -q is-active systemd-networkd.service", sudo: true)
|
comm.test("systemctl -q is-active systemd-networkd.service", sudo: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Check if a unit file with the given name is defined. Name can
|
||||||
|
# be a pattern or explicit name.
|
||||||
|
#
|
||||||
|
# @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator
|
||||||
|
# @param [String] name Name or pattern to search
|
||||||
|
# @return [Boolean]
|
||||||
|
def systemd_unit_file?(comm, name)
|
||||||
|
comm.test("systemctl -q list-unit-files | grep \"#{name}\"")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if a unit is currently active within systemd
|
||||||
|
#
|
||||||
|
# @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator
|
||||||
|
# @param [String] name Name or pattern to search
|
||||||
|
# @return [Boolean]
|
||||||
|
def systemd_unit?(comm, name)
|
||||||
|
comm.test("systemctl -q list-units | grep \"#{name}\"")
|
||||||
|
end
|
||||||
|
|
||||||
# Check if given service is controlled by systemd
|
# Check if given service is controlled by systemd
|
||||||
#
|
#
|
||||||
# @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator
|
# @param [Vagrant::Plugin::V2::Communicator] comm Guest communicator
|
||||||
|
|
|
@ -1,18 +1,35 @@
|
||||||
|
require "tempfile"
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
module VagrantPlugins
|
module VagrantPlugins
|
||||||
module GuestCoreOS
|
module GuestCoreOS
|
||||||
module Cap
|
module Cap
|
||||||
class ChangeHostName
|
class ChangeHostName
|
||||||
|
extend Vagrant::Util::GuestInspection::Linux
|
||||||
|
|
||||||
def self.change_host_name(machine, name)
|
def self.change_host_name(machine, name)
|
||||||
comm = machine.communicate
|
comm = machine.communicate
|
||||||
|
|
||||||
if !comm.test("hostname -f | grep '^#{name}$'", sudo: false)
|
if systemd_unit_file?(comm, "system-cloudinit*")
|
||||||
basename = name.split(".", 2)[0]
|
file = Tempfile.new("vagrant-coreos-hostname")
|
||||||
comm.sudo("hostname '#{basename}'")
|
file.puts("#cloud-config\n")
|
||||||
|
file.puts({"hostname" => name}.to_yaml)
|
||||||
|
file.close
|
||||||
|
|
||||||
# Note that when working with CoreOS, we explicitly do not add the
|
dst = "/var/tmp/hostname.yml"
|
||||||
# entry to /etc/hosts because this file does not exist on CoreOS.
|
svc_path = dst.tr("/", "-")[1..-1]
|
||||||
# We could create it, but the recommended approach on CoreOS is to
|
comm.upload(file.path, dst)
|
||||||
# use Fleet to manage /etc/hosts files.
|
comm.sudo("systemctl start system-cloudinit@#{svc_path}.service")
|
||||||
|
else
|
||||||
|
if !comm.test("hostname -f | grep '^#{name}$'", sudo: false)
|
||||||
|
basename = name.split(".", 2)[0]
|
||||||
|
comm.sudo("hostname '#{basename}'")
|
||||||
|
|
||||||
|
# Note that when working with CoreOS, we explicitly do not add the
|
||||||
|
# entry to /etc/hosts because this file does not exist on CoreOS.
|
||||||
|
# We could create it, but the recommended approach on CoreOS is to
|
||||||
|
# use Fleet to manage /etc/hosts files.
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,79 +6,63 @@ module VagrantPlugins
|
||||||
module GuestCoreOS
|
module GuestCoreOS
|
||||||
module Cap
|
module Cap
|
||||||
class ConfigureNetworks
|
class ConfigureNetworks
|
||||||
include Vagrant::Util
|
extend Vagrant::Util::GuestInspection::Linux
|
||||||
|
|
||||||
|
DEFAULT_ENVIRONMENT_IP = "127.0.0.1".freeze
|
||||||
|
|
||||||
def self.configure_networks(machine, networks)
|
def self.configure_networks(machine, networks)
|
||||||
machine.communicate.tap do |comm|
|
cloud_config = {}
|
||||||
# Read network interface names
|
# Locate configured IP addresses to drop in /etc/environment
|
||||||
interfaces = []
|
# for export. If no addresses found, fall back to default
|
||||||
comm.sudo("ifconfig | grep -E '(e[n,t][h,s,p][[:digit:]]([a-z][[:digit:]])?)' | cut -f1 -d:") do |_, result|
|
public_ip = catch(:public_ip) do
|
||||||
interfaces = result.split("\n")
|
machine.config.vm.networks.each do |type, opts|
|
||||||
|
next if type != :public_network
|
||||||
|
throw(:public_ip, opts[:ip]) if opts[:ip]
|
||||||
end
|
end
|
||||||
|
DEFAULT_ENVIRONMENT_IP
|
||||||
|
end
|
||||||
|
private_ip = catch(:private_ip) do
|
||||||
|
machine.config.vm.networks.each do |type, opts|
|
||||||
|
next if type != :private_network
|
||||||
|
throw(:private_ip, opts[:ip]) if opts[:ip]
|
||||||
|
end
|
||||||
|
public_ip
|
||||||
|
end
|
||||||
|
cloud_config["write_files"] = [
|
||||||
|
{"path" => "/etc/environment",
|
||||||
|
"content" => "COREOS_PUBLIC_IPV4=#{public_ip}\nCOREOS_PRIVATE_IPV4=#{private_ip}"}
|
||||||
|
]
|
||||||
|
|
||||||
primary_machine_config = machine.env.active_machines.first
|
# Generate configuration for any static network interfaces
|
||||||
primary_machine = machine.env.machine(*primary_machine_config, true)
|
# which have been defined
|
||||||
|
interfaces = machine.guest.capability(:network_interfaces)
|
||||||
primary_machine_ip = get_ip(primary_machine)
|
units = networks.map do |network|
|
||||||
current_ip = get_ip(machine)
|
iface = network[:interface].to_i
|
||||||
if current_ip == primary_machine_ip
|
unit_name = "50-vagrant#{iface}.network"
|
||||||
entry = TemplateRenderer.render("guests/coreos/etcd.service", options: {
|
device = interfaces[iface]
|
||||||
my_ip: current_ip,
|
if network[:type].to_s == "dhcp"
|
||||||
})
|
network_content = "DHCP=yes"
|
||||||
else
|
else
|
||||||
connection_string = "#{primary_machine_ip}:7001"
|
prefix = IPAddr.new("255.255.255.255/#{network[:netmask]}").to_i.to_s(2).count("1")
|
||||||
entry = TemplateRenderer.render("guests/coreos/etcd.service", options: {
|
address = "#{network[:ip]}/#{prefix}"
|
||||||
connection_string: connection_string,
|
network_content = "Address=#{address}"
|
||||||
my_ip: current_ip,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
{"name" => unit_name,
|
||||||
Tempfile.open("vagrant-coreos-configure-networks") do |f|
|
"runtime" => "no",
|
||||||
f.binmode
|
"content" => "[Match]\nName=#{device}\n[Network]\n#{network_content}"}
|
||||||
f.write(entry)
|
|
||||||
f.fsync
|
|
||||||
f.close
|
|
||||||
comm.upload(f.path, "/tmp/etcd-cluster.service")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Build a list of commands
|
|
||||||
commands = []
|
|
||||||
|
|
||||||
# Stop default systemd
|
|
||||||
commands << "systemctl stop etcd"
|
|
||||||
|
|
||||||
# Configure interfaces
|
|
||||||
# FIXME: fix matching of interfaces with IP addresses
|
|
||||||
networks.each do |network|
|
|
||||||
iface = interfaces[network[:interface].to_i]
|
|
||||||
commands << "ifconfig #{iface} #{network[:ip]} netmask #{network[:netmask]}".squeeze(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
commands << <<-EOH.gsub(/^ {14}/, '')
|
|
||||||
mv /tmp/etcd-cluster.service /media/state/units/
|
|
||||||
systemctl restart local-enable.service
|
|
||||||
|
|
||||||
# Restart default etcd
|
|
||||||
systemctl start etcd
|
|
||||||
EOH
|
|
||||||
|
|
||||||
# Run all network configuration commands in one communicator session.
|
|
||||||
comm.sudo(commands.join("\n"))
|
|
||||||
end
|
end
|
||||||
end
|
cloud_config["coreos"] = {"units" => units.compact}
|
||||||
|
|
||||||
private
|
# Upload configuration and apply
|
||||||
|
file = Tempfile.new("vagrant-coreos-networks")
|
||||||
|
file.puts("#cloud-config\n")
|
||||||
|
file.puts(cloud_config.to_yaml)
|
||||||
|
file.close
|
||||||
|
|
||||||
def self.get_ip(machine)
|
dst = "/var/tmp/networks.yml"
|
||||||
ip = nil
|
svc_path = dst.tr("/", "-")[1..-1]
|
||||||
machine.config.vm.networks.each do |type, opts|
|
machine.communicate.upload(file.path, dst)
|
||||||
if type == :private_network && opts[:ip]
|
machine.communicate.sudo("systemctl start system-cloudinit@#{svc_path}.service")
|
||||||
ip = opts[:ip]
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ip
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,17 +21,47 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ChangeHostName" do
|
||||||
|
|
||||||
describe ".change_host_name" do
|
describe ".change_host_name" do
|
||||||
let(:name) { "banana-rama.example.com" }
|
let(:name) { "banana-rama.example.com" }
|
||||||
|
let(:has_cloudinit) { false }
|
||||||
|
|
||||||
it "sets the hostname" do
|
before do
|
||||||
comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 1)
|
allow(described_class).to receive(:systemd_unit_file?).
|
||||||
comm.expect_command("hostname 'banana-rama'")
|
with(anything, /cloudinit/).and_return(has_cloudinit)
|
||||||
described_class.change_host_name(machine, name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not change the hostname if already set" do
|
context "with systemd cloud-init" do
|
||||||
comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 0)
|
let(:has_cloudinit) { true }
|
||||||
described_class.change_host_name(machine, name)
|
|
||||||
expect(comm.received_commands.size).to eq(1)
|
it "should upload cloudinit configuration file" do
|
||||||
|
expect(comm).to receive(:upload)
|
||||||
|
described_class.change_host_name(machine, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should set hostname in configuration file" do
|
||||||
|
expect(comm).to receive(:upload) do |src, dst|
|
||||||
|
contents = File.read(src)
|
||||||
|
expect(contents).to include(name)
|
||||||
|
end
|
||||||
|
described_class.change_host_name(machine, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should start the cloudinit service" do
|
||||||
|
expect(comm).to receive(:sudo).with(/systemctl start system-cloudinit/)
|
||||||
|
described_class.change_host_name(machine, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without systemd cloud-init" do
|
||||||
|
it "sets the hostname" do
|
||||||
|
comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 1)
|
||||||
|
comm.expect_command("hostname 'banana-rama'")
|
||||||
|
described_class.change_host_name(machine, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not change the hostname if already set" do
|
||||||
|
comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 0)
|
||||||
|
described_class.change_host_name(machine, name)
|
||||||
|
expect(comm.received_commands.size).to eq(1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,25 +8,20 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks" do
|
||||||
.get(:configure_networks)
|
.get(:configure_networks)
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:machine) { double("machine") }
|
let(:machine) { double("machine", config: config, guest: guest) }
|
||||||
let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
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
|
let(:env) do
|
||||||
double("env", machine: machine, active_machines: [machine])
|
double("env", machine: machine, active_machines: [machine])
|
||||||
end
|
end
|
||||||
|
let(:interfaces) { ["eth0", "eth1", "lo"] }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(machine).to receive(:communicate).and_return(comm)
|
allow(machine).to receive(:communicate).and_return(comm)
|
||||||
allow(machine).to receive(:env).and_return(env)
|
allow(machine).to receive(:env).and_return(env)
|
||||||
|
|
||||||
allow(described_class).to receive(:get_ip).and_return("1.2.3.4")
|
|
||||||
|
|
||||||
comm.stub_command("ifconfig | grep -E '(e[n,t][h,s,p][[:digit:]]([a-z][[:digit:]])?)' | cut -f1 -d:",
|
|
||||||
stdout: "eth1\neth2")
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
comm.verify_expectations!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".configure_networks" do
|
describe ".configure_networks" do
|
||||||
|
@ -36,7 +31,9 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks" do
|
||||||
type: "dhcp",
|
type: "dhcp",
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
let(:netconfig_1) do
|
||||||
|
[:public_interface, {}]
|
||||||
|
end
|
||||||
let(:network_2) do
|
let(:network_2) do
|
||||||
{
|
{
|
||||||
interface: 1,
|
interface: 1,
|
||||||
|
@ -46,14 +43,145 @@ describe "VagrantPlugins::GuestCoreOS::Cap::ConfigureNetworks" do
|
||||||
gateway: "33.33.0.1",
|
gateway: "33.33.0.1",
|
||||||
}
|
}
|
||||||
end
|
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 "creates and starts the networks" do
|
before do
|
||||||
described_class.configure_networks(machine, [network_1, network_2])
|
allow(guest).to receive(:capability).with(:network_interfaces).
|
||||||
expect(comm.received_commands[1]).to match(/systemctl stop etcd/)
|
and_return(interfaces)
|
||||||
expect(comm.received_commands[1]).to match(/ifconfig eth1 netmask/)
|
allow(vm).to receive(:networks).and_return(network_configs)
|
||||||
expect(comm.received_commands[1]).to match(/ifconfig eth2 33.33.33.10 netmask 255.255.0.0/)
|
allow(comm).to receive(:upload)
|
||||||
expect(comm.received_commands[1]).to match(/systemctl restart local-enable.service/)
|
allow(comm).to receive(:sudo)
|
||||||
expect(comm.received_commands[1]).to match(/systemctl start etcd/)
|
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]}")
|
||||||
|
}
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue