diff options
author | Chris Roberts <chrisroberts.code@gmail.com> | 2012-05-11 14:51:06 -0700 |
---|---|---|
committer | Bryan McLellan <btm@opscode.com> | 2012-07-26 11:31:25 -0700 |
commit | eab92691df5665994c888f3727eecc3577fb5f69 (patch) | |
tree | 9a5414ead047d6338fd669cf2e23f510b30f5557 | |
parent | 43e2a3851bac0fa587ec71f594c3c9ea2cdfec1b (diff) | |
download | chef-eab92691df5665994c888f3727eecc3577fb5f69.tar.gz |
Converge via fork
-rw-r--r-- | chef/lib/chef/application/client.rb | 6 | ||||
-rw-r--r-- | chef/lib/chef/application/solo.rb | 8 | ||||
-rw-r--r-- | chef/lib/chef/client.rb | 121 | ||||
-rw-r--r-- | chef/lib/chef/config.rb | 1 | ||||
-rw-r--r-- | chef/spec/unit/client_spec.rb | 62 |
5 files changed, 135 insertions, 63 deletions
diff --git a/chef/lib/chef/application/client.rb b/chef/lib/chef/application/client.rb index dffae827ac..e9408ce5fc 100644 --- a/chef/lib/chef/application/client.rb +++ b/chef/lib/chef/application/client.rb @@ -169,6 +169,12 @@ class Chef::Application::Client < Chef::Application :long => '--why-run', :description => 'Enable whyrun mode', :boolean => true + + option :client_fork, + :short => "-f", + :long => "--fork", + :description => "Fork client", + :boolean => true attr_reader :chef_client_json diff --git a/chef/lib/chef/application/solo.rb b/chef/lib/chef/application/solo.rb index 93b7f0bb34..7ec6c36e76 100644 --- a/chef/lib/chef/application/solo.rb +++ b/chef/lib/chef/application/solo.rb @@ -132,7 +132,13 @@ class Chef::Application::Solo < Chef::Application Chef::RunList::RunListItem.new(item) } } - + + option :client_fork, + :short => "-f", + :long => "--fork", + :description => "Fork client", + :boolean => true + option :why_run, :short => '-W', :long => '--why-run', diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb index 2802163313..ed052c1fdd 100644 --- a/chef/lib/chef/client.rb +++ b/chef/lib/chef/client.rb @@ -153,62 +153,30 @@ class Chef end # Do a full run for this Chef::Client. Calls: + # * do_run # - # * run_ohai - Collect information about the system - # * load_node - Get the last known stage from the server - # * build_node - Merge state with local changes - # * register - If not in solo mode, make sure the server knows about this client - # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks - # * converge - Bring this system up to date - # + # This provides a wrapper around #do_run allowing the + # run to be optionally forked. # === Returns - # true:: Always returns true. + # boolean:: Return value from #do_run. Should always returns true. def run - run_context = nil - - @events.run_start(Chef::VERSION) - Chef::Log.info("*** Chef #{Chef::VERSION} ***") - enforce_path_sanity - run_ohai - @events.ohai_completed(node) - register unless Chef::Config[:solo] - - load_node - - begin - build_node - - run_status.start_clock - Chef::Log.info("Starting Chef Run for #{node.name}") - run_started - - run_context = setup_run_context - - converge(run_context) - - save_updated_node - - run_status.stop_clock - Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") - run_completed_successfully - @events.run_completed(node) + if(Chef::Config[:client_fork] && Process.respond_to?(:fork)) + Chef::Log.info "Forking chef instance to converge..." + pid = fork do + Chef::Log.info "Forked instance now converging" + do_run + exit + end + Chef::Log.info "Fork successful. Waiting for new chef pid: #{pid}" + result = Process.waitpid2(pid) + raise "Forked convergence run failed" unless result.last.success? + Chef::Log.info "Forked child successfully reaped (pid: #{pid})" true - rescue Exception => e - run_status.stop_clock - run_status.exception = e - run_failed - Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}") - @events.run_failed(e) - raise - ensure - run_status = nil - run_context = nil - GC.start + else + do_run end - true end - # Configures the Chef::Cookbook::FileVendor class to fetch file from the # server or disk as appropriate, creates the run context for this run, and # sanity checks the cookbook collection. @@ -412,6 +380,61 @@ class Chef private + # Do a full run for this Chef::Client. Calls: + # + # * run_ohai - Collect information about the system + # * build_node - Get the last known state, merge with local changes + # * register - If not in solo mode, make sure the server knows about this client + # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks + # * converge - Bring this system up to date + # + # === Returns + # true:: Always returns true. + def do_run + run_context = nil + + @events.run_start(Chef::VERSION) + Chef::Log.info("*** Chef #{Chef::VERSION} ***") + enforce_path_sanity + run_ohai + @events.ohai_completed(node) + register unless Chef::Config[:solo] + + load_node + + begin + build_node + + run_status.start_clock + Chef::Log.info("Starting Chef Run for #{node.name}") + run_started + + run_context = setup_run_context + + converge(run_context) + + save_updated_node + + run_status.stop_clock + Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") + run_completed_successfully + @events.run_completed(node) + true + rescue Exception => e + run_status.stop_clock + run_status.exception = e + run_failed + Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}") + @events.run_failed(e) + raise + ensure + run_status = nil + run_context = nil + GC.start + end + true + end + # Ensures runlist override contains RunListItem instances def runlist_override_sanity_check! @override_runlist = @override_runlist.split(',') if @override_runlist.is_a?(String) diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb index 65af681f9e..727314280f 100644 --- a/chef/lib/chef/config.rb +++ b/chef/lib/chef/config.rb @@ -196,6 +196,7 @@ class Chef splay nil why_run false color false + client_fork false # Set these to enable SSL authentication / mutual-authentication # with the server diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb index f7f74fa3b1..64bb8799de 100644 --- a/chef/spec/unit/client_spec.rb +++ b/chef/spec/unit/client_spec.rb @@ -24,7 +24,7 @@ require 'chef/run_context' require 'chef/rest' require 'rbconfig' -describe Chef::Client do +shared_examples_for Chef::Client do before do Chef::Log.logger = Logger.new(StringIO.new) @@ -52,24 +52,26 @@ describe Chef::Client do end describe "run" do + it "should identify the node and run ohai, then register the client" do mock_chef_rest_for_node = mock("Chef::REST (node)") mock_chef_rest_for_client = mock("Chef::REST (client)") mock_chef_rest_for_node_save = mock("Chef::REST (node save)") mock_chef_runner = mock("Chef::Runner") - # --Client#register + # --Client.register # Make sure Client#register thinks the client key doesn't # exist, so it tries to register and create one. File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(false) - # Client#register will register with the validation client name. - Chef::REST.should_receive(:new).with(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]).and_return(mock_chef_rest_for_client) - mock_chef_rest_for_client.should_receive(:register).with(@fqdn, Chef::Config[:client_key]).and_return(true) - # Client#register will then turn around create another + # Client.register will register with the validation client name. + Chef::REST.should_receive(:new).with(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]).exactly(1).and_return(mock_chef_rest_for_client) + mock_chef_rest_for_client.should_receive(:register).with(@fqdn, Chef::Config[:client_key]).exactly(1).and_return(true) + # Client.register will then turn around create another + # Chef::REST object, this time with the client key it got from the # previous step. - Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).and_return(mock_chef_rest_for_node) + Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node) # --Client#build_node # looks up the node, which we will return, then later saves it. @@ -98,19 +100,42 @@ describe Chef::Client do Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_node_save) mock_chef_rest_for_node_save.should_receive(:put_rest).with("nodes/#{@fqdn}", @node).and_return(true) + # Post conditions: check that node has been filled in correctly @client.should_receive(:run_started) @client.should_receive(:run_completed_successfully) - + if(Chef::Config[:client_fork]) + require 'stringio' + if(Chef::Config[:pipe_node]) + pipe_sim = StringIO.new + pipe_sim.should_receive(:close).exactly(4).and_return(nil) + res = '' + pipe_sim.should_receive(:puts) do |string| + res.replace(string) + end + pipe_sim.should_receive(:gets).and_return(res) + Chef::CouchDB.should_receive(:new).and_return(nil) + IO.should_receive(:pipe).and_return([pipe_sim, pipe_sim]) + IO.should_receive(:select).and_return(true) + end + proc_ret = Class.new.new + proc_ret.should_receive(:success?).and_return(true) + Process.should_receive(:waitpid2).and_return([1, proc_ret]) + @client.should_receive(:exit).and_return(nil) + @client.should_receive(:fork) do |&block| + block.call + end + end + # This is what we're testing. @client.run - - # Post conditions: check that node has been filled in correctly - @node.automatic_attrs[:platform].should == "example-platform" - @node.automatic_attrs[:platform_version].should == "example-platform-1.0" + if(!Chef::Config[:client_fork] || Chef::Config[:pipe_node]) + @node.automatic_attrs[:platform].should == "example-platform" + @node.automatic_attrs[:platform_version].should == "example-platform-1.0" + end end - + describe "when notifying other objects of the status of the chef run" do before do Chef::Client.clear_notifications @@ -224,3 +249,14 @@ describe Chef::Client do end end + +describe Chef::Client do + it_behaves_like Chef::Client +end + +describe "Chef::Client Forked" do + it_behaves_like Chef::Client + before do + Chef::Config[:client_fork] = true + end +end |