mcollective-client 1.3.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mcollective-client might be problematic. Click here for more details.

Files changed (103) hide show
  1. data/bin/mc-call-agent +54 -0
  2. data/bin/mco +27 -0
  3. data/lib/mcollective.rb +70 -0
  4. data/lib/mcollective/agents.rb +160 -0
  5. data/lib/mcollective/application.rb +354 -0
  6. data/lib/mcollective/applications.rb +145 -0
  7. data/lib/mcollective/client.rb +292 -0
  8. data/lib/mcollective/config.rb +202 -0
  9. data/lib/mcollective/connector.rb +18 -0
  10. data/lib/mcollective/connector/base.rb +24 -0
  11. data/lib/mcollective/facts.rb +39 -0
  12. data/lib/mcollective/facts/base.rb +86 -0
  13. data/lib/mcollective/log.rb +103 -0
  14. data/lib/mcollective/logger.rb +5 -0
  15. data/lib/mcollective/logger/base.rb +73 -0
  16. data/lib/mcollective/logger/console_logger.rb +61 -0
  17. data/lib/mcollective/logger/file_logger.rb +46 -0
  18. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  19. data/lib/mcollective/matcher.rb +16 -0
  20. data/lib/mcollective/matcher/parser.rb +93 -0
  21. data/lib/mcollective/matcher/scanner.rb +123 -0
  22. data/lib/mcollective/message.rb +201 -0
  23. data/lib/mcollective/monkey_patches.rb +104 -0
  24. data/lib/mcollective/optionparser.rb +164 -0
  25. data/lib/mcollective/pluginmanager.rb +180 -0
  26. data/lib/mcollective/pluginpackager.rb +26 -0
  27. data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
  28. data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
  29. data/lib/mcollective/registration.rb +16 -0
  30. data/lib/mcollective/registration/base.rb +75 -0
  31. data/lib/mcollective/rpc.rb +188 -0
  32. data/lib/mcollective/rpc/actionrunner.rb +142 -0
  33. data/lib/mcollective/rpc/agent.rb +441 -0
  34. data/lib/mcollective/rpc/audit.rb +38 -0
  35. data/lib/mcollective/rpc/client.rb +793 -0
  36. data/lib/mcollective/rpc/ddl.rb +258 -0
  37. data/lib/mcollective/rpc/helpers.rb +339 -0
  38. data/lib/mcollective/rpc/progress.rb +63 -0
  39. data/lib/mcollective/rpc/reply.rb +61 -0
  40. data/lib/mcollective/rpc/request.rb +51 -0
  41. data/lib/mcollective/rpc/result.rb +41 -0
  42. data/lib/mcollective/rpc/stats.rb +185 -0
  43. data/lib/mcollective/runnerstats.rb +90 -0
  44. data/lib/mcollective/security.rb +26 -0
  45. data/lib/mcollective/security/base.rb +237 -0
  46. data/lib/mcollective/shell.rb +87 -0
  47. data/lib/mcollective/ssl.rb +246 -0
  48. data/lib/mcollective/unix_daemon.rb +37 -0
  49. data/lib/mcollective/util.rb +274 -0
  50. data/lib/mcollective/vendor.rb +41 -0
  51. data/lib/mcollective/vendor/require_vendored.rb +2 -0
  52. data/lib/mcollective/windows_daemon.rb +25 -0
  53. data/spec/Rakefile +16 -0
  54. data/spec/fixtures/application/test.rb +7 -0
  55. data/spec/fixtures/test-cert.pem +15 -0
  56. data/spec/fixtures/test-private.pem +15 -0
  57. data/spec/fixtures/test-public.pem +6 -0
  58. data/spec/monkey_patches/instance_variable_defined.rb +7 -0
  59. data/spec/spec.opts +1 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/unit/agents_spec.rb +280 -0
  62. data/spec/unit/application_spec.rb +636 -0
  63. data/spec/unit/applications_spec.rb +155 -0
  64. data/spec/unit/array.rb +30 -0
  65. data/spec/unit/config_spec.rb +148 -0
  66. data/spec/unit/facts/base_spec.rb +118 -0
  67. data/spec/unit/facts_spec.rb +39 -0
  68. data/spec/unit/log_spec.rb +71 -0
  69. data/spec/unit/logger/base_spec.rb +110 -0
  70. data/spec/unit/logger/syslog_logger_spec.rb +86 -0
  71. data/spec/unit/matcher/parser_spec.rb +106 -0
  72. data/spec/unit/matcher/scanner_spec.rb +71 -0
  73. data/spec/unit/message_spec.rb +401 -0
  74. data/spec/unit/optionparser_spec.rb +113 -0
  75. data/spec/unit/pluginmanager_spec.rb +173 -0
  76. data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
  77. data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
  78. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
  79. data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
  80. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
  81. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
  82. data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
  83. data/spec/unit/registration/base_spec.rb +77 -0
  84. data/spec/unit/rpc/actionrunner_spec.rb +213 -0
  85. data/spec/unit/rpc/agent_spec.rb +155 -0
  86. data/spec/unit/rpc/client_spec.rb +523 -0
  87. data/spec/unit/rpc/ddl_spec.rb +388 -0
  88. data/spec/unit/rpc/helpers_spec.rb +55 -0
  89. data/spec/unit/rpc/reply_spec.rb +143 -0
  90. data/spec/unit/rpc/request_spec.rb +115 -0
  91. data/spec/unit/rpc/result_spec.rb +66 -0
  92. data/spec/unit/rpc/stats_spec.rb +288 -0
  93. data/spec/unit/runnerstats_spec.rb +40 -0
  94. data/spec/unit/security/base_spec.rb +279 -0
  95. data/spec/unit/shell_spec.rb +144 -0
  96. data/spec/unit/ssl_spec.rb +244 -0
  97. data/spec/unit/symbol.rb +11 -0
  98. data/spec/unit/unix_daemon.rb +41 -0
  99. data/spec/unit/util_spec.rb +342 -0
  100. data/spec/unit/vendor_spec.rb +34 -0
  101. data/spec/unit/windows_daemon.rb +43 -0
  102. data/spec/windows_spec.opts +1 -0
  103. metadata +242 -0
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ module RPC
7
+ describe ActionRunner do
8
+ before(:each) do
9
+ @req = mock
10
+ @req.stubs(:agent).returns("spectester")
11
+ @req.stubs(:action).returns("tester")
12
+
13
+ command = "/bin/echo 1"
14
+
15
+ @runner = ActionRunner.new(command, @req, :json)
16
+ end
17
+
18
+ describe "#initialize" do
19
+ it "should set command" do
20
+ @runner.command.should == "/bin/echo 1"
21
+ end
22
+
23
+ it "should set agent" do
24
+ @runner.agent.should == "spectester"
25
+ end
26
+
27
+ it "should set action" do
28
+ @runner.action.should == "tester"
29
+ end
30
+
31
+ it "should set format" do
32
+ @runner.format.should == :json
33
+ end
34
+
35
+ it "should set request" do
36
+ @runner.request.should == @req
37
+ end
38
+
39
+ it "should set stdout" do
40
+ @runner.stdout.should == ""
41
+ end
42
+
43
+ it "should set stderr" do
44
+ @runner.stderr.should == ""
45
+ end
46
+
47
+ it "should set the command via path_to_command" do
48
+ ActionRunner.any_instance.expects(:path_to_command).with("rspec").once
49
+ ActionRunner.new("rspec", @req, :json)
50
+ end
51
+ end
52
+
53
+ describe "#shell" do
54
+ it "should create a shell instance with correct settings" do
55
+ s = @runner.shell("test", "infile", "outfile")
56
+
57
+ s.command.should == "test infile outfile"
58
+ s.cwd.should == Dir.tmpdir
59
+ s.stdout.should == ""
60
+ s.stderr.should == ""
61
+ s.environment["MCOLLECTIVE_REQUEST_FILE"].should == "infile"
62
+ s.environment["MCOLLECTIVE_REPLY_FILE"].should == "outfile"
63
+ end
64
+ end
65
+
66
+ describe "#load_results" do
67
+ it "should call the correct format loader" do
68
+ req = mock
69
+ req.expects(:agent).returns("spectester")
70
+ req.expects(:action).returns("tester")
71
+
72
+ runner = ActionRunner.new("/bin/echo 1", req, :foo)
73
+ runner.expects("load_foo_results").returns({:foo => :bar})
74
+ runner.load_results("/dev/null").should == {:foo => :bar}
75
+ end
76
+
77
+ it "should set all keys to Symbol" do
78
+ data = {"foo" => "bar", "bar" => "baz"}
79
+ Tempfile.open("mcollective_test", Dir.tmpdir) do |f|
80
+ f.puts data.to_json
81
+ f.close
82
+
83
+ results = @runner.load_results(f.path)
84
+ results.should == {:foo => "bar", :bar => "baz"}
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#load_json_results" do
90
+ it "should load data from a file" do
91
+ Tempfile.open("mcollective_test", Dir.tmpdir) do |f|
92
+ f.puts '{"foo":"bar","bar":"baz"}'
93
+ f.close
94
+
95
+ @runner.load_json_results(f.path).should == {"foo" => "bar", "bar" => "baz"}
96
+ end
97
+
98
+ end
99
+
100
+ it "should return empty data on JSON parse error" do
101
+ @runner.load_json_results("/dev/null").should == {}
102
+ end
103
+
104
+ it "should return empty data for missing files" do
105
+ @runner.load_json_results("/nonexisting").should == {}
106
+ end
107
+
108
+ it "should load complex data correctly" do
109
+ data = {"foo" => "bar", "bar" => {"one" => "two"}}
110
+ Tempfile.open("mcollective_test", Dir.tmpdir) do |f|
111
+ f.puts data.to_json
112
+ f.close
113
+
114
+ @runner.load_json_results(f.path).should == data
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ describe "#saverequest" do
121
+ it "should call the correct format serializer" do
122
+ req = mock
123
+ req.expects(:agent).returns("spectester")
124
+ req.expects(:action).returns("tester")
125
+
126
+ runner = ActionRunner.new("/bin/echo 1", req, :foo)
127
+
128
+ runner.expects("save_foo_request").with(req).returns('{"foo":"bar"}')
129
+
130
+ runner.saverequest(req)
131
+ end
132
+
133
+ it "should save to a temp file" do
134
+ @req.expects(:to_json).returns({:foo => "bar"}.to_json)
135
+ fname = @runner.saverequest(@req).path
136
+
137
+ JSON.load(File.read(fname)).should == {"foo" => "bar"}
138
+ File.dirname(fname).should == Dir.tmpdir
139
+ end
140
+ end
141
+
142
+ describe "#save_json_request" do
143
+ it "should return correct json data" do
144
+ @req.expects(:to_json).returns({:foo => "bar"}.to_json)
145
+ @runner.save_json_request(@req).should == '{"foo":"bar"}'
146
+ end
147
+ end
148
+
149
+ describe "#canrun?" do
150
+ it "should correctly report executables" do
151
+ if Util.windows?
152
+ @runner.canrun?(File.join(ENV['SystemRoot'], "explorer.exe")).should == true
153
+ else
154
+ @runner.canrun?("/bin/true").should == true
155
+ end
156
+ end
157
+
158
+ it "should detect missing files" do
159
+ @runner.canrun?("/nonexisting").should == false
160
+ end
161
+ end
162
+
163
+ describe "#to_s" do
164
+ it "should return correct data" do
165
+ @runner.to_s.should == "spectester#tester command: /bin/echo 1"
166
+ end
167
+ end
168
+
169
+ describe "#tempfile" do
170
+ it "should return a TempFile" do
171
+ @runner.tempfile("foo").class.should == Tempfile
172
+ end
173
+
174
+ it "should contain the prefix in its name" do
175
+ @runner.tempfile("foo").path.should match(/foo/)
176
+ end
177
+ end
178
+
179
+ describe "#path_to_command" do
180
+ it "should return the command if it starts with separator" do
181
+ command = "#{File::SEPARATOR}rspec"
182
+
183
+ runner = ActionRunner.new(command , @req, :json)
184
+ runner.path_to_command(command).should == command
185
+ end
186
+
187
+ it "should find the first match in the libdir" do
188
+ Config.any_instance.expects(:libdir).returns(["#{File::SEPARATOR}libdir1", "#{File::SEPARATOR}libdir2"])
189
+
190
+ action_in_first_dir = File.join(File::SEPARATOR, "libdir1", "agent", "spectester", "action.sh")
191
+ action_in_last_dir = File.join(File::SEPARATOR, "libdir2", "agent", "spectester", "action.sh")
192
+
193
+ File.expects("exist?").with(action_in_first_dir).returns(true)
194
+ File.expects("exist?").with(action_in_last_dir).never
195
+
196
+ ActionRunner.new("action.sh", @req, :json).command.should == action_in_first_dir
197
+ end
198
+
199
+ it "should find the match even in the last libdir" do
200
+ Config.any_instance.expects(:libdir).returns(["#{File::SEPARATOR}libdir1", "#{File::SEPARATOR}libdir2"])
201
+
202
+ action_in_first_dir = File.join(File::SEPARATOR, "libdir1", "agent", "spectester", "action.sh")
203
+ action_in_last_dir = File.join(File::SEPARATOR, "libdir2", "agent", "spectester", "action.sh")
204
+
205
+ File.expects("exist?").with(action_in_first_dir).returns(false)
206
+ File.expects("exist?").with(action_in_last_dir).returns(true)
207
+
208
+ ActionRunner.new("action.sh", @req, :json).command.should == action_in_last_dir
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ module RPC
7
+ describe Agent do
8
+ before do
9
+ @agent = Agent.new
10
+ @agent.reply = {}
11
+ @agent.request = {}
12
+ end
13
+
14
+ describe "#run" do
15
+ before do
16
+ @status = mock
17
+ @status.stubs(:exitstatus).returns(0)
18
+ @shell = mock
19
+ @shell.stubs(:runcommand)
20
+ @shell.stubs(:status).returns(@status)
21
+ end
22
+
23
+ it "should accept stderr and stdout and force them to be strings" do
24
+ Shell.expects(:new).with("rspec", {:stderr => "", :stdout => ""}).returns(@shell)
25
+ @agent.send(:run, "rspec", {:stderr => :err, :stdout => :out})
26
+ @agent.reply[:err].should == ""
27
+ @agent.reply[:out].should == ""
28
+ end
29
+
30
+ it "should accept existing variables for stdout and stderr and fail if they dont support <<" do
31
+ @agent.reply[:err] = "err"
32
+ @agent.reply[:out] = "out"
33
+
34
+ Shell.expects(:new).with("rspec", {:stderr => "err", :stdout => "out"}).returns(@shell)
35
+ @agent.send(:run, "rspec", {:stderr => @agent.reply[:err], :stdout => @agent.reply[:out]})
36
+ @agent.reply[:err].should == "err"
37
+ @agent.reply[:out].should == "out"
38
+
39
+ @agent.reply.expects("fail!").with("stderr should support << while calling run(rspec)").raises("stderr fail")
40
+ expect { @agent.send(:run, "rspec", {:stderr => nil, :stdout => ""}) }.to raise_error("stderr fail")
41
+
42
+ @agent.reply.expects("fail!").with("stdout should support << while calling run(rspec)").raises("stdout fail")
43
+ expect { @agent.send(:run, "rspec", {:stderr => "", :stdout => nil}) }.to raise_error("stdout fail")
44
+ end
45
+
46
+ it "should set stdin, cwd and environment if supplied" do
47
+ Shell.expects(:new).with("rspec", {:stdin => "stdin", :cwd => "cwd", :environment => "env"}).returns(@shell)
48
+ @agent.send(:run, "rspec", {:stdin => "stdin", :cwd => "cwd", :environment => "env"})
49
+ end
50
+
51
+ it "should ignore unknown options" do
52
+ Shell.expects(:new).with("rspec", {}).returns(@shell)
53
+ @agent.send(:run, "rspec", {:rspec => "rspec"})
54
+ end
55
+
56
+ it "should chomp strings if configured to do so" do
57
+ Shell.expects(:new).with("rspec", {:stderr => 'err', :stdout => 'out'}).returns(@shell)
58
+
59
+ @agent.reply[:err] = "err"
60
+ @agent.reply[:out] = "out"
61
+
62
+ @agent.reply[:err].expects("chomp!")
63
+ @agent.reply[:out].expects("chomp!")
64
+
65
+ @agent.send(:run, "rspec", {:chomp => true, :stdout => @agent.reply[:out], :stderr => @agent.reply[:err]})
66
+ end
67
+
68
+ it "should return the exitstatus" do
69
+ Shell.expects(:new).with("rspec", {}).returns(@shell)
70
+ @agent.send(:run, "rspec", {}).should == 0
71
+ end
72
+
73
+ it "should handle nil from the shell handler" do
74
+ @shell.expects(:status).returns(nil)
75
+ Shell.expects(:new).with("rspec", {}).returns(@shell)
76
+ @agent.send(:run, "rspec", {}).should == -1
77
+ end
78
+ end
79
+
80
+ describe "#validate" do
81
+ it "should detect missing data" do
82
+ @agent.request = {}
83
+ expect { @agent.send(:validate, :foo, String) }.to raise_error(MissingRPCData, "please supply a foo argument")
84
+ end
85
+
86
+ it "should support regular expressions" do
87
+ @agent.request = {:foo => "this is a test, 123"}
88
+
89
+ expect { @agent.send(:validate, :foo, /foo/) }.to raise_error(InvalidRPCData, /foo should match/)
90
+ @agent.send(:validate, :foo, /is a test, \d\d\d$/)
91
+ end
92
+
93
+ it "should support type checking" do
94
+ @agent.request = {:str => "foo"}
95
+
96
+ expect { @agent.send(:validate, :str, Numeric) }.to raise_error(InvalidRPCData, /str should be a Numeric/)
97
+ @agent.send(:validate, :str, String)
98
+ end
99
+
100
+ it "should correctly validate ipv4 addresses" do
101
+ @agent.request = {:goodip4 => "1.1.1.1",
102
+ :badip4 => "300.300.300.300"}
103
+
104
+ expect { @agent.send(:validate, :badip4, :ipv4address) }.to raise_error(InvalidRPCData, /badip4 should be an ipv4 address/)
105
+ @agent.send(:validate, :goodip4, :ipv4address)
106
+ end
107
+
108
+ it "should correctly validate ipv6 addresses" do
109
+ @agent.request = {:goodip6 => "2a00:1450:8006::93",
110
+ :badip6 => "300.300.300.300"}
111
+
112
+ expect { @agent.send(:validate, :badip6, :ipv6address) }.to raise_error(InvalidRPCData, /badip6 should be an ipv6 address/)
113
+ @agent.send(:validate, :goodip6, :ipv6address)
114
+ end
115
+
116
+ it "should correctly validate boolean data" do
117
+ @agent.request = {:true => true, :false => false, :string => "foo", :number => 1}
118
+
119
+ @agent.send(:validate, :true, :boolean)
120
+ @agent.send(:validate, :false, :boolean)
121
+ expect { @agent.send(:validate, :string, :boolean) }.to raise_error(InvalidRPCData)
122
+ expect { @agent.send(:validate, :number, :boolean) }.to raise_error(InvalidRPCData)
123
+ end
124
+
125
+ it "should correctly validate list data" do
126
+ @agent.request = {:str => "foo"}
127
+ expect { @agent.send(:validate, :str, ["bar", "baz"]) }.to raise_error(InvalidRPCData, /str should be one of bar, baz/)
128
+
129
+ @agent.request = {:str => "foo"}
130
+ expect { @agent.send(:validate, :str, ["bar", "baz", "foo"]) }
131
+ @agent.send(:validate, :str, ["bar", "baz", "foo"])
132
+ end
133
+
134
+ it "should correctly identify characters that are not shell safe" do
135
+ @agent.request = {:backtick => 'foo`bar',
136
+ :semicolon => 'foo;bar',
137
+ :dollar => 'foo$(bar)',
138
+ :pipe => 'foo|bar',
139
+ :redirto => 'foo>bar',
140
+ :inputfrom => 'foo<bar',
141
+ :good => 'foo bar baz'}
142
+
143
+ expect { @agent.send(:validate, :backtick, :shellsafe) }.to raise_error(InvalidRPCData, /backtick should not have ` in it/)
144
+ expect { @agent.send(:validate, :semicolon, :shellsafe) }.to raise_error(InvalidRPCData, /semicolon should not have ; in it/)
145
+ expect { @agent.send(:validate, :dollar, :shellsafe) }.to raise_error(InvalidRPCData, /dollar should not have \$ in it/)
146
+ expect { @agent.send(:validate, :pipe, :shellsafe) }.to raise_error(InvalidRPCData, /pipe should not have \| in it/)
147
+ expect { @agent.send(:validate, :redirto, :shellsafe) }.to raise_error(InvalidRPCData, /redirto should not have > in it/)
148
+ expect { @agent.send(:validate, :inputfrom, :shellsafe) }.to raise_error(InvalidRPCData, /inputfrom should not have \< in it/)
149
+
150
+ @agent.send(:validate, :good, :shellsafe)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,523 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ module RPC
7
+ describe Client do
8
+ describe "#limit_method" do
9
+ before do
10
+ client = stub
11
+
12
+ client.stubs("options=")
13
+ client.stubs(:collective).returns("mcollective")
14
+
15
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
16
+ MCollective::Client.expects(:new).returns(client)
17
+ Config.any_instance.stubs(:direct_addressing).returns(true)
18
+
19
+ @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
20
+ end
21
+
22
+ it "should force strings to symbols" do
23
+ @client.limit_method = "first"
24
+ @client.limit_method.should == :first
25
+ end
26
+
27
+ it "should only allow valid methods" do
28
+ @client.limit_method = :first
29
+ @client.limit_method.should == :first
30
+ @client.limit_method = :random
31
+ @client.limit_method.should == :random
32
+
33
+ expect { @client.limit_method = :fail }.to raise_error(/Unknown/)
34
+ expect { @client.limit_method = "fail" }.to raise_error(/Unknown/)
35
+ end
36
+ end
37
+
38
+ describe "#method_missing" do
39
+ before do
40
+ client = stub
41
+ client.stubs("options=")
42
+ client.stubs(:collective).returns("mcollective")
43
+ MCollective::Client.stubs(:new).returns(client)
44
+
45
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
46
+ end
47
+
48
+ it "should reset the stats" do
49
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
50
+ client.stubs(:call_agent)
51
+
52
+ Stats.any_instance.expects(:reset).once
53
+ client.foo
54
+ end
55
+
56
+ it "should validate the request against the ddl" do
57
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
58
+
59
+ client.stubs(:call_agent)
60
+
61
+ ddl = mock
62
+ ddl.expects(:validate_request).with("rspec", {:arg => :val}).raises("validation failed")
63
+ client.instance_variable_set("@ddl", ddl)
64
+
65
+ expect { client.rspec(:arg => :val) }.to raise_error("validation failed")
66
+ end
67
+
68
+ it "should support limited targets" do
69
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
70
+ client.limit_targets = 10
71
+
72
+ client.expects(:pick_nodes_from_discovered).with(10).returns(["one", "two"])
73
+ client.expects(:custom_request).with("foo", {}, ["one", "two"], {"identity" => /^(one|two)$/}).once
74
+
75
+ client.foo
76
+ end
77
+
78
+ describe "batch mode" do
79
+ before do
80
+ Config.any_instance.stubs(:direct_addressing).returns(true)
81
+ @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
82
+ end
83
+
84
+ it "should support global batch_size" do
85
+ @client.batch_size = 10
86
+ @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 1)
87
+ @client.rspec
88
+ end
89
+
90
+ it "should support custom batch_size" do
91
+ @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 1)
92
+ @client.rspec :batch_size => 10
93
+ end
94
+
95
+ it "should allow supplied batch_size override global one" do
96
+ @client.batch_size = 10
97
+ @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 20, 1)
98
+ @client.rspec :batch_size => 20
99
+ end
100
+
101
+ it "should support global batch_sleep_time" do
102
+ @client.batch_size = 10
103
+ @client.batch_sleep_time = 20
104
+ @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 20)
105
+ @client.rspec
106
+ end
107
+
108
+ it "should support custom batch_sleep_time" do
109
+ @client.batch_size = 10
110
+ @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 20)
111
+ @client.rspec :batch_sleep_time => 20
112
+ end
113
+
114
+ it "should allow supplied batch_sleep_time override global one" do
115
+ @client.batch_size = 10
116
+ @client.batch_sleep_time = 10
117
+ @client.expects(:call_agent_batched).with("rspec", {}, @client.options, 10, 20)
118
+ @client.rspec :batch_sleep_time => 20
119
+ end
120
+ end
121
+
122
+ it "should support normal calls" do
123
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
124
+
125
+ client.expects(:call_agent).with("foo", {}, client.options, :auto).once
126
+
127
+ client.foo
128
+ end
129
+ end
130
+
131
+ describe "#limit_targets=" do
132
+ before do
133
+ client = stub
134
+
135
+ client.stubs("options=")
136
+ client.stubs(:collective).returns("mcollective")
137
+
138
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
139
+ MCollective::Client.expects(:new).returns(client)
140
+ Config.any_instance.stubs(:direct_addressing).returns(true)
141
+
142
+ @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
143
+ end
144
+
145
+ it "should support percentages" do
146
+ @client.limit_targets = "10%"
147
+ @client.limit_targets.should == "10%"
148
+ end
149
+
150
+ it "should support integers" do
151
+ @client.limit_targets = 10
152
+ @client.limit_targets.should == 10
153
+ @client.limit_targets = "20"
154
+ @client.limit_targets.should == 20
155
+ @client.limit_targets = 1.1
156
+ @client.limit_targets.should == 1
157
+ @client.limit_targets = 1.7
158
+ @client.limit_targets.should == 1
159
+ end
160
+
161
+ it "should not invalid limits to be set" do
162
+ expect { @client.limit_targets = "a" }.to raise_error /Invalid/
163
+ expect { @client.limit_targets = "%1" }.to raise_error /Invalid/
164
+ expect { @client.limit_targets = "1.1" }.to raise_error /Invalid/
165
+ end
166
+ end
167
+
168
+ describe "#call_agent_batched" do
169
+ before do
170
+ @client = stub
171
+
172
+ @client.stubs("options=")
173
+ @client.stubs(:collective).returns("mcollective")
174
+
175
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
176
+ MCollective::Client.expects(:new).returns(@client)
177
+ Config.any_instance.stubs(:direct_addressing).returns(true)
178
+ end
179
+
180
+ it "should require direct addressing" do
181
+ Config.any_instance.stubs(:direct_addressing).returns(false)
182
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
183
+
184
+ expect {
185
+ client.send(:call_agent_batched, "foo", {}, {}, 1, 1)
186
+ }.to raise_error("Batched requests requires direct addressing")
187
+ end
188
+
189
+ it "should require that all results be processed" do
190
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
191
+
192
+ expect {
193
+ client.send(:call_agent_batched, "foo", {:process_results => false}, {}, 1, 1)
194
+ }.to raise_error("Cannot bypass result processing for batched requests")
195
+ end
196
+
197
+ it "should only accept integer batch sizes" do
198
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
199
+
200
+ expect {
201
+ client.send(:call_agent_batched, "foo", {}, {}, "foo", 1)
202
+ }.to raise_error(/invalid value for Integer/)
203
+ end
204
+
205
+ it "should only accept float sleep times" do
206
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
207
+
208
+ expect {
209
+ client.send(:call_agent_batched, "foo", {}, {}, 1, "foo")
210
+ }.to raise_error(/invalid value for Float/)
211
+ end
212
+
213
+ it "should batch hosts in the correct size" do
214
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
215
+
216
+ client.expects(:new_request).returns("req")
217
+
218
+ discovered = mock
219
+ discovered.stubs(:size).returns(1)
220
+ discovered.expects(:in_groups_of).with(10).raises("spec pass")
221
+ @client.stubs(:discover).returns(discovered)
222
+
223
+ expect { client.send(:call_agent_batched, "foo", {}, {}, 10, 1) }.to raise_error("spec pass")
224
+ end
225
+
226
+ it "should force direct requests" do
227
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
228
+
229
+ Message.expects(:new).with('req', nil, {:type => :direct_request, :agent => 'foo', :filter => nil, :options => {}, :collective => 'mcollective'}).raises("spec pass")
230
+ client.expects(:new_request).returns("req")
231
+
232
+ @client.stubs(:discover).returns(["test"])
233
+
234
+ expect { client.send(:call_agent_batched, "foo", {}, {}, 1, 1) }.to raise_error("spec pass")
235
+ end
236
+
237
+ it "should process blocks correctly" do
238
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
239
+
240
+ msg = mock
241
+ msg.expects(:discovered_hosts=).times(10)
242
+
243
+ stats = {:noresponsefrom => [], :responses => 0, :blocktime => 0, :totaltime => 0, :discoverytime => 0}
244
+
245
+ Message.expects(:new).with('req', nil, {:type => :direct_request, :agent => 'foo', :filter => nil, :options => {}, :collective => 'mcollective'}).returns(msg).times(10)
246
+ client.expects(:new_request).returns("req")
247
+ client.expects(:sleep).with(1.0).times(9)
248
+
249
+ @client.stubs(:discover).returns([1,2,3,4,5,6,7,8,9,0])
250
+ @client.expects(:req).with(msg).yields("result").times(10)
251
+ @client.stubs(:stats).returns stats
252
+
253
+ client.expects(:process_results_with_block).with("foo", "result", instance_of(Proc)).times(10)
254
+
255
+ result = client.send(:call_agent_batched, "foo", {}, {}, 1, 1) { }
256
+ result.class.should == Stats
257
+ end
258
+
259
+ it "should return an array of results in array mode" do
260
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stderr => StringIO.new}})
261
+
262
+ msg = mock
263
+ msg.expects(:discovered_hosts=).times(10)
264
+
265
+ stats = {:noresponsefrom => [], :responses => 0, :blocktime => 0, :totaltime => 0, :discoverytime => 0}
266
+
267
+ Progress.expects(:new).never
268
+
269
+ Message.expects(:new).with('req', nil, {:type => :direct_request, :agent => 'foo', :filter => nil, :options => {}, :collective => 'mcollective'}).returns(msg).times(10)
270
+ client.expects(:new_request).returns("req")
271
+ client.expects(:sleep).with(1.0).times(9)
272
+
273
+ @client.stubs(:discover).returns([1,2,3,4,5,6,7,8,9,0])
274
+ @client.expects(:req).with(msg).yields("result").times(10)
275
+ @client.stubs(:stats).returns stats
276
+
277
+ client.expects(:process_results_without_block).with("result", "foo").returns("rspec").times(10)
278
+
279
+ client.send(:call_agent_batched, "foo", {}, {}, 1, 1).should == ["rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec", "rspec"]
280
+ end
281
+ end
282
+
283
+ describe "#batch_sleep_time=" do
284
+ before do
285
+ @client = stub
286
+
287
+ @client.stubs("options=")
288
+ @client.stubs(:collective).returns("mcollective")
289
+
290
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
291
+ MCollective::Client.expects(:new).returns(@client)
292
+ end
293
+
294
+ it "should correctly set the sleep" do
295
+ Config.any_instance.stubs(:direct_addressing).returns(true)
296
+
297
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
298
+ client.batch_sleep_time = 5
299
+ client.batch_sleep_time.should == 5
300
+ end
301
+
302
+ it "should only allow batch sleep to be set for direct addressing capable clients" do
303
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
304
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
305
+
306
+ expect { client.batch_sleep_time = 5 }.to raise_error("Can only set batch sleep time if direct addressing is supported")
307
+ end
308
+ end
309
+
310
+ describe "#batch_size=" do
311
+ before do
312
+ @client = stub
313
+
314
+ @client.stubs("options=")
315
+ @client.stubs(:collective).returns("mcollective")
316
+
317
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
318
+ MCollective::Client.expects(:new).returns(@client)
319
+ end
320
+
321
+ it "should correctly set the size" do
322
+ Config.any_instance.stubs(:direct_addressing).returns(true)
323
+
324
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
325
+ client.batch_mode.should == false
326
+ client.batch_size = 5
327
+ client.batch_size.should == 5
328
+ client.batch_mode.should == true
329
+ end
330
+
331
+ it "should only allow batch size to be set for direct addressing capable clients" do
332
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
333
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
334
+
335
+ expect { client.batch_size = 5 }.to raise_error("Can only set batch size if direct addressing is supported")
336
+ end
337
+
338
+ it "should support disabling batch mode when supplied a batch size of 0" do
339
+ Config.any_instance.stubs(:direct_addressing).returns(true)
340
+
341
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
342
+ client.batch_size = 5
343
+ client.batch_mode.should == true
344
+ client.batch_size = 0
345
+ client.batch_mode.should == false
346
+ end
347
+ end
348
+
349
+ describe "#discover" do
350
+ before do
351
+ @client = stub
352
+
353
+ @client.stubs("options=")
354
+ @client.stubs(:collective).returns("mcollective")
355
+
356
+ @stderr = stub
357
+ @stdout = stub
358
+
359
+ Config.any_instance.stubs(:loadconfig).with("/nonexisting").returns(true)
360
+ MCollective::Client.expects(:new).returns(@client)
361
+ end
362
+
363
+ it "should not accept invalid flags" do
364
+ Config.any_instance.stubs(:direct_addressing).returns(true)
365
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
366
+
367
+ expect { client.discover(:rspec => :rspec) }.to raise_error("Unknown option rspec passed to discover")
368
+ end
369
+
370
+ it "should reset when :json, :hosts or :nodes are provided" do
371
+ Config.any_instance.stubs(:direct_addressing).returns(true)
372
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
373
+ client.expects(:reset).times(3)
374
+ client.discover(:hosts => ["one"])
375
+ client.discover(:nodes => ["one"])
376
+ client.discover(:json => ["one"])
377
+ end
378
+
379
+ it "should only allow discovery data in direct addressing mode" do
380
+ Config.any_instance.stubs(:direct_addressing).returns(false)
381
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
382
+ client.expects(:reset).once
383
+
384
+ expect {
385
+ client.discover(:nodes => ["one"])
386
+ }.to raise_error("Can only supply discovery data if direct_addressing is enabled")
387
+ end
388
+
389
+ it "should parse :nodes and :hosts and force direct requests" do
390
+ Config.any_instance.stubs(:direct_addressing).returns(true)
391
+ Helpers.expects(:extract_hosts_from_array).with(["one"]).returns(["one"]).twice
392
+
393
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
394
+ client.discover(:nodes => ["one"]).should == ["one"]
395
+ client.discover(:hosts => ["one"]).should == ["one"]
396
+ client.instance_variable_get("@force_direct_request").should == true
397
+ client.instance_variable_get("@discovered_agents").should == ["one"]
398
+ end
399
+
400
+ it "should parse :json and force direct requests" do
401
+ Config.any_instance.stubs(:direct_addressing).returns(true)
402
+ Helpers.expects(:extract_hosts_from_json).with('["one"]').returns(["one"]).once
403
+
404
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting"}})
405
+ client.discover(:json => '["one"]').should == ["one"]
406
+ client.instance_variable_get("@force_direct_request").should == true
407
+ client.instance_variable_get("@discovered_agents").should == ["one"]
408
+ end
409
+
410
+ it "should force direct mode for non regex identity filters" do
411
+ Config.any_instance.stubs(:direct_addressing).returns(true)
412
+
413
+ client = Client.new("foo", {:options => {:filter => {"identity" => ["foo"], "agent" => []}, :config => "/nonexisting"}})
414
+ client.discover
415
+ client.instance_variable_get("@discovered_agents").should == ["foo"]
416
+ client.instance_variable_get("@force_direct_request").should == true
417
+ end
418
+
419
+ it "should not set direct mode if its disabled" do
420
+ Config.any_instance.stubs(:direct_addressing).returns(false)
421
+
422
+ client = Client.new("foo", {:options => {:filter => {"identity" => ["foo"], "agent" => []}, :config => "/nonexisting"}})
423
+
424
+ client.discover
425
+ client.instance_variable_get("@force_direct_request").should == false
426
+ client.instance_variable_get("@discovered_agents").should == ["foo"]
427
+ end
428
+
429
+ it "should not set direct mode for regex identities" do
430
+ Config.any_instance.stubs(:direct_addressing).returns(false)
431
+
432
+ @client.expects(:discover).with({'identity' => ['/foo/'], 'agent' => ['foo']}, nil).once.returns(["foo"])
433
+ client = Client.new("foo", {:options => {:filter => {"identity" => ["/foo/"], "agent" => []}, :config => "/nonexisting"}})
434
+
435
+ client.discover
436
+ client.instance_variable_get("@force_direct_request").should == false
437
+ client.instance_variable_get("@discovered_agents").should == ["foo"]
438
+ end
439
+
440
+ it "should print status to stderr if in verbose mode" do
441
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
442
+ @stderr.expects(:print).with("Determining the amount of hosts matching filter for 2 seconds .... ")
443
+ @stderr.expects(:puts).with(1)
444
+
445
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => true, :disctimeout => 2, :stderr => @stderr, :stdout => @stdout}})
446
+ client.discover
447
+ end
448
+
449
+ it "should not print status to stderr if in verbose mode" do
450
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
451
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2, :stderr => @stderr, :stdout => @stdout}})
452
+
453
+ @stderr.expects(:print).never
454
+ @stderr.expects(:puts).never
455
+
456
+ client.discover
457
+ end
458
+
459
+ it "should record the start and end times" do
460
+ Stats.any_instance.expects(:time_discovery).with(:start)
461
+ Stats.any_instance.expects(:time_discovery).with(:end)
462
+
463
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
464
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
465
+
466
+ client.discover
467
+ end
468
+
469
+ it "should discover using limits in :first rpclimit mode given a number" do
470
+ Config.any_instance.stubs(:rpclimitmethod).returns(:first)
471
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2, 1).returns(["foo"])
472
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
473
+ client.limit_targets = 1
474
+
475
+ client.discover
476
+ end
477
+
478
+ it "should not discover using limits in :first rpclimit mode given a string" do
479
+ Config.any_instance.stubs(:rpclimitmethod).returns(:first)
480
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
481
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
482
+ client.limit_targets = "10%"
483
+
484
+ client.discover
485
+ end
486
+
487
+ it "should not discover using limits when not in :first mode" do
488
+ Config.any_instance.stubs(:rpclimitmethod).returns(:random)
489
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
490
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
491
+ client.limit_targets = 1
492
+
493
+ client.discover
494
+ end
495
+
496
+ it "should ensure force_direct mode is false when doing traditional discovery" do
497
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
498
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
499
+
500
+ client.instance_variable_set("@force_direct_request", true)
501
+ client.discover
502
+ client.instance_variable_get("@force_direct_request").should == false
503
+ end
504
+
505
+ it "should store discovered nodes in stats" do
506
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
507
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
508
+
509
+ client.discover
510
+ client.stats.discovered_nodes.should == ["foo"]
511
+ end
512
+
513
+ it "should save discovered nodes in RPC" do
514
+ @client.expects(:discover).with({'identity' => [], 'compound' => [], 'fact' => [], 'agent' => ['foo'], 'cf_class' => []}, 2).returns(["foo"])
515
+ client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :verbose => false, :disctimeout => 2}})
516
+
517
+ RPC.expects(:discovered).with(["foo"]).once
518
+ client.discover
519
+ end
520
+ end
521
+ end
522
+ end
523
+ end