diff --git a/lib/mb/cli_gateway.rb b/lib/mb/cli_gateway.rb index fdb005fe..423d2b52 100644 --- a/lib/mb/cli_gateway.rb +++ b/lib/mb/cli_gateway.rb @@ -350,6 +350,10 @@ def enable(hostname) desc: "Perform HOST disable even if the environment is locked", default: false, aliases: "-f" + method_option :offline, + type: :boolean, + desc: "Do not attempt to connect to the node to disable services. Assume it is offline.", + default: false desc "disable HOST", "Stop services on HOST and prevent chef-client from running until reenabled." def disable(hostname) job = node_querier.async_disable(hostname, options.to_hash.symbolize_keys) diff --git a/lib/mb/node_querier.rb b/lib/mb/node_querier.rb index 006c2bad..4fcf548e 100644 --- a/lib/mb/node_querier.rb +++ b/lib/mb/node_querier.rb @@ -287,6 +287,8 @@ def async_purge(host, options = {}) # @param [Hash] options # # @option options [Boolean] :force (false) Ignore environment lock and execute anyway. + # @option options [Boolean] :offline (false) Do not attempt to connect to the node to + # disable services. Assume it is offline. # # @return [MB::JobTicket] def async_disable(host, options = {}) @@ -423,17 +425,25 @@ def enable(job, host, options = {}) # @param [Hash] options # # @option options [Boolean] :force (false) Ignore environment lock and execute anyway. + # @option options [Boolean] :offline (false) Do not attempt to connect to the node to + # disable services. Assume it is offline. # def disable(job, host, options = {}) - job.report_running("Discovering host's registered node name") - node_name = registered_as(host) - if !node_name - # TODO auth could fail and cause this to throw - job.report_failure("Could not discover the host's node name. The host may not be " + - "registered with Chef or the embedded Ruby used to identify the " + - "node name may not be available. #{host} was not disabled!") - end - job.set_status("Host registered as #{node_name}.") + node_name = if options[:offline] + job.report_running("Using #{host} as node name as --offline was passed") + host + else + job.report_running("Discovering host's registered node name") + discovered_name = registered_as(host) + if !node_name + # TODO auth could fail and cause this to throw + job.report_failure("Could not discover the host's node name. The host may not be " + + "registered with Chef or the embedded Ruby used to identify the " + + "node name may not be available. #{host} was not disabled!") + end + job.set_status("Host registered as #{discovered_name}.") + discovered_name + end node = fetch_node(job, node_name) @@ -444,12 +454,30 @@ def disable(job, host, options = {}) job.set_status("#{node.name} is already disabled.") success = true else - required_run_list = on_dynamic_services(job, node) do |dynamic_service, plugin| - dynamic_service.node_state_change(job, - plugin, - node, - MB::Gear::DynamicService::STOP, - false) + required_run_list = [] + unless options[:offline] + required_run_list = on_dynamic_services(job, node) do |dynamic_service, plugin| + dynamic_service.node_state_change(job, + plugin, + node, + MB::Gear::DynamicService::STOP, + false) + end + if !required_run_list.empty? + job.set_status "Running chef with the following run list: #{required_run_list.inspect}" + bulk_chef_run(job, [node], required_run_list) + else + job.set_status "No recipes required to run." + end + end + + node.run_list = [DISABLED_RUN_LIST_ENTRY].concat(node.run_list) + + if node.save + job.set_status "#{node.name} disabled." + success = true + else + job.set_status "#{node.name} did not save! Disabled run_list entry was unable to be added to the node due to node save failure." end end diff --git a/spec/unit/mb/node_querier_spec.rb b/spec/unit/mb/node_querier_spec.rb index 7b2f004c..7097d011 100644 --- a/spec/unit/mb/node_querier_spec.rb +++ b/spec/unit/mb/node_querier_spec.rb @@ -258,7 +258,7 @@ let(:host) { "192.168.1.1" } let(:options) { Hash.new } - it "creates a Job and delegates to #disable" do + it "creates a Job and delegates to #enable" do ticket = double('ticket') job = double('job', ticket: ticket) MB::Job.should_receive(:new).and_return(job) @@ -275,7 +275,7 @@ describe "#enable" do let(:host) { "192.168.1.1" } - let(:job) { MB::Job.new(:disable) } + let(:job) { MB::Job.new(:enable) } let(:future_stub) { double(Celluloid::Future, value: nil) } let(:node_stub) { double(Ridley::NodeObject, @@ -422,6 +422,17 @@ e.message.should == "exit" end end + + it "reports failure when using --offline" do + subject.should_not_receive(:registered_as) + job.should_receive(:report_failure).with("The node #{host} is not registered with the Chef server.").and_return { raise "exit" } + subject.stub_chain("chef_connection.node.find").and_return { raise Ridley::Errors::ResourceNotFound } + begin + subject.disable(job, host, offline: true) + rescue => e + e.message.should == "exit" + end + end end context "when the node is registered" do @@ -452,11 +463,16 @@ components: [send(:"#{x}_component")]) } let(:"environment") { "theenv" } end + + before do + subject.stub_chain(:chef_connection, :node, :find).with(node_name).and_return(node) + node.should_receive(:run_list).and_return(run_list) + node.stub(:run_list=) + end it "disables the node" do subject.stub(:plugin_manager).and_return(plugin_manager) subject.should_receive(:registered_as).with(host).and_return(node_name) - subject.stub_chain(:chef_connection, :node, :find).with(node_name).and_return(node) %w[foo bar].each_with_index do |x, i| plugin_manager .should_receive(:for_run_list_entry) @@ -466,13 +482,25 @@ send(:"#{x}_service").stub(:dynamic_service?).and_return(true) send(:"#{x}_group").should_receive(:includes_recipe?).with(run_list[i]).and_return(true) send(:"dynamic_#{x}_service").should_receive(:node_state_change).with(job, send(:"#{x}_plugin"), node, MB::Gear::DynamicService::STOP, false) - node.should_receive(:run_list).and_return(run_list) - node.stub(:run_list=) end subject.should_receive(:bulk_chef_run).with(job, [node], %w[foo bar].collect { |x| send(:"service_#{x}_recipe") }) subject.disable(job, host) end + + it "disables the node without running the service command when provided --offline" do + subject.should_not_receive(:on_dynamic_services) + subject.should_not_receive(:bulk_chef_run) + node.should_receive(:save).and_return true + job.should_receive(:report_success).with("#{node.name} disabled.") + subject.disable(job, node_name, offline: true) + end + + it "reports failure when unable to disable the node" do + node.should_receive(:save).and_return false + job.should_receive(:report_failure).with("#{node.name} did not save! Disabled run_list entry was unable to be added to the node due to node save failure.") + subject.disable(job, node_name, offline: true) + end end end