diff --git a/lib/vagrant/action/builtin.rb b/lib/vagrant/action/builtin.rb index 6cfd620b7..ec5f099c8 100644 --- a/lib/vagrant/action/builtin.rb +++ b/lib/vagrant/action/builtin.rb @@ -23,6 +23,7 @@ module Vagrant use VM::HostName use VM::Network use VM::Customize + use VM::Modify use VM::Boot end) @@ -73,6 +74,7 @@ module Vagrant use Action[:halt] use VM::ClearForwardedPorts use VM::ClearSharedFolders + use VM::Modify use VM::Export use VM::PackageVagrantfile use VM::Package diff --git a/lib/vagrant/action/vm.rb b/lib/vagrant/action/vm.rb index c1e3ea2c2..b8b43a9af 100644 --- a/lib/vagrant/action/vm.rb +++ b/lib/vagrant/action/vm.rb @@ -18,6 +18,7 @@ module Vagrant autoload :HostName, 'vagrant/action/vm/host_name' autoload :Import, 'vagrant/action/vm/import' autoload :MatchMACAddress, 'vagrant/action/vm/match_mac_address' + autoload :Modify, 'vagrant/action/vm/modify' autoload :Network, 'vagrant/action/vm/network' autoload :NFS, 'vagrant/action/vm/nfs' autoload :Package, 'vagrant/action/vm/package' diff --git a/lib/vagrant/action/vm/customize.rb b/lib/vagrant/action/vm/customize.rb index 3068e112d..2cce925e2 100644 --- a/lib/vagrant/action/vm/customize.rb +++ b/lib/vagrant/action/vm/customize.rb @@ -7,11 +7,15 @@ module Vagrant end def call(env) - if !env.env.config.vm.proc_stack.empty? - env.ui.info I18n.t("vagrant.actions.vm.customize.running") - env.env.config.vm.run_procs!(env["vm"].vm) - env["vm"].vm.save - env["vm"].reload! + if !env["config"].vm.proc_stack.empty? + # Create the proc which runs all of our procs + proc = lambda do |vm| + env.ui.info I18n.t("vagrant.actions.vm.customize.running") + env["config"].vm.run_procs!(vm) + end + + # Add it to modify sequence + env["vm.modify"].call(proc) end @app.call(env) diff --git a/lib/vagrant/action/vm/match_mac_address.rb b/lib/vagrant/action/vm/match_mac_address.rb index 2db2d85d9..4ca995be9 100644 --- a/lib/vagrant/action/vm/match_mac_address.rb +++ b/lib/vagrant/action/vm/match_mac_address.rb @@ -9,11 +9,15 @@ module Vagrant def call(env) raise Errors::VMBaseMacNotSpecified if !env.env.config.vm.base_mac - env["config"].vm.customize do |vm| + # Create the proc which we want to use to modify the virtual machine + proc = lambda do |vm| env.ui.info I18n.t("vagrant.actions.vm.match_mac.matching") vm.network_adapters.first.mac_address = env["config"].vm.base_mac end + # Add the proc to the modification chain + env["vm.modify"].call(proc) + @app.call(env) end end diff --git a/lib/vagrant/action/vm/modify.rb b/lib/vagrant/action/vm/modify.rb new file mode 100644 index 000000000..5f6895d66 --- /dev/null +++ b/lib/vagrant/action/vm/modify.rb @@ -0,0 +1,37 @@ +module Vagrant + class Action + module VM + # This class allows other actions on the virtual machine object + # to be consolidated under a single write lock. This vastly speeds + # up modification of virtual machines. This should be used whereever + # possible when dealing with virtual machine modifications. + class Modify + include Util::StackedProcRunner + + def initialize(app, env) + @app = app + + # Initialize the proc_stack, which should already be empty + # but just making sure here. + proc_stack.clear + + # Create the lambda in the environment which is to be called + # to add new procs to the modification sequence. + env["vm.modify"] = lambda do |*procs| + procs.each { |p| push_proc(&p) } + end + end + + def call(env) + # Run the procs we have saved up, save the machine, and reload + # to verify we get the new settings + run_procs!(env["vm"].vm) + env["vm"].vm.save + env["vm"].reload! + + @app.call(env) + end + end + end + end +end diff --git a/test/vagrant/action/vm/customize_test.rb b/test/vagrant/action/vm/customize_test.rb index cdbf0bd6b..aede1c0db 100644 --- a/test/vagrant/action/vm/customize_test.rb +++ b/test/vagrant/action/vm/customize_test.rb @@ -8,23 +8,29 @@ class CustomizeVMActionTest < Test::Unit::TestCase @vm = mock("vm") @env["vm"] = @vm + @env["vm.modify"] = mock("proc") @internal_vm = mock("internal") @vm.stubs(:vm).returns(@internal_vm) end should "not run anything if no customize blocks exist" do - @env.env.config.vm.proc_stack.clear - @internal_vm.expects(:save).never + @env["config"].vm.proc_stack.clear + @env["vm.modify"].expects(:call).never @app.expects(:call).with(@env).once @instance.call(@env) end should "run the VM customization procs then save the VM" do - @env.env.config.vm.customize { |vm| } - @env.env.config.vm.expects(:run_procs!).with(@internal_vm) - @internal_vm.expects(:save).once - @env["vm"].expects(:reload!).once + ran = false + @env["config"].vm.customize { |vm| } + @env["config"].vm.expects(:run_procs!).with(@internal_vm) + + @env["vm.modify"].expects(:call).with() do |proc| + proc.call(@internal_vm) + true + end + @app.expects(:call).with(@env).once @instance.call(@env) end diff --git a/test/vagrant/action/vm/match_mac_address_test.rb b/test/vagrant/action/vm/match_mac_address_test.rb index b091b55f0..a75fc051c 100644 --- a/test/vagrant/action/vm/match_mac_address_test.rb +++ b/test/vagrant/action/vm/match_mac_address_test.rb @@ -22,7 +22,11 @@ class MatchMACAddressVMActionTest < Test::Unit::TestCase @internal_vm.expects(:network_adapters).returns([nic]).once.in_sequence(update_seq) @app.expects(:call).with(@env).once.in_sequence(update_seq) - @env["config"].vm.expects(:customize).yields(@internal_vm) + @env["vm.modify"].expects(:call).with() do |proc| + proc.call(@internal_vm) + true + end + @instance.call(@env) end diff --git a/test/vagrant/action/vm/modify_test.rb b/test/vagrant/action/vm/modify_test.rb new file mode 100644 index 000000000..2c1aa437e --- /dev/null +++ b/test/vagrant/action/vm/modify_test.rb @@ -0,0 +1,38 @@ +require "test_helper" + +class ModifyVMActionTest < Test::Unit::TestCase + setup do + @klass = Vagrant::Action::VM::Modify + @app, @env = action_env + + @vm = mock("vm") + @vm.stubs(:ssh).returns(mock("ssh")) + @env["vm"] = @vm + + @internal_vm = mock("internal") + @vm.stubs(:vm).returns(@internal_vm) + + @instance = @klass.new(@app, @env) + end + + context "initialization" do + should "have the vm.modify function setup in the environment" do + assert @env.has_key?("vm.modify") + end + end + + context "calling" do + should "run the procs with the VM as an argument and save the VM" do + seq = sequence("procseq") + + proc = Proc.new { |vm| } + @env["vm.modify"].call(proc) + + proc.expects(:call).with(@internal_vm).once.in_sequence(seq) + @internal_vm.expects(:save).once.in_sequence(seq) + @vm.expects(:reload!).once.in_sequence(seq) + + @instance.call(@env) + end + end +end