5905c02d282298887045785f2b73cb243f2aae89
[packages/precise/mcollective.git] / spec / unit / rpc / client_spec.rb
1 #!/usr/bin/env rspec
2
3 require 'spec_helper'
4
5 module MCollective
6   module RPC
7     describe Client do
8       before do
9         @coreclient = mock
10         @discoverer = mock
11
12         ddl = DDL.new("foo", "agent", false)
13         ddl.action("rspec", :description => "mock agent")
14
15         ddl.stubs(:meta).returns({:timeout => 2})
16         DDL.stubs(:new).returns(ddl)
17
18         @discoverer.stubs(:force_direct_mode?).returns(false)
19         @discoverer.stubs(:discovery_method).returns("mc")
20         @discoverer.stubs(:force_discovery_method_by_filter).returns(false)
21         @discoverer.stubs(:discovery_timeout).returns(2)
22         @discoverer.stubs(:ddl).returns(ddl)
23
24         @coreclient.stubs("options=")
25         @coreclient.stubs(:collective).returns("mcollective")
26         @coreclient.stubs(:timeout_for_compound_filter).returns(0)
27         @coreclient.stubs(:discoverer).returns(@discoverer)
28
29         Config.instance.stubs(:loadconfig).with("/nonexisting").returns(true)
30         Config.instance.stubs(:direct_addressing).returns(true)
31         Config.instance.stubs(:collectives).returns(["mcollective", "rspec"])
32         MCollective::Client.stubs(:new).returns(@coreclient)
33
34         @stderr = StringIO.new
35         @stdout = StringIO.new
36
37         @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
38         @client.stubs(:ddl).returns(ddl)
39       end
40
41       describe "#initialize" do
42         it "should fail for missing DDLs" do
43           DDL.stubs(:new).raises("DDL failure")
44           expect { Client.new("foo", {:options => {:config => "/nonexisting"}}) }.to raise_error("DDL failure")
45         end
46
47         it "should set a empty filter when none is supplied" do
48           filter = Util.empty_filter
49           Util.expects(:empty_filter).once.returns(filter)
50
51           Client.new("foo", :options => {:config => "/nonexisting"})
52         end
53
54         it "should default the discovery_timeout to nil" do
55           c = Client.new("rspec", :options => {:config => "/nonexisting"})
56           c.instance_variable_get("@discovery_timeout").should == nil
57         end
58
59         it "should accept a supplied discovery_timeout" do
60           c = Client.new("rspec", :options => {:config => "/nonexisting", :disctimeout => 10})
61           c.instance_variable_get("@discovery_timeout").should == 10
62         end
63       end
64
65       describe "#validate_request" do
66         it "should fail when a DDL isn't present" do
67           @client.instance_variable_set("@ddl", nil)
68           expect { @client.validate_request("rspec", {}) }.to raise_error("No DDL found for agent foo cannot validate inputs")
69         end
70
71         it "should validate the input arguments" do
72           @client.ddl.expects(:set_default_input_arguments).with("rspec", {})
73           @client.ddl.expects(:validate_rpc_request).with("rspec", {})
74           @client.validate_request("rspec", {})
75         end
76       end
77
78       describe "#process_results_with_block" do
79         it "should inform the stats object correctly for passed requests" do
80           response = {:senderid => "rspec", :body => {:statuscode => 0}}
81
82           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response)
83           @client.stats.expects(:ok)
84           @client.stats.expects(:node_responded).with("rspec")
85           @client.stats.expects(:time_block_execution).with(:start)
86           @client.stats.expects(:time_block_execution).with(:end)
87           @client.expects(:aggregate_reply).returns("aggregate stub")
88
89           blk = Proc.new {}
90
91           @client.process_results_with_block("rspec", response, blk, "").should == "aggregate stub"
92         end
93
94         it "should inform the stats object correctly for failed requests" do
95           @client.stats.expects(:fail)
96           @client.stats.expects(:node_responded).with("rspec")
97
98           response = {:senderid => "rspec", :body => {:statuscode => 1}}
99           blk = Proc.new {}
100
101           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response)
102           @client.process_results_with_block("rspec", response, blk, nil)
103         end
104
105         it "should raise correct exceptions on failure" do
106           blk = Proc.new {}
107
108           @client.stubs(:rpc_result_from_reply)
109
110           [[2, UnknownRPCAction], [3, MissingRPCData], [4, InvalidRPCData], [5, UnknownRPCError]].each do |err|
111             response = {:senderid => "rspec", :body => {:statuscode => err[0]}}
112
113             expect { @client.process_results_with_block("rspec", response, blk, nil) }.to raise_error(err[1])
114           end
115         end
116
117         it "should pass raw results for single arity blocks" do
118           response = {:senderid => "rspec", :body => {:statuscode => 1}}
119           blk = Proc.new {|r| r.should == response}
120
121           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response)
122           @client.process_results_with_block("rspec", response, blk, nil)
123         end
124
125         it "should pass raw and rpc style results for 2 arity blocks" do
126           response = {:senderid => "rspec", :body => {:statuscode => 1}}
127           blk = Proc.new do |r, s|
128             r.should == response
129             s.should.class == RPC::Result
130           end
131
132           @client.process_results_with_block("rspec", response, blk, nil)
133         end
134       end
135
136       describe "#process_results_without_block" do
137         it "should inform the stats object correctly for passed requests" do
138           response = {:senderid => "rspec", :body => {:statuscode => 0}}
139           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response)
140           @client.stats.expects(:ok)
141           @client.stats.expects(:node_responded).with("rspec")
142           @client.process_results_without_block(response, "rspec", nil)
143         end
144
145         it "should inform the stats object correctly for failed requests" do
146           @client.stats.expects(:fail).twice
147           @client.stats.expects(:node_responded).with("rspec").twice
148
149           response = {:senderid => "rspec", :body => {:statuscode => 1}}
150           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response)
151           @client.process_results_without_block(response, "rspec", nil)
152
153           response = {:senderid => "rspec", :body => {:statuscode => 3}}
154           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response)
155           @client.process_results_without_block(response, "rspec", nil)
156         end
157
158         it "should return the result and the aggregate" do
159           @client.expects(:aggregate_reply).returns("aggregate stub")
160
161           response = {:senderid => "rspec", :body => {:statuscode => 0}}
162           result = @client.rpc_result_from_reply("foo", "rspec", response)
163
164           @client.stubs(:rpc_result_from_reply).with("foo", "rspec", response).returns(result)
165           @client.process_results_without_block(response, "rspec", "").should == [result, "aggregate stub"]
166         end
167       end
168
169       describe "#load_aggregate_functions" do
170         it "should not load if the ddl is not set" do
171           @client.load_aggregate_functions("rspec", nil).should == nil
172         end
173
174         it "should create the aggregate for the right action" do
175           @client.ddl.expects(:action_interface).with("rspec").returns({:aggregate => []}).twice
176           Aggregate.expects(:new).with(:aggregate => []).returns("rspec aggregate")
177           @client.load_aggregate_functions("rspec", @client.ddl).should == "rspec aggregate"
178         end
179
180         it "should log and return nil on failure" do
181           @client.ddl.expects(:action_interface).raises("rspec")
182           Log.expects(:error).with(regexp_matches(/Failed to load aggregate/))
183           @client.load_aggregate_functions("rspec", @client.ddl)
184         end
185       end
186
187       describe "#aggregate_reply" do
188         it "should not call anything if the aggregate isnt set" do
189           @client.aggregate_reply(nil, nil).should == nil
190         end
191
192         it "should call the aggregate functions with the right data" do
193           result = @client.rpc_result_from_reply("rspec", "rspec", {:body => {:data => "rspec"}})
194
195           aggregate = mock
196           aggregate.expects(:call_functions).with(result).returns(aggregate)
197
198           @client.aggregate_reply(result, aggregate).should == aggregate
199         end
200
201         it "should log and return nil on failure" do
202           aggregate = mock
203           aggregate.expects(:call_functions).raises
204
205           Log.expects(:error).with(regexp_matches(/Failed to calculate aggregate summaries/))
206
207           @client.aggregate_reply({}, aggregate).should == nil
208         end
209       end
210
211       describe "#collective=" do
212         it "should validate the collective" do
213           expect { @client.collective = "fail" }.to raise_error("Unknown collective fail")
214           @client.collective = "rspec"
215         end
216
217         it "should set the collective" do
218           @client.options[:collective].should == "mcollective"
219           @client.collective = "rspec"
220           @client.options[:collective].should == "rspec"
221         end
222
223         it "should reset the client" do
224           @client.expects(:reset)
225           @client.collective = "rspec"
226         end
227       end
228
229       describe "#discovery_method=" do
230         it "should set the method" do
231           @client.discovery_method = "rspec"
232           @client.discovery_method.should == "rspec"
233         end
234
235         it "should set initial options if provided" do
236           client = Client.new("rspec", {:options => {:discovery_options => ["rspec"], :filter => Util.empty_filter, :config => "/nonexisting"}})
237           client.discovery_method = "rspec"
238           client.discovery_method.should == "rspec"
239           client.discovery_options.should == ["rspec"]
240         end
241
242         it "should clear the options if none are given initially" do
243           @client.discovery_options = ["rspec"]
244           @client.discovery_method = "rspec"
245           @client.discovery_options.should == []
246         end
247
248         it "should set the client options" do
249           @client.expects(:options).returns("rspec")
250           @client.client.expects(:options=).with("rspec")
251           @client.discovery_method = "rspec"
252         end
253
254         it "should adjust timeout for the new method" do
255           @client.expects(:discovery_timeout).once.returns(1)
256           @client.discovery_method = "rspec"
257           @client.instance_variable_get("@timeout").should == 4
258         end
259
260         it "should preserve any user supplied discovery timeout" do
261           @client.discovery_timeout = 10
262           @client.discovery_method = "rspec"
263           @client.discovery_timeout.should == 10
264         end
265
266         it "should reset the rpc client" do
267           @client.expects(:reset)
268           @client.discovery_method = "rspec"
269         end
270       end
271
272       describe "#discovery_options=" do
273         it "should flatten the options array" do
274           @client.discovery_options = "foo"
275           @client.discovery_options.should == ["foo"]
276         end
277       end
278
279       describe "#discovery_timeout" do
280         it "should favour the initial options supplied timeout" do
281           client = Client.new("rspec", {:options => {:disctimeout => 3, :filter => Util.empty_filter, :config => "/nonexisting"}})
282           client.discovery_timeout.should == 3
283         end
284
285         it "should return the DDL data if no specific options are supplied" do
286           client = Client.new("rspec", {:options => {:disctimeout => nil, :filter => Util.empty_filter, :config => "/nonexisting"}})
287           client.discovery_timeout.should == 2
288         end
289       end
290
291       describe "#discovery_timeout=" do
292         it "should store the discovery timeout" do
293           @client.discovery_timeout = 10
294           @client.discovery_timeout.should == 10
295         end
296
297         it "should update the overall timeout with the new discovery timeout" do
298           @client.instance_variable_get("@timeout").should == 4
299
300           @client.discovery_timeout = 10
301
302           @client.instance_variable_get("@timeout").should == 12
303         end
304       end
305
306       describe "#limit_method" do
307         it "should force strings to symbols" do
308           @client.limit_method = "first"
309           @client.limit_method.should == :first
310         end
311
312         it "should only allow valid methods" do
313           @client.limit_method = :first
314           @client.limit_method.should == :first
315           @client.limit_method = :random
316           @client.limit_method.should == :random
317
318           expect { @client.limit_method = :fail }.to raise_error(/Unknown/)
319           expect { @client.limit_method = "fail" }.to raise_error(/Unknown/)
320         end
321       end
322
323       describe "#method_missing" do
324         it "should reset the stats" do
325           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
326           client.stubs(:call_agent)
327
328           Stats.any_instance.expects(:reset).once
329           client.rspec
330         end
331
332         it "should validate the request against the ddl" do
333           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
334
335           client.stubs(:call_agent)
336
337           client.expects(:validate_request).with("rspec", {:arg => :val}).raises("validation failed")
338
339           expect { client.rspec(:arg => :val) }.to raise_error("validation failed")
340         end
341
342         it "should support limited targets" do
343           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
344           client.limit_targets = 10
345
346           client.expects(:pick_nodes_from_discovered).with(10).returns(["one", "two"])
347           client.expects(:custom_request).with("rspec", {}, ["one", "two"], {"identity" => /^(one|two)$/}).once
348
349           client.rspec
350         end
351
352         describe "batch mode" do
353           before do
354             Config.instance.stubs(:direct_addressing).returns(true)
355             @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
356           end
357
358           it "should support global batch_size" do
359             @client.batch_size = 10
360             @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 1)
361             @client.rspec
362           end
363
364           it "should support custom batch_size" do
365             @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 1)
366             @client.rspec :batch_size => 10
367           end
368
369           it "should allow supplied batch_size override global one" do
370             @client.batch_size = 10
371             @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 20, 1)
372             @client.rspec :batch_size => 20
373           end
374
375           it "should support global batch_sleep_time" do
376             @client.batch_size = 10
377             @client.batch_sleep_time = 20
378             @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 20)
379             @client.rspec
380           end
381
382           it "should support custom batch_sleep_time" do
383             @client.batch_size = 10
384             @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 20)
385             @client.rspec :batch_sleep_time => 20
386           end
387
388           it "should allow supplied batch_sleep_time override global one" do
389             @client.batch_size = 10
390             @client.batch_sleep_time = 10
391             @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 20)
392             @client.rspec :batch_sleep_time => 20
393           end
394         end
395
396         it "should support normal calls" do
397           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
398
399           client.expects(:call_agent).with("rspec", {}, client.options, :auto).once
400
401           client.rspec
402         end
403       end
404
405       describe "#pick_nodes_from_discovered" do
406         before do
407           client = stub
408           discoverer = stub
409           ddl = stub
410
411           ddl.stubs(:meta).returns({:timeout => 2})
412
413           discoverer.stubs(:ddl).returns(ddl)
414
415           client.stubs("options=")
416           client.stubs(:collective).returns("mcollective")
417           client.stubs(:discoverer).returns(discoverer)
418
419           Config.instance.stubs(:loadconfig).with("/nonexisting").returns(true)
420           MCollective::Client.stubs(:new).returns(client)
421           Config.instance.stubs(:direct_addressing).returns(true)
422         end
423
424         it "should return a percentage of discovered hosts" do
425           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
426           client.stubs(:discover).returns((1..10).map{|i| i.to_s})
427           client.limit_method = :first
428           client.pick_nodes_from_discovered("20%").should == ["1", "2"]
429         end
430
431         it "should return the same list when a random seed is supplied" do
432           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :limit_seed => 5}})
433           client.stubs(:discover).returns((1..10).map{|i| i.to_s})
434           client.limit_method = :random
435           client.pick_nodes_from_discovered("30%").should == ["3", "7", "8"]
436           client.pick_nodes_from_discovered("30%").should == ["3", "7", "8"]
437           client.pick_nodes_from_discovered("3").should == ["3", "7", "8"]
438           client.pick_nodes_from_discovered("3").should == ["3", "7", "8"]
439         end
440
441         it "should correctly pick a numeric amount of discovered nodes" do
442           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :limit_seed => 5}})
443           client.stubs(:discover).returns((1..10).map{|i| i.to_s})
444           client.limit_method = :first
445           client.pick_nodes_from_discovered(5).should == (1..5).map{|i| i.to_s}
446           client.pick_nodes_from_discovered(5).should == (1..5).map{|i| i.to_s}
447         end
448       end
449
450       describe "#limit_targets=" do
451         before do
452           client = stub
453           discoverer = stub
454           ddl = stub
455
456           ddl.stubs(:meta).returns({:timeout => 2})
457
458           discoverer.stubs(:force_direct_mode?).returns(false)
459           discoverer.stubs(:ddl).returns(ddl)
460           discoverer.stubs(:discovery_method).returns("mc")
461
462           client.stubs("options=")
463           client.stubs(:collective).returns("mcollective")
464           client.stubs(:discoverer).returns(discoverer)
465
466           Config.instance.stubs(:loadconfig).with("/nonexisting").returns(true)
467           MCollective::Client.expects(:new).returns(client)
468           Config.instance.stubs(:direct_addressing).returns(true)
469
470           @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
471         end
472
473         it "should support percentages" do
474           @client.limit_targets = "10%"
475           @client.limit_targets.should == "10%"
476         end
477
478         it "should support integers" do
479           @client.limit_targets = 10
480           @client.limit_targets.should == 10
481           @client.limit_targets = "20"
482           @client.limit_targets.should == 20
483           @client.limit_targets = 1.1
484           @client.limit_targets.should == 1
485           @client.limit_targets = 1.7
486           @client.limit_targets.should == 1
487         end
488
489         it "should not invalid limits to be set" do
490           expect { @client.limit_targets = "a" }.to raise_error(/Invalid/)
491           expect { @client.limit_targets = "%1" }.to raise_error(/Invalid/)
492           expect { @client.limit_targets = "1.1" }.to raise_error(/Invalid/)
493         end
494       end
495
496       describe "#call_agent_batched" do
497         before do
498           @client = stub
499           @discoverer = stub
500           @ddl = stub
501
502           @ddl.stubs(:meta).returns({:timeout => 2})
503
504           @discoverer.stubs(:force_direct_mode?).returns(false)
505           @discoverer.stubs(:ddl).returns(@ddl)
506           @discoverer.stubs(:discovery_method).returns("mc")
507
508           @client.stubs("options=")
509           @client.stubs(:collective).returns("mcollective")
510           @client.stubs(:discoverer).returns(@discoverer)
511
512           Config.instance.stubs(:loadconfig).with("/nonexisting").returns(true)
513           MCollective::Client.expects(:new).returns(@client)
514           Config.instance.stubs(:direct_addressing).returns(true)
515         end
516
517         it "should require direct addressing" do
518           Config.instance.stubs(:direct_addressing).returns(false)
519           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
520
521           expect {
522             client.send(:call_agent_batched, "foo", {}, {}, 1, 1)
523           }.to raise_error("Batched requests requires direct addressing")
524         end
525
526         it "should require that all results be processed" do
527           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
528
529           expect {
530             client.send(:call_agent_batched, "foo", {:process_results => false}, {}, 1, 1)
531           }.to raise_error("Cannot bypass result processing for batched requests")
532         end
533
534         it "should only accept integer batch sizes" do
535           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
536
537           expect {
538             client.send(:call_agent_batched, "foo", {}, {}, "foo", 1)
539           }.to raise_error(/invalid value for Integer/)
540         end
541
542         it "should only accept float sleep times" do
543           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
544
545           expect {
546             client.send(:call_agent_batched, "foo", {}, {}, 1, "foo")
547           }.to raise_error(/invalid value for Float/)
548         end
549
550         it "should batch hosts in the correct size" do
551           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
552
553           client.expects(:new_request).returns("req")
554
555           discovered = mock
556           discovered.stubs(:size).returns(1)
557           discovered.expects(:in_groups_of).with(10).raises("spec pass")
558
559           client.instance_variable_set("@client", @coreclient)
560           @coreclient.stubs(:discover).returns(discovered)
561           @coreclient.stubs(:timeout_for_compound_filter).returns(0)
562
563           expect { client.send(:call_agent_batched, "foo", {}, {}, 10, 1) }.to raise_error("spec pass")
564         end
565
566         it "should force direct requests" do
567           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
568
569           Message.expects(:new).with('req', nil, {:type => :direct_request, :agent => 'foo', :filter => nil, :options => {}, :collective => 'mcollective'}).raises("spec pass")
570           client.expects(:new_request).returns("req")
571
572           client.instance_variable_set("@client", @coreclient)
573           @coreclient.stubs(:discover).returns(["test"])
574           @coreclient.stubs(:timeout_for_compound_filter).returns(0)
575
576           expect { client.send(:call_agent_batched, "foo", {}, {}, 1, 1) }.to raise_error("spec pass")
577         end
578
579         it "should process blocks correctly" do
580           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
581
582           msg = mock
583           msg.expects(:discovered_hosts=).times(10)
584           msg.expects(:create_reqid).returns("823a3419a0975c3facbde121f72ab61f")
585           msg.expects(:requestid=).with("823a3419a0975c3facbde121f72ab61f").times(10)
586
587           stats = {:noresponsefrom => [], :responses => 0, :blocktime => 0, :totaltime => 0, :discoverytime => 0, :requestid => "823a3419a0975c3facbde121f72ab61f"}
588
589           Message.expects(:new).with('req', nil, {:type => :direct_request, :agent => 'foo', :filter => nil, :options => {}, :collective => 'mcollective'}).returns(msg).times(10)
590           client.expects(:new_request).returns("req")
591           client.expects(:sleep).with(1.0).times(9)
592
593           client.instance_variable_set("@client", @coreclient)
594           @coreclient.stubs(:discover).returns([1,2,3,4,5,6,7,8,9,0])
595           @coreclient.expects(:req).with(msg).yields("result").times(10)
596           @coreclient.stubs(:stats).returns stats
597           @coreclient.stubs(:timeout_for_compound_filter).returns(0)
598
599           client.expects(:process_results_with_block).with("foo", "result", instance_of(Proc), nil).times(10)
600
601           result = client.send(:call_agent_batched, "foo", {}, {}, 1, 1) { }
602           result[:requestid].should == "823a3419a0975c3facbde121f72ab61f"
603           result.class.should == Stats
604         end
605
606         it "should return an array of results in array mode" do
607           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
608           client.instance_variable_set("@client", @coreclient)
609
610           msg = mock
611           msg.expects(:discovered_hosts=).times(10)
612           msg.expects(:create_reqid).returns("823a3419a0975c3facbde121f72ab61f")
613           msg.expects(:requestid=).with("823a3419a0975c3facbde121f72ab61f").times(10)
614
615           stats = {:noresponsefrom => [], :responses => 0, :blocktime => 0, :totaltime => 0, :discoverytime => 0, :requestid => "823a3419a0975c3facbde121f72ab61f"}
616
617           Progress.expects(:new).never
618
619           Message.expects(:new).with('req', nil, {:type => :direct_request, :agent => 'foo', :filter => nil, :options => {}, :collective => 'mcollective'}).returns(msg).times(10)
620           client.expects(:new_request).returns("req")
621           client.expects(:sleep).with(1.0).times(9)
622
623           @coreclient.stubs(:discover).returns([1,2,3,4,5,6,7,8,9,0])
624           @coreclient.expects(:req).with(msg).yields("result").times(10)
625           @coreclient.stubs(:stats).returns stats
626           @coreclient.stubs(:timeout_for_compound_filter).returns(0)
627
628           client.expects(:process_results_without_block).with("result", "foo", nil).returns("rspec").times(10)
629
630           client.send(:call_agent_batched, "foo", {}, {}, 1, 1).should == ["rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec"]
631
632           client.stats[:requestid].should == "823a3419a0975c3facbde121f72ab61f"
633         end
634       end
635
636       describe "#batch_sleep_time=" do
637         it "should correctly set the sleep" do
638           Config.instance.stubs(:direct_addressing).returns(true)
639
640           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
641           client.batch_sleep_time = 5
642           client.batch_sleep_time.should == 5
643         end
644
645         it "should only allow batch sleep to be set for direct addressing capable clients" do
646           Config.instance.stubs(:direct_addressing).returns(false)
647           Config.instance.stubs(:loadconfig).with("/nonexisting").returns(true)
648           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
649
650           expect { client.batch_sleep_time = 5 }.to raise_error("Can only set batch sleep time if direct addressing is supported")
651         end
652       end
653
654       describe "#batch_size=" do
655         it "should correctly set the size" do
656           Config.instance.stubs(:direct_addressing).returns(true)
657
658           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
659           client.batch_mode.should == false
660           client.batch_size = 5
661           client.batch_size.should == 5
662           client.batch_mode.should == true
663         end
664
665         it "should only allow batch size to be set for direct addressing capable clients" do
666           Config.instance.stubs(:loadconfig).with("/nonexisting").returns(true)
667           Config.instance.stubs(:direct_addressing).returns(false)
668           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
669
670           expect { client.batch_size = 5 }.to raise_error("Can only set batch size if direct addressing is supported")
671         end
672
673         it "should support disabling batch mode when supplied a batch size of 0" do
674           Config.instance.stubs(:direct_addressing).returns(true)
675
676           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
677           client.batch_size = 5
678           client.batch_mode.should == true
679           client.batch_size = 0
680           client.batch_mode.should == false
681         end
682       end
683
684       describe "#discover" do
685         it "should not accept invalid flags" do
686           Config.instance.stubs(:direct_addressing).returns(true)
687           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
688
689           expect { client.discover(:rspec => :rspec) }.to raise_error("Unknown option rspec passed to discover")
690         end
691
692         it "should reset when :json, :hosts or :nodes are provided" do
693           Config.instance.stubs(:direct_addressing).returns(true)
694           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
695           client.expects(:reset).times(3)
696           client.discover(:hosts => ["one"])
697           client.discover(:nodes => ["one"])
698           client.discover(:json => ["one"])
699         end
700
701         it "should only allow discovery data in direct addressing mode" do
702           Config.instance.stubs(:direct_addressing).returns(false)
703           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
704           client.expects(:reset).once
705
706           expect {
707             client.discover(:nodes => ["one"])
708           }.to raise_error("Can only supply discovery data if direct_addressing is enabled")
709         end
710
711         it "should parse :nodes and :hosts and force direct requests" do
712           Config.instance.stubs(:direct_addressing).returns(true)
713           Helpers.expects(:extract_hosts_from_array).with(["one"]).returns(["one"]).twice
714
715           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
716           client.discover(:nodes => ["one"]).should == ["one"]
717           client.discover(:hosts => ["one"]).should == ["one"]
718           client.instance_variable_get("@force_direct_request").should == true
719           client.instance_variable_get("@discovered_agents").should == ["one"]
720         end
721
722         it "should parse :json and force direct requests" do
723           Config.instance.stubs(:direct_addressing).returns(true)
724           Helpers.expects(:extract_hosts_from_json).with('["one"]').returns(["one"]).once
725
726           client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
727           client.discover(:json => '["one"]').should == ["one"]
728           client.instance_variable_get("@force_direct_request").should == true
729           client.instance_variable_get("@discovered_agents").should == ["one"]
730         end
731
732         it "should not set direct mode for non 'mc' discovery methods" do
733           Config.instance.stubs(:direct_addressing).returns(true)
734
735           client = Client.new("foo", {:options => {:discovery_method => "rspec", :filter => {"identity" => ["foo"], "agent" => []}, :config => "/nonexisting"}})
736           @coreclient.expects(:discover).returns(["foo"])
737
738           client.discover
739           client.instance_variable_get("@discovered_agents").should == ["foo"]
740           client.instance_variable_get("@force_direct_request").should == false
741         end
742
743         it "should force direct mode for non regex identity filters" do
744           Config.instance.stubs(:direct_addressing).returns(true)
745
746           client = Client.new("foo", {:options => {:discovery_method => "mc", :filter => {"identity" => ["foo"], "agent" => []}, :config => "/nonexisting"}})
747           client.discover
748           client.instance_variable_get("@discovered_agents").should == ["foo"]
749           client.instance_variable_get("@force_direct_request").should == true
750         end
751
752         it "should not set direct mode if its disabled" do
753           Config.instance.stubs(:direct_addressing).returns(false)
754
755           client = Client.new("foo", {:options => {:discovery_method => "mc", :filter => {"identity" => ["foo"], "agent" => []}, :config => "/nonexisting"}})
756
757           client.discover
758           client.instance_variable_get("@force_direct_request").should == false
759           client.instance_variable_get("@discovered_agents").should == ["foo"]
760         end
761
762         it "should not set direct mode for regex identities" do
763           Config.instance.stubs(:direct_addressing).returns(false)
764
765           rpcclient = Client.new("foo", {:options => {:filter => {"identity" => ["/foo/"], "agent" => []}, :config => "/nonexisting"}})
766
767           rpcclient.client.expects(:discover).with({'identity' => ['/foo/'], 'agent' => ['foo']}, 2).once.returns(["foo"])
768
769           rpcclient.discover
770           rpcclient.instance_variable_get("@force_direct_request").should == false
771           rpcclient.instance_variable_get("@discovered_agents").should == ["foo"]
772         end
773
774         it "should print status to stderr if in verbose mode" do
775           @stderr.expects(:print).with("Discovering hosts using the mc method for 2 second(s) .... ")
776           @stderr.expects(:puts).with(1)
777
778           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => true, :disctimeout => 2, :stderr => @stderr, :stdout => @stdout}})
779
780           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
781
782           rpcclient.discover
783         end
784
785         it "should not print status to stderr if in nonverbose mode" do
786           @stderr.expects(:print).never
787           @stderr.expects(:puts).never
788
789           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2, :stderr => @stderr, :stdout => @stdout}})
790           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
791
792           rpcclient.discover
793         end
794
795         it "should record the start and end times" do
796           Stats.any_instance.expects(:time_discovery).with(:start)
797           Stats.any_instance.expects(:time_discovery).with(:end)
798
799           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
800           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
801
802           rpcclient.discover
803         end
804
805         it "should discover using limits in :first rpclimit mode given a number" do
806           Config.instance.stubs(:rpclimitmethod).returns(:first)
807           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
808           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2, 1).returns(["foo"])
809
810           rpcclient.limit_targets = 1
811
812           rpcclient.discover
813         end
814
815         it "should not discover using limits in :first rpclimit mode given a string" do
816           Config.instance.stubs(:rpclimitmethod).returns(:first)
817           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
818           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
819           rpcclient.limit_targets = "10%"
820
821           rpcclient.discover
822         end
823
824         it "should not discover using limits when not in :first mode" do
825           Config.instance.stubs(:rpclimitmethod).returns(:random)
826
827           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
828           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
829
830           rpcclient.limit_targets = 1
831           rpcclient.discover
832         end
833
834         it "should ensure force_direct mode is false when doing traditional discovery" do
835           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
836           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
837
838           rpcclient.instance_variable_set("@force_direct_request", true)
839           rpcclient.discover
840           rpcclient.instance_variable_get("@force_direct_request").should == false
841         end
842
843         it "should store discovered nodes in stats" do
844           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
845           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
846
847           rpcclient.discover
848           rpcclient.stats.discovered_nodes.should == ["foo"]
849         end
850
851         it "should save discovered nodes in RPC" do
852           rpcclient = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
853           rpcclient.client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
854
855           RPC.expects(:discovered).with(["foo"]).once
856           rpcclient.discover
857         end
858       end
859     end
860   end
861 end