New abstraction: Systems. Updated config and environment to properly load configured system.

This allows the OS-specific tasks to be pulled out into separate classes, so that other systems could potentially be supported. For now, a "Linux" system has been created.
This commit is contained in:
Mitchell Hashimoto 2010-04-25 01:46:51 -07:00
parent 1429723277
commit 95938c652d
11 changed files with 292 additions and 16 deletions

View File

@ -21,6 +21,7 @@ Vagrant::Config.run do |config|
config.vm.shared_folder_uid = nil
config.vm.shared_folder_gid = nil
config.vm.boot_mode = "vrdp"
config.vm.system = :linux
config.package.name = 'vagrant'
config.package.extension = '.box'

View File

@ -86,6 +86,7 @@ module Vagrant
attr_accessor :provisioner
attr_accessor :shared_folder_uid
attr_accessor :shared_folder_gid
attr_accessor :system
def initialize
@forwarded_ports = {}

View File

@ -174,8 +174,7 @@ module Vagrant
return if !root_path || !File.file?(dotfile_path)
File.open(dotfile_path) do |f|
@vm = Vagrant::VM.find(f.read)
@vm.env = self if @vm
@vm = Vagrant::VM.find(f.read, self)
end
rescue Errno::ENOENT
@vm = nil
@ -206,9 +205,7 @@ module Vagrant
# in {Command.up}. This will very likely be refactored at a later
# time.
def create_vm
@vm = VM.new
@vm.env = self
@vm
@vm = VM.new(self)
end
# Persists this environment's VM to the dotfile so it can be

View File

@ -0,0 +1,44 @@
module Vagrant
module Systems
# The base class for a "system." A system represents an installed
# operating system on a given box. There are some portions of
# Vagrant which are fairly OS-specific (such as mounting shared
# folders) and while the number is few, this abstraction allows
# more obscure operating systems to be installed without having
# to directly modify Vagrant internals.
#
# Subclasses of the system base class are expected to implement
# all the methods. These methods are described in the comments
# above their definition.
#
# **This is by no means a complete specification. The methods
# required by systems can and will change at any time. Any
# changes will be noted on release notes.**
class Base
include Vagrant::Util
# The VM which this system is tied to.
attr_reader :vm
# Initializes the system. Any subclasses MUST make sure this
# method is called on the parent. Therefore, if a subclass overrides
# `initialize`, then you must call `super`.
def initialize(vm)
@vm = vm
end
# Mounts a shared folder. This method is called by the shared
# folder action with an open SSH session (passed in as `ssh`).
# This method should create, mount, and properly set permissions
# on the shared folder. This method should also properly
# adhere to any configuration values such as `shared_folder_uid`
# on `config.vm`.
#
# @param [Object] ssh The Net::SSH session.
# @param [String] name The name of the shared folder.
# @param [String] guestpath The path on the machine which the user
# wants the folder mounted.
def mount_shared_folder(ssh, name, guestpath); end
end
end
end

View File

@ -0,0 +1,46 @@
module Vagrant
module Systems
# A general Vagrant system implementation for "linux." In general,
# any linux-based OS will work fine with this system, although its
# not tested exhaustively. BSD or other based systems may work as
# well, but that hasn't been tested at all.
#
# At any rate, this system implementation should server as an
# example of how to implement any custom systems necessary.
class Linux < Base
#-------------------------------------------------------------------
# Overridden methods
#-------------------------------------------------------------------
def mount_shared_folder(ssh, name, guestpath)
ssh.exec!("sudo mkdir -p #{guestpath}")
mount_folder(ssh, name, guestpath)
ssh.exec!("sudo chown #{vm.env.config.ssh.username} #{guestpath}")
end
#-------------------------------------------------------------------
# "Private" methods which assist above methods
#-------------------------------------------------------------------
def mount_folder(ssh, name, guestpath, sleeptime=5)
# Determine the permission string to attach to the mount command
perms = []
perms << "uid=#{vm.env.config.vm.shared_folder_uid}"
perms << "gid=#{vm.env.config.vm.shared_folder_gid}"
perms = " -o #{perms.join(",")}" if !perms.empty?
attempts = 0
while true
result = ssh.exec!("sudo mount -t vboxsf#{perms} #{name} #{guestpath}") do |ch, type, data|
# net/ssh returns the value in ch[:result] (based on looking at source)
ch[:result] = !!(type == :stderr && data =~ /No such device/i)
end
break unless result
attempts += 1
raise Actions::ActionException.new(:vm_mount_fail) if attempts >= 10
sleep sleeptime
end
end
end
end
end

View File

@ -2,22 +2,48 @@ module Vagrant
class VM < Actions::Runner
include Vagrant::Util
attr_accessor :env
attr_reader :env
attr_reader :system
attr_accessor :vm
attr_accessor :from
class << self
# Finds a virtual machine by a given UUID and either returns
# a Vagrant::VM object or returns nil.
def find(uuid)
def find(uuid, env=nil)
vm = VirtualBox::VM.find(uuid)
return nil if vm.nil?
new(vm)
new(env, vm)
end
end
def initialize(vm=nil)
def initialize(env, vm=nil)
@env = env
@vm = vm
load_system!
end
def load_system!
system = env.config.vm.system
if system.is_a?(Class)
@system = system.new(self)
error_and_exit(:system_invalid_class, :system => system.to_s) unless @system.is_a?(Systems::Base)
elsif system.is_a?(Symbol)
# Hard-coded internal systems
mapping = {
:linux => Systems::Linux
}
if !mapping.has_key?(system)
error_and_exit(:system_unknown_type, :system => system.to_s)
return # for tests
end
@system = mapping[system].new(self)
else
error_and_exit(:system_unspecified)
end
end
def uuid

View File

@ -103,7 +103,17 @@
For a more detailed guide please consult:
http://vagrantup.com/docs/getting-started/windows.html
:system_invalid_class: |-
The specified system does not inherit from `Vagrant::Systems::Base`. The
specified system class must inherit from this class.
The specified system class was: <%= system %>
:system_unknown_type: |-
The specified system type is unknown: <%= system %>. Please change this
to a proper value.
:system_unspecified: |-
A VM system type must be specified! This is done via the `config.vm.system`
configuration value. Please read the documentation online for more information.
:virtualbox_import_failure: |-
The VM import failed! Try running `VBoxManage import` on the box file
manually for more verbose error output.

View File

@ -41,6 +41,7 @@ class Test::Unit::TestCase
config.vm.forward_port("ssh", 22, 2222)
config.vm.shared_folder_uid = nil
config.vm.shared_folder_gid = nil
config.vm.system = :linux
config.package.name = 'vagrant'
config.package.extension = '.box'

View File

@ -356,11 +356,10 @@ class EnvironmentTest < Test::Unit::TestCase
should "loading of the uuid from the dotfile" do
vm = mock("vm")
vm.expects(:env=).with(@env)
filemock = mock("filemock")
filemock.expects(:read).returns("foo")
Vagrant::VM.expects(:find).with("foo").returns(vm)
Vagrant::VM.expects(:find).with("foo", @env).returns(vm)
File.expects(:open).with(@env.dotfile_path).once.yields(filemock)
File.expects(:file?).with(@env.dotfile_path).once.returns(true)
@env.load_vm!
@ -371,7 +370,7 @@ class EnvironmentTest < Test::Unit::TestCase
should "not set the environment if the VM is nil" do
filemock = mock("filemock")
filemock.expects(:read).returns("foo")
Vagrant::VM.expects(:find).with("foo").returns(nil)
Vagrant::VM.expects(:find).with("foo", @env).returns(nil)
File.expects(:open).with(@env.dotfile_path).once.yields(filemock)
File.expects(:file?).with(@env.dotfile_path).once.returns(true)

View File

@ -0,0 +1,98 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class LinuxSystemTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Systems::Linux
@vm = mock("vm")
@vm.stubs(:env).returns(mock_environment)
@instance = @klass.new(@vm)
end
context "mounting shared folders" do
setup do
@ssh = mock("ssh")
@name = "foo"
@guestpath = "/bar"
end
should "create the dir, mount the folder, then set permissions" do
mount_seq = sequence("mount_seq")
@ssh.expects(:exec!).with("sudo mkdir -p #{@guestpath}").in_sequence(mount_seq)
@instance.expects(:mount_folder).with(@ssh, @name, @guestpath).in_sequence(mount_seq)
@ssh.expects(:exec!).with("sudo chown #{@vm.env.config.ssh.username} #{@guestpath}").in_sequence(mount_seq)
@instance.mount_shared_folder(@ssh, @name, @guestpath)
end
end
#-------------------------------------------------------------------
# "Private" methods tests
#-------------------------------------------------------------------
context "mounting the main folder" do
setup do
@ssh = mock("ssh")
@name = "foo"
@guestpath = "bar"
@sleeptime = 0
@limit = 10
@success_return = false
end
def mount_folder
@instance.mount_folder(@ssh, @name, @guestpath, @sleeptime)
end
should "execute the proper mount command" do
@ssh.expects(:exec!).with("sudo mount -t vboxsf -o uid=#{@vm.env.config.ssh.username},gid=#{@vm.env.config.ssh.username} #{@name} #{@guestpath}").returns(@success_return)
mount_folder
end
should "test type of text and text string to detect error" do
data = mock("data")
data.expects(:[]=).with(:result, !@success_return)
@ssh.expects(:exec!).yields(data, :stderr, "No such device").returns(@success_return)
mount_folder
end
should "test type of text and test string to detect success" do
data = mock("data")
data.expects(:[]=).with(:result, @success_return)
@ssh.expects(:exec!).yields(data, :stdout, "Nothing such device").returns(@success_return)
mount_folder
end
should "raise an ActionException if the command fails constantly" do
@ssh.expects(:exec!).times(@limit).returns(!@success_return)
assert_raises(Vagrant::Actions::ActionException) {
mount_folder
}
end
should "not raise any exception if the command succeeded" do
@ssh.expects(:exec!).once.returns(@success_return)
assert_nothing_raised {
mount_folder
}
end
should "add uid AND gid to mount" do
uid = "foo"
gid = "bar"
env = mock_environment do |config|
config.vm.shared_folder_uid = uid
config.vm.shared_folder_gid = gid
end
@vm.stubs(:env).returns(env)
@ssh.expects(:exec!).with("sudo mount -t vboxsf -o uid=#{uid},gid=#{gid} #{@name} #{@guestpath}").returns(@success_return)
mount_folder
end
end
end

View File

@ -14,7 +14,7 @@ class VMTest < Test::Unit::TestCase
context "being an action runner" do
should "be an action runner" do
vm = Vagrant::VM.new
vm = Vagrant::VM.new(@env)
assert vm.is_a?(Vagrant::Actions::Runner)
end
end
@ -27,7 +27,7 @@ class VMTest < Test::Unit::TestCase
should "return a Vagrant::VM object for that VM otherwise" do
VirtualBox::VM.expects(:find).with("foo").returns("bar")
result = Vagrant::VM.find("foo")
result = Vagrant::VM.find("foo", mock_environment)
assert result.is_a?(Vagrant::VM)
assert_equal "bar", result.vm
end
@ -35,10 +35,63 @@ class VMTest < Test::Unit::TestCase
context "vagrant VM instance" do
setup do
@vm = Vagrant::VM.new(@mock_vm)
@vm = Vagrant::VM.new(@env, @mock_vm)
@mock_vm.stubs(:uuid).returns("foo")
end
context "loading associated system" do
should "error and exit if system is not specified" do
@vm.env.config.vm.system = nil
@vm.expects(:error_and_exit).with(:system_unspecified).once
@vm.load_system!
end
context "with a class" do
class FakeSystemClass
def initialize(vm); end
end
should "initialize class if given" do
@vm.env.config.vm.system = Vagrant::Systems::Linux
@vm.expects(:error_and_exit).never
@vm.load_system!
assert @vm.system.is_a?(Vagrant::Systems::Linux)
end
should "error and exit if class has invalid parent" do
@vm.env.config.vm.system = FakeSystemClass
@vm.expects(:error_and_exit).with(:system_invalid_class, :system => @vm.env.config.vm.system.to_s).once
@vm.load_system!
end
end
context "with a symbol" do
should "initialize proper symbols" do
valid = {
:linux => Vagrant::Systems::Linux
}
valid.each do |symbol, klass|
@vm.env.config.vm.system = symbol
@vm.expects(:error_and_exit).never
@vm.load_system!
assert @vm.system.is_a?(klass)
assert_equal @vm, @vm.system.vm
end
end
should "error and exit with invalid symbol" do
@vm.env.config.vm.system = :shall_never_exist
@vm.expects(:error_and_exit).with(:system_unknown_type, :system => @vm.env.config.vm.system.to_s).once
@vm.load_system!
end
end
end
context "uuid" do
should "call UUID on VM object" do
uuid = mock("uuid")