Better host loading scheme

This commit is contained in:
Mitchell Hashimoto 2011-12-11 23:22:44 -08:00
parent b8d40ea463
commit 668bab0ba9
12 changed files with 191 additions and 115 deletions

View File

@ -42,6 +42,10 @@ module Vagrant
def self.source_root def self.source_root
@source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
end end
def self.hosts
@hosts ||= Registry.new
end
end end
# # Default I18n to load the en locale # # Default I18n to load the en locale
@ -53,3 +57,10 @@ require 'vagrant/provisioners'
require 'vagrant/systems' require 'vagrant/systems'
require 'vagrant/version' require 'vagrant/version'
Vagrant::Plugin.load! Vagrant::Plugin.load!
# Register the built-in hosts
Vagrant.hosts.register(:arch) { Vagrant::Hosts::Arch }
Vagrant.hosts.register(:freebsd) { Vagrant::Hosts::FreeBSD }
Vagrant.hosts.register(:fedora) { Vagrant::Hosts::Fedora }
Vagrant.hosts.register(:linux) { Vagrant::Hosts::Linux }
Vagrant.hosts.register(:bsd) { Vagrant::Hosts::BSD }

View File

@ -179,7 +179,16 @@ module Vagrant
# #
# @return [Hosts::Base] # @return [Hosts::Base]
def host def host
@host ||= Hosts::Base.load(self, config.vagrant.host) return @host if defined?(@host)
# Attempt to figure out the host class. Note that the order
# matters here, so please don't touch. Specifically: The symbol
# check is done after the detect check because the symbol check
# will return nil, and we don't want to trigger a detect load.
host_klass = config.global.vagrant.host
host_klass = Hosts.detect(Vagrant.hosts) if host_klass.nil? || host_klass == :detect
host_klass = Vagrant.hosts.get(host_klass) if host_klass.is_a?(Symbol)
@host ||= host_klass.new(@ui)
end end
# Action runner for executing actions in the context of this environment. # Action runner for executing actions in the context of this environment.

View File

@ -6,5 +6,21 @@ module Vagrant
autoload :FreeBSD,'vagrant/hosts/freebsd' autoload :FreeBSD,'vagrant/hosts/freebsd'
autoload :Fedora, 'vagrant/hosts/fedora' autoload :Fedora, 'vagrant/hosts/fedora'
autoload :Linux, 'vagrant/hosts/linux' autoload :Linux, 'vagrant/hosts/linux'
# This method detects the correct host based on the `match?` methods
# implemented in the registered hosts.
def self.detect(registry)
# Sort the hosts by their precedence
host_klasses = registry.to_hash.values
host_klasses = host_klasses.sort_by { |a| a.precedence }.reverse
# Test for matches and return the host class that matches
host_klasses.each do |klass|
return klass if klass.match?
end
# No matches found...
return nil
end
end end
end end

View File

@ -1,13 +1,17 @@
module Vagrant module Vagrant
module Hosts module Hosts
class Arch < Linux class Arch < Linux
def nfs_export(ip, folders) def self.match?
File.exist?("/etc/rc.conf") && File.exist?("/etc/pacman.conf")
end
def nfs_export(id, ip, folders)
output = TemplateRenderer.render('nfs/exports_linux', output = TemplateRenderer.render('nfs/exports_linux',
:uuid => env.vm.uuid, :uuid => id,
:ip => ip, :ip => ip,
:folders => folders) :folders => folders)
env.ui.info I18n.t("vagrant.hosts.arch.nfs_export.prepare") @ui.info I18n.t("vagrant.hosts.arch.nfs_export.prepare")
sleep 0.5 sleep 0.5
output.split("\n").each do |line| output.split("\n").each do |line|

View File

@ -1,54 +1,39 @@
module Vagrant module Vagrant
module Hosts module Hosts
# Base class representing a host machine. These classes # Interface for classes which house behavior that is specific
# define methods which may have host-specific (Mac OS X, Windows, # to the host OS that is running Vagrant.
# Linux, etc) behavior. The class is automatically determined by #
# default but may be explicitly set via `config.vagrant.host`. # By default, Vagrant will attempt to choose the best option
# for your machine, but the host may also be explicitly set
# via the `config.vagrant.host` parameter.
class Base class Base
# The {Environment} which this host belongs to. # This returns true/false depending on if the current running system
attr_reader :env # matches the host class.
# Loads the proper host for the given value. If the value is nil
# or is the symbol `:detect`, then the host class will be detected
# using the `RUBY_PLATFORM` constant.
# #
# @param [Environment] env # @return [Boolean]
# @param [String] klass def self.match?
# @return [Base]
def self.load(env, klass)
klass = detect if klass.nil? || klass == :detect
return nil if !klass
return klass.new(env)
end
# Detects the proper host class for current platform and returns
# the class.
#
# @return [Class]
def self.detect
[BSD, Linux].each do |type|
result = type.distro_dispatch
return result if result
end
nil
rescue Exception
nil nil
end end
# This must be implemented by subclasses to dispatch to the proper # The precedence of the host when checking for matches. This is to
# distro-specific class for the host. If this returns nil then it is # allow certain host such as generic OS's ("Linux", "BSD", etc.)
# an invalid host class. # to be specified last.
def self.distro_dispatch #
nil # The hosts with the higher numbers will be checked first.
#
# If you're implementing a basic host, you can probably ignore this.
def self.precedence
5
end end
# Initialzes a new host. This method shouldn't be called directly, # Initializes a new host class.
# typically, since it will be called by {Environment#load!}.
# #
# @param [Environment] env # The only required parameter is a UI object so that the host
def initialize(env) # objects have some way to communicate with the outside world.
@env = env #
# @param [UI] ui UI for the hosts to output to.
def initialize(ui)
@ui = ui
end end
# Returns true of false denoting whether or not this host supports # Returns true of false denoting whether or not this host supports
@ -60,16 +45,19 @@ module Vagrant
false false
end end
# Exports the given hash of folders via NFS. This method will raise # Exports the given hash of folders via NFS.
# an {Vagrant::Action::ActionException} if anything goes wrong.
# #
# @param [String] id A unique ID that is guaranteed to be unique to
# match these sets of folders.
# @param [String] ip IP of the guest machine. # @param [String] ip IP of the guest machine.
# @param [Hash] folders Shared folders to sync. # @param [Hash] folders Shared folders to sync.
def nfs_export(ip, folders) def nfs_export(id, ip, folders)
end end
# Cleans up the exports for the current VM. # Cleans up the exports for the given ID.
def nfs_cleanup #
# @param [String] id A unique ID to identify the folder set to cleanup.
def nfs_cleanup(id)
end end
end end
end end

View File

@ -7,9 +7,20 @@ module Vagrant
include Util include Util
include Util::Retryable include Util::Retryable
def self.distro_dispatch def self.match?
return FreeBSD if Util::Platform.freebsd? Util::Platform.darwin? || Util::Platform.bsd?
return self if Util::Platform.darwin? || Util::Platform.bsd? end
def self.precedence
# Set a lower precedence because this is a generic OS. We
# want specific distros to match first.
2
end
def initialize(*args)
super
@nfs_restart_command = "sudo nfsd restart"
end end
def nfs? def nfs?
@ -18,15 +29,15 @@ module Vagrant
end end
end end
def nfs_export(ip, folders) def nfs_export(id, ip, folders)
output = TemplateRenderer.render('nfs/exports', output = TemplateRenderer.render('nfs/exports',
:uuid => env.vm.uuid, :uuid => id,
:ip => ip, :ip => ip,
:folders => folders) :folders => folders)
# The sleep ensures that the output is truly flushed before any `sudo` # The sleep ensures that the output is truly flushed before any `sudo`
# commands are issued. # commands are issued.
env.ui.info I18n.t("vagrant.hosts.bsd.nfs_export.prepare") @ui.info I18n.t("vagrant.hosts.bsd.nfs_export.prepare")
sleep 0.5 sleep 0.5
output.split("\n").each do |line| output.split("\n").each do |line|
@ -38,19 +49,20 @@ module Vagrant
# We run restart here instead of "update" just in case nfsd # We run restart here instead of "update" just in case nfsd
# is not starting # is not starting
system("sudo nfsd restart") system(@nfs_restart_command)
end end
def nfs_cleanup def nfs_cleanup(id)
return if !File.exist?("/etc/exports") return if !File.exist?("/etc/exports")
retryable(:tries => 10, :on => TypeError) do retryable(:tries => 10, :on => TypeError) do
system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{env.vm.uuid}' > /dev/null 2>&1") system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{id}' > /dev/null 2>&1")
if $?.to_i == 0 if $?.to_i == 0
# Use sed to just strip out the block of code which was inserted # Use sed to just strip out the block of code which was inserted
# by Vagrant # by Vagrant, and restart NFS.
system("sudo sed -e '/^# VAGRANT-BEGIN: #{env.vm.uuid}/,/^# VAGRANT-END: #{env.vm.uuid}/ d' -ibak /etc/exports") system("sudo sed -e '/^# VAGRANT-BEGIN: #{id}/,/^# VAGRANT-END: #{id}/ d' -ibak /etc/exports")
system(@nfs_restart_command)
end end
end end
end end

View File

@ -1,6 +1,20 @@
require 'pathname'
module Vagrant module Vagrant
module Hosts module Hosts
class Fedora < Linux class Fedora < Linux
def self.match?
release_file = Pathname.new("/etc/redhat-release")
if release_file.exist?
release_file.open("r") do |f|
return true if f.gets =~ /^Fedora/
end
end
false
end
def initialize(*args) def initialize(*args)
super super

View File

@ -7,45 +7,15 @@ module Vagrant
include Util include Util
include Util::Retryable include Util::Retryable
def nfs_export(ip, folders) def self.match?
output = TemplateRenderer.render('nfs/exports', Util::Platform.freebsd?
:uuid => env.vm.uuid,
:ip => ip,
:folders => folders)
# The sleep ensures that the output is truly flushed before any `sudo`
# commands are issued.
env.ui.info I18n.t("vagrant.hosts.bsd.nfs_export.prepare")
sleep 0.5
output.split("\n").each do |line|
# This should only ask for administrative permission once, even
# though its executed in multiple subshells.
line = line.gsub('"', '\"')
system(%Q[sudo su root -c "echo '#{line}' >> /etc/exports"])
end
# We run restart here instead of "update" just in case nfsd
# is not starting
system("sudo /etc/rc.d/mountd onereload")
end end
def initialize(*args)
super
@nfs_restart_command = "sudo /etc/rc.d/mountd onereload"
end
end end
def nfs_cleanup
return if !File.exist?("/etc/exports")
retryable(:tries => 10, :on => TypeError) do
system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{env.vm.uuid}' > /dev/null 2>&1")
if $?.to_i == 0
# Use sed to just strip out the block of code which was inserted
# by Vagrant
system("sudo sed -e '/^# VAGRANT-BEGIN: #{env.vm.uuid}/,/^# VAGRANT-END: #{env.vm.uuid}/ d' -ibak /etc/exports")
end
system("sudo /etc/rc.d/mountd onereload")
end
end
end end
end end

View File

@ -7,18 +7,14 @@ module Vagrant
include Util include Util
include Util::Retryable include Util::Retryable
def self.distro_dispatch def self.match?
return nil if !Util::Platform.linux? Util::Platform.linux?
return Arch if File.exist?("/etc/rc.conf") && File.exist?("/etc/pacman.conf") end
if File.exist?("/etc/redhat-release") def self.precedence
# Check if we have a known redhat release # Set a lower precedence because this is a generic OS. We
File.open("/etc/redhat-release") do |f| # want specific distros to match first.
return Fedora if f.gets =~ /^Fedora/ 2
end
end
return self
end end
def initialize(*args) def initialize(*args)
@ -34,13 +30,13 @@ module Vagrant
end end
end end
def nfs_export(ip, folders) def nfs_export(id, ip, folders)
output = TemplateRenderer.render('nfs/exports_linux', output = TemplateRenderer.render('nfs/exports_linux',
:uuid => env.vm.uuid, :uuid => id,
:ip => ip, :ip => ip,
:folders => folders) :folders => folders)
env.ui.info I18n.t("vagrant.hosts.linux.nfs_export.prepare") @ui.info I18n.t("vagrant.hosts.linux.nfs_export.prepare")
sleep 0.5 sleep 0.5
output.split("\n").each do |line| output.split("\n").each do |line|
@ -54,14 +50,14 @@ module Vagrant
system("sudo #{@nfs_server_binary} restart") system("sudo #{@nfs_server_binary} restart")
end end
def nfs_cleanup def nfs_cleanup(id)
return if !File.exist?("/etc/exports") return if !File.exist?("/etc/exports")
system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{env.vm.uuid}' > /dev/null 2>&1") system("cat /etc/exports | grep 'VAGRANT-BEGIN: #{id}' > /dev/null 2>&1")
if $?.to_i == 0 if $?.to_i == 0
# Use sed to just strip out the block of code which was inserted # Use sed to just strip out the block of code which was inserted
# by Vagrant # by Vagrant
system("sudo sed -e '/^# VAGRANT-BEGIN: #{env.vm.uuid}/,/^# VAGRANT-END: #{env.vm.uuid}/ d' -ibak /etc/exports") system("sudo sed -e '/^# VAGRANT-BEGIN: #{id}/,/^# VAGRANT-END: #{id}/ d' -ibak /etc/exports")
end end
end end
end end

View File

@ -35,5 +35,15 @@ module Vagrant
yield key, get(key) yield key, get(key)
end end
end end
# Converts this registry to a hash
def to_hash
result = {}
self.each do |key, value|
result[key] = value
end
result
end
end end
end end

View File

@ -0,0 +1,36 @@
require File.expand_path("../../base", __FILE__)
describe Vagrant::Hosts do
let(:registry) { Vagrant::Registry.new }
it "detects the host that matches true" do
foo_klass = Class.new(Vagrant::Hosts::Base) do
def self.match?; false; end
end
bar_klass = Class.new(Vagrant::Hosts::Base) do
def self.match?; true; end
end
registry.register(:foo, foo_klass)
registry.register(:bar, bar_klass)
described_class.detect(registry).should == bar_klass
end
it "detects the host that matches true with the highest precedence first" do
foo_klass = Class.new(Vagrant::Hosts::Base) do
def self.match?; true; end
end
bar_klass = Class.new(Vagrant::Hosts::Base) do
def self.match?; true; end
def self.precedence; 9; end
end
registry.register(:foo, foo_klass)
registry.register(:bar, bar_klass)
described_class.detect(registry).should == bar_klass
end
end

View File

@ -43,4 +43,14 @@ describe Vagrant::Registry do
keys.sort.should == ["bar", "foo"] keys.sort.should == ["bar", "foo"]
values.sort.should == ["barvalue", "foovalue"] values.sort.should == ["barvalue", "foovalue"]
end end
it "should be able to convert to a hash" do
instance.register("foo", "foovalue")
instance.register("bar", "barvalue")
result = instance.to_hash
result.should be_a(Hash)
result["foo"].should == "foovalue"
result["bar"].should == "barvalue"
end
end end