From 9debf5abe974a0289f93a30ee8812c8e9cba5a7b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 18 Dec 2011 18:14:20 -0800 Subject: [PATCH] Attach a driver to each VM. Use that to detect VirtualBox. --- lib/vagrant.rb | 2 + lib/vagrant/driver.rb | 5 ++ lib/vagrant/driver/virtualbox.rb | 36 +++++++++ lib/vagrant/environment.rb | 25 ------ lib/vagrant/util/subprocess.rb | 128 +++++++++++++++++++++++++++++++ lib/vagrant/vm.rb | 2 + vagrant.gemspec | 4 +- 7 files changed, 175 insertions(+), 27 deletions(-) create mode 100644 lib/vagrant/driver.rb create mode 100644 lib/vagrant/driver/virtualbox.rb create mode 100644 lib/vagrant/util/subprocess.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 166c0dfb3..89126adc7 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -10,6 +10,7 @@ if ENV["VAGRANT_LOG"] end require 'pathname' +require 'childprocess' require 'json' require 'i18n' require 'virtualbox' @@ -27,6 +28,7 @@ module Vagrant autoload :Config, 'vagrant/config' autoload :DataStore, 'vagrant/data_store' autoload :Downloaders, 'vagrant/downloaders' + autoload :Driver, 'vagrant/driver' autoload :Environment, 'vagrant/environment' autoload :Errors, 'vagrant/errors' autoload :Guest, 'vagrant/guest' diff --git a/lib/vagrant/driver.rb b/lib/vagrant/driver.rb new file mode 100644 index 000000000..8d74b793f --- /dev/null +++ b/lib/vagrant/driver.rb @@ -0,0 +1,5 @@ +module Vagrant + module Driver + autoload :VirtualBox, 'vagrant/driver/virtualbox' + end +end diff --git a/lib/vagrant/driver/virtualbox.rb b/lib/vagrant/driver/virtualbox.rb new file mode 100644 index 000000000..04e90903c --- /dev/null +++ b/lib/vagrant/driver/virtualbox.rb @@ -0,0 +1,36 @@ +require 'vagrant/util/subprocess' + +module Vagrant + module Driver + # This class contains the logic to drive VirtualBox. + class VirtualBox + # Include this so we can use `Subprocess` more easily. + include Vagrant::Util + + # The version of virtualbox that is running. + attr_reader :version + + def initialize + # Read and assign the version of VirtualBox we know which + # specific driver to instantiate. + begin + @version = read_version + rescue Subprocess::ProcessFailedToStart + # This means that VirtualBox was not found, so we raise this + # error here. + raise Errors::VirtualBoxNotDetected + end + end + + protected + + # This returns the version of VirtualBox that is running. + # + # @return [String] + def read_version + result = Subprocess.execute("VBoxManage", "--version") + result.stdout.split("r")[0] + end + end + end +end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 56553430c..7a151502f 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -36,27 +36,6 @@ module Vagrant # The path to the default private key attr_reader :default_private_key_path - #--------------------------------------------------------------- - # Class Methods - #--------------------------------------------------------------- - class << self - # Verifies that VirtualBox is installed and that the version of - # VirtualBox installed is high enough. - def check_virtualbox! - version = VirtualBox.version - raise Errors::VirtualBoxNotDetected if version.nil? - raise Errors::VirtualBoxInvalidVersion, :version => version.to_s if version.to_f < 4.1 || version.to_f >= 4.2 - rescue Errors::VirtualBoxNotDetected - # On 64-bit Windows, show a special error. This error is a subclass - # of VirtualBoxNotDetected, so libraries which use Vagrant can just - # rescue VirtualBoxNotDetected. - raise Errors::VirtualBoxNotDetected_Win64 if Util::Platform.windows? && Util::Platform.bit64? - - # Otherwise, reraise the old error - raise - end - end - # Initializes a new environment with the given options. The options # is a hash where the main available key is `cwd`, which defines where # the environment represents. There are other options available but @@ -331,10 +310,6 @@ module Vagrant def load! if !loaded? @loaded = true - - @logger.info("Environment not loaded. Checking virtual box version...") - self.class.check_virtualbox! - @logger.info("Loading configuration...") load_config! end diff --git a/lib/vagrant/util/subprocess.rb b/lib/vagrant/util/subprocess.rb new file mode 100644 index 000000000..1fb1c4e0c --- /dev/null +++ b/lib/vagrant/util/subprocess.rb @@ -0,0 +1,128 @@ +require 'childprocess' +require 'log4r' + +module Vagrant + module Util + # Execute a command in a subprocess, gathering the results and + # exit status. + # + # This class also allows you to read the data as it is outputted + # from the subprocess in real time, by simply passing a block to + # the execute method. + class Subprocess + # Convenience method for executing a method. + def self.execute(*command, &block) + new(*command).execute(&block) + end + + def initialize(*command) + @command = command + @logger = Log4r::Logger.new("vagrant::util::subprocess") + end + + def execute + # Build the ChildProcess + @logger.debug("Starting process: #{@command.inspect}") + process = ChildProcess.build(*@command) + + # Create the pipes so we can read the output in real time as + # we execute the command. + stdout, stdout_writer = IO.pipe + stderr, stderr_writer = IO.pipe + process.io.stdout = stdout_writer + process.io.stderr = stderr_writer + process.duplex = true + + # Start the process + begin + process.start + rescue Exception => e + if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" + if e.is_a?(NativeException) + # This usually means that the process failed to start, so we + # raise that error. + raise ProcessFailedToStart + end + end + + raise + end + + # Make sure the stdin does not buffer + process.io.stdin.sync = true + + # Close the writer pipes, since we're just reading + stdout_writer.close + stderr_writer.close + + # Create a dictionary to store all the output we see. + io_data = { stdout => "", stderr => "" } + + @logger.debug("Selecting on IO") + while true + results = IO.select([stdout, stderr], [process.io.stdin], nil, 5) + readers, writers = results + + # Check the readers to see if they're ready + if !readers.empty? + begin + readers.each do |r| + data = r.read_nonblock(1024) + io_name = r == stdout ? :stdout : :stderr + @logger.debug(data) + + if io_name == :stderr && io_data[r] == "" && data =~ /Errno::ENOENT/ + # This is how we detect that a process failed to start on + # Linux. Hacky, but it works fairly well. + raise ProcessFailedToStart + end + + io_data[r] += data + yield io_name, data if block_given? + end + rescue IO::WaitReadable + # This just means the IO wasn't actually ready and we + # should wait some more. No problem! Just pass on through... + rescue EOFError + # Process exited, most likely. We're done here. + break + end + end + + # Break out if the process exited. We have to do this before + # attempting to write to stdin otherwise we'll get a broken pipe + # error. + break if process.exited? + + # Check the writers to see if they're ready, and notify any listeners + if !writers.empty? + yield :stdin, process.io.stdin if block_given? + end + end + + # Wait for the process to end. + process.poll_for_exit(32000) + @logger.debug("Exit status: #{process.exit_code}") + + # Return an exit status container + return Result.new(process.exit_code, io_data[stdout], io_data[stderr]) + end + + # An error which occurs when a process fails to start. + class ProcessFailedToStart < StandardError; end + + # Container class to store the results of executing a subprocess. + class Result + attr_reader :exit_code + attr_reader :stdout + attr_reader :stderr + + def initialize(exit_code, stdout, stderr) + @exit_code = exit_code + @stdout = stdout + @stderr = stderr + end + end + end + end +end diff --git a/lib/vagrant/vm.rb b/lib/vagrant/vm.rb index 27d9177c0..74a1b30d9 100644 --- a/lib/vagrant/vm.rb +++ b/lib/vagrant/vm.rb @@ -9,6 +9,7 @@ module Vagrant attr_reader :vm attr_reader :box attr_reader :config + attr_reader :driver def initialize(name, env, config, vm=nil) @logger = Log4r::Logger.new("vagrant::vm") @@ -18,6 +19,7 @@ module Vagrant @env = env @config = config @box = env.boxes.find(config.vm.box) + @driver = Driver::VirtualBox.new # Load the associated guest. load_guest! diff --git a/vagrant.gemspec b/vagrant.gemspec index 471def75a..ce8185e34 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -15,6 +15,7 @@ Gem::Specification.new do |s| s.rubyforge_project = "vagrant" s.add_dependency "archive-tar-minitar", "= 0.5.2" + s.add_dependency "childprocess", "~> 0.2.3" s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "json", "~> 1.5.1" s.add_dependency "log4r", "~> 1.1.9" @@ -27,8 +28,7 @@ Gem::Specification.new do |s| s.add_development_dependency "contest", ">= 0.1.2" s.add_development_dependency "minitest", "~> 2.5.1" s.add_development_dependency "mocha" - s.add_development_dependency "childprocess", "~> 0.2.3" - s.add_development_dependency "sys-proctable", "~> 0.9.0" + #s.add_development_dependency "sys-proctable", "~> 0.9.0" s.add_development_dependency "rspec-core", "~> 2.7.1" s.add_development_dependency "rspec-expectations", "~> 2.7.0" s.add_development_dependency "rspec-mocks", "~> 2.7.0"