diff options
Diffstat (limited to 'spec/unit/client_spec.rb')
-rw-r--r-- | spec/unit/client_spec.rb | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb new file mode 100644 index 0000000000..9d0c88dad1 --- /dev/null +++ b/spec/unit/client_spec.rb @@ -0,0 +1,290 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright 2008-2010 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' + +require 'chef/run_context' +require 'chef/rest' +require 'rbconfig' + +shared_examples_for Chef::Client do + before do + Chef::Log.logger = Logger.new(StringIO.new) + + # Node/Ohai data + @hostname = "hostname" + @fqdn = "hostname.example.org" + Chef::Config[:node_name] = @fqdn + ohai_data = { :fqdn => @fqdn, + :hostname => @hostname, + :platform => 'example-platform', + :platform_version => 'example-platform-1.0', + :data => {} } + ohai_data.stub!(:all_plugins).and_return(true) + ohai_data.stub!(:data).and_return(ohai_data) + Ohai::System.stub!(:new).and_return(ohai_data) + + @node = Chef::Node.new + @node.name(@fqdn) + @node.chef_environment("_default") + + @client = Chef::Client.new + @client.node = @node + end + + describe "authentication protocol selection" do + after do + Chef::Config[:authentication_protocol_version] = "1.0" + end + + context "when the node name is <= 90 bytes" do + it "does not force the authentication protocol to 1.1" do + Chef::Config[:node_name] = ("f" * 90) + # ugly that this happens as a side effect of a getter :( + @client.node_name + Chef::Config[:authentication_protocol_version].should == "1.0" + end + end + + context "when the node name is > 90 bytes" do + it "sets the authentication protocol to version 1.1" do + Chef::Config[:node_name] = ("f" * 91) + # ugly that this happens as a side effect of a getter :( + @client.node_name + Chef::Config[:authentication_protocol_version].should == "1.1" + end + end + 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 + # 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]).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]).exactly(1).and_return(mock_chef_rest_for_node) + + # --Client#build_node + # looks up the node, which we will return, then later saves it. + Chef::Node.should_receive(:find_or_create).with(@fqdn).and_return(@node) + + # --ResourceReporter#node_load_completed + # gets a run id from the server for storing resource history + # (has its own tests, so stubbing it here.) + Chef::ResourceReporter.any_instance.should_receive(:node_load_completed) + + # --ResourceReporter#run_completed + # updates the server with the resource history + # (has its own tests, so stubbing it here.) + Chef::ResourceReporter.any_instance.should_receive(:run_completed) + # --Client#setup_run_context + # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync + # + Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks) + mock_chef_rest_for_node.should_receive(:post_rest).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({}) + + # --Client#converge + Chef::Runner.should_receive(:new).and_return(mock_chef_runner) + mock_chef_runner.should_receive(:converge).and_return(true) + + # --Client#save_updated_node + 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) + + Chef::RunLock.any_instance.should_receive(:acquire) + Chef::RunLock.any_instance.should_receive(:release) + + # 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) + 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 + + 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 + Chef::Node.stub!(:find_or_create).and_return(@node) + @node.stub!(:save) + @client.build_node + end + + it "notifies observers that the run has started" do + notified = false + Chef::Client.when_run_starts do |run_status| + run_status.node.should == @node + notified = true + end + + @client.run_started + notified.should be_true + end + + it "notifies observers that the run has completed successfully" do + notified = false + Chef::Client.when_run_completes_successfully do |run_status| + run_status.node.should == @node + notified = true + end + + @client.run_completed_successfully + notified.should be_true + end + + it "notifies observers that the run failed" do + notified = false + Chef::Client.when_run_fails do |run_status| + run_status.node.should == @node + notified = true + end + + @client.run_failed + notified.should be_true + end + end + end + + describe "build_node" do + it "should expand the roles and recipes for the node" do + @node.run_list << "role[role_containing_cookbook1]" + role_containing_cookbook1 = Chef::Role.new + role_containing_cookbook1.name("role_containing_cookbook1") + role_containing_cookbook1.run_list << "cookbook1" + + # build_node will call Node#expand! with server, which will + # eventually hit the server to expand the included role. + mock_chef_rest = mock("Chef::REST") + mock_chef_rest.should_receive(:get_rest).with("roles/role_containing_cookbook1").and_return(role_containing_cookbook1) + Chef::REST.should_receive(:new).and_return(mock_chef_rest) + + # check pre-conditions. + @node[:roles].should be_nil + @node[:recipes].should be_nil + + @client.build_node + + # check post-conditions. + @node[:roles].should_not be_nil + @node[:roles].length.should == 1 + @node[:roles].should include("role_containing_cookbook1") + @node[:recipes].should_not be_nil + @node[:recipes].length.should == 1 + @node[:recipes].should include("cookbook1") + end + end + + describe "when a run list override is provided" do + before do + @node = Chef::Node.new + @node.name(@fqdn) + @node.chef_environment("_default") + @node.automatic_attrs[:platform] = "example-platform" + @node.automatic_attrs[:platform_version] = "example-platform-1.0" + end + + it "should permit spaces in overriding run list" do + @client = Chef::Client.new(nil, :override_runlist => 'role[a], role[b]') + end + + it "should override the run list and save original runlist" do + @client = Chef::Client.new(nil, :override_runlist => 'role[test_role]') + @client.node = @node + + @node.run_list << "role[role_containing_cookbook1]" + + override_role = Chef::Role.new + override_role.name 'test_role' + override_role.run_list << 'cookbook1' + + original_runlist = @node.run_list.dup + + mock_chef_rest = mock("Chef::REST") + mock_chef_rest.should_receive(:get_rest).with("roles/test_role").and_return(override_role) + Chef::REST.should_receive(:new).and_return(mock_chef_rest) + + @node.should_receive(:save).and_return(nil) + + @client.build_node + + @node[:roles].should_not be_nil + @node[:roles].should eql(['test_role']) + @node[:recipes].should eql(['cookbook1']) + + @client.save_updated_node + + @node.run_list.should == original_runlist + + end + 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 |