mcollective-client 2.10.6 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -59,7 +59,7 @@ module MCollective
59
59
 
60
60
  MCollective::Vendor.load_vendored
61
61
 
62
- VERSION="2.10.6"
62
+ VERSION="2.11.0"
63
63
 
64
64
  def self.version
65
65
  VERSION
@@ -10,7 +10,7 @@ module MCollective
10
10
  require 'pp'
11
11
 
12
12
  def audit_request(request, connection)
13
- logfile = Config.instance.pluginconf["rpcaudit.logfile"] || "/var/log/mcollective-audit.log"
13
+ logfile = Config.instance.pluginconf["rpcaudit.logfile"] || "/var/log/puppetlabs/mcollective/mcollective-audit.log"
14
14
 
15
15
  now = Time.now
16
16
  # Already told timezone to be in UTC so we don't look it up again
@@ -66,10 +66,7 @@ module MCollective
66
66
  # responses and doesn't execute any passed in code blocks for responses
67
67
  def sendreq(msg, agent, filter = {})
68
68
  request = createreq(msg, agent, filter)
69
-
70
- Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}")
71
-
72
- request.publish
69
+ publish(request)
73
70
  request.requestid
74
71
  end
75
72
 
@@ -224,14 +221,18 @@ module MCollective
224
221
  Log.debug("Starting publishing with publish timeout of #{publish_timeout}")
225
222
  begin
226
223
  Timeout.timeout(publish_timeout) do
227
- Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}")
228
- request.publish
224
+ publish(request)
229
225
  end
230
226
  rescue Timeout::Error => e
231
227
  Log.warn("Could not publish all messages. Publishing timed out.")
232
228
  end
233
229
  end
234
230
 
231
+ def publish(request)
232
+ Log.info("Sending request #{request.requestid} for agent '#{request.agent}' with ttl #{request.ttl} in collective '#{request.collective}'")
233
+ request.publish
234
+ end
235
+
235
236
  # Starts the response receiver routine
236
237
  # Expected to return the amount of received responses.
237
238
  def start_receiver(requestid, waitfor, timeout, &block)
@@ -166,13 +166,53 @@ module MCollective
166
166
  return unless input
167
167
 
168
168
  input.keys.each do |key|
169
- if !arguments.include?(key) && !input[key][:default].nil? && !input[key][:optional]
169
+ if key.is_a?(Symbol) && arguments.include?(key.to_s) && !input.include?(key.to_s)
170
+ compat_arg = key.to_s
171
+ else
172
+ compat_arg = key
173
+ end
174
+
175
+ if !arguments.include?(compat_arg) && !input[key][:default].nil? && !input[key][:optional]
170
176
  Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]])
171
- arguments[key] = input[key][:default]
177
+ arguments[compat_arg] = input[key][:default]
172
178
  end
173
179
  end
174
180
  end
175
181
 
182
+ # Creates a new set of arguments with string arguments mapped to symbol ones
183
+ #
184
+ # This is to assist with moving to a JSON pure world where requests might come
185
+ # in from REST or other languages, those languages and indeed JSON itself does
186
+ # not support symbols.
187
+ #
188
+ # It ensures a backward compatible mode where for rpcutil both of these requests
189
+ # are equivelant
190
+ #
191
+ # c.get_fact(:fact => "cluster")
192
+ # c.get_fact("fact" => "cluster")
193
+ #
194
+ # The case where both :fact and "fact" is in the DDL cannot be handled correctly
195
+ # and this code will assume the caller means "fact" in that case. There's no
196
+ # way to represent such a request in JSON and just in general sounds like a bad
197
+ # idea, so a warning is logged which would in default client configuration appear
198
+ # on the clients display
199
+ def symbolize_basic_input_arguments(input, arguments)
200
+ warned = false
201
+
202
+ Hash[arguments.map do |key, value|
203
+ if input.include?(key.intern) && input.include?(key.to_s) && !warned
204
+ Log.warn("String and Symbol versions of input %s found in the DDL for %s, ensure your DDL keys are unique." % [key, @pluginname])
205
+ warned = true
206
+ end
207
+
208
+ if key.is_a?(String) && input.include?(key.intern) && !input.include?(key)
209
+ [key.intern, value]
210
+ else
211
+ [key, value]
212
+ end
213
+ end]
214
+ end
215
+
176
216
  # Helper to use the DDL to figure out if the remote call to an
177
217
  # agent should be allowed based on action name and inputs.
178
218
  def validate_rpc_request(action, arguments)
@@ -182,16 +222,17 @@ module MCollective
182
222
  end
183
223
 
184
224
  input = action_interface(action)[:input] || {}
225
+ compatible_args = symbolize_basic_input_arguments(input, arguments)
185
226
 
186
227
  input.keys.each do |key|
187
228
  unless input[key][:optional]
188
- unless arguments.keys.include?(key)
229
+ unless compatible_args.include?(key)
189
230
  raise DDLValidationError, "Action #{action} needs a #{key} argument"
190
231
  end
191
232
  end
192
233
 
193
- if arguments.keys.include?(key)
194
- validate_input_argument(input, key, arguments[key])
234
+ if compatible_args.include?(key)
235
+ validate_input_argument(input, key, compatible_args[key])
195
236
  end
196
237
  end
197
238
 
@@ -130,6 +130,14 @@ module MCollective
130
130
  @base64
131
131
  end
132
132
 
133
+ def description
134
+ cid = ""
135
+ cid += payload[:callerid] + "@" if payload.include?(:callerid)
136
+ cid += payload[:senderid]
137
+
138
+ "#{requestid} for agent '#{agent}' in collective '#{collective}' from #{cid}"
139
+ end
140
+
133
141
  def encode!
134
142
  case type
135
143
  when :reply
@@ -207,17 +215,11 @@ module MCollective
207
215
  msg_age = Time.now.utc.to_i - msgtime
208
216
 
209
217
  if msg_age > ttl
210
- cid = ""
211
- cid += payload[:callerid] + "@" if payload.include?(:callerid)
212
- cid += payload[:senderid]
213
-
214
- if msg_age > ttl
215
- PluginManager["global_stats"].ttlexpired
216
-
217
- raise(MsgTTLExpired, "message #{requestid} from #{cid} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}. Rejecting message.")
218
- end
218
+ PluginManager["global_stats"].ttlexpired
219
+ raise(MsgTTLExpired, "Message #{description} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}. Rejecting message.")
219
220
  end
220
- raise(NotTargettedAtUs, "Received message is not targetted to us") unless PluginManager["security_plugin"].validate_filter?(payload[:filter])
221
+
222
+ raise(NotTargettedAtUs, "Message #{description} does not pass filters. Ignoring message.") unless PluginManager["security_plugin"].validate_filter?(payload[:filter])
221
223
 
222
224
  @validated = true
223
225
  end
@@ -134,7 +134,9 @@ class OpenSSL::SSL::SSLContext
134
134
  DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
135
135
  end
136
136
 
137
- DEFAULT_PARAMS[:ciphers] << ':!SSLv2'
137
+ if DEFAULT_PARAMS[:ciphers]
138
+ DEFAULT_PARAMS[:ciphers] << ':!SSLv2'
139
+ end
138
140
 
139
141
  alias __mcollective_original_initialize initialize
140
142
  private :__mcollective_original_initialize
@@ -739,8 +739,13 @@ module MCollective
739
739
  end
740
740
 
741
741
  def rpc_result_from_reply(agent, action, reply)
742
- Result.new(agent, action, {:sender => reply[:senderid], :statuscode => reply[:body][:statuscode],
743
- :statusmsg => reply[:body][:statusmsg], :data => reply[:body][:data]})
742
+ senderid = reply.include?("senderid") ? reply["senderid"] : reply[:senderid]
743
+ body = reply.include?("body") ? reply["body"] : reply[:body]
744
+ s_code = body.include?("statuscode") ? body["statuscode"] : body[:statuscode]
745
+ s_msg = body.include?("statusmsg") ? body["statusmsg"] : body[:statusmsg]
746
+ data = body.include?("data") ? body["data"] : body[:data]
747
+
748
+ Result.new(agent, action, {:sender => senderid, :statuscode => s_code, :statusmsg => s_msg, :data => data})
744
749
  end
745
750
 
746
751
  # for requests that do not care for results just
@@ -231,7 +231,6 @@ module MCollective
231
231
  result_text << ""
232
232
  else
233
233
  [result].flatten.each do |r|
234
-
235
234
  if flags[:verbose]
236
235
  result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]
237
236
 
@@ -6,45 +6,69 @@ module MCollective
6
6
 
7
7
  def initialize(msg, ddl)
8
8
  @time = msg[:msgtime]
9
- @action = msg[:body][:action]
10
- @data = msg[:body][:data]
9
+ @action = msg[:body][:action] || msg[:body]["action"]
10
+ @data = msg[:body][:data] || msg[:body]["data"]
11
11
  @sender = msg[:senderid]
12
- @agent = msg[:body][:agent]
12
+ @agent = msg[:body][:agent] || msg[:body]["agent"]
13
13
  @uniqid = msg[:requestid]
14
14
  @caller = msg[:callerid] || "unknown"
15
15
  @ddl = ddl
16
16
  end
17
17
 
18
+ # In a scenario where a request came from a JSON pure medium like a REST
19
+ # service or other language client DDL::AgentDDL#validate_rpc_request will
20
+ # check "package" against the intput :package should the input "package" not
21
+ # also be known
22
+ #
23
+ # Thus once the request is built it will also have "package" and not :package
24
+ # data, so we need to fetch the correct key out of the hash.
25
+ def compatible_key(key)
26
+ return key if data.include?(key)
27
+
28
+ if ddl
29
+ input = ddl.action_interface(action)[:input]
30
+
31
+ # if :package is requested and the DDL also declares "package" we cant tell it to fetch
32
+ # "package", hence the check against the input here
33
+ return key.to_s if key.is_a?(Symbol) && !input.include?(key.to_s) && data.include?(key.to_s)
34
+ end
35
+
36
+ key
37
+ end
38
+
18
39
  # If data is a hash, quick helper to get access to it's include? method
19
40
  # else returns false
20
41
  def include?(key)
21
42
  return false unless @data.is_a?(Hash)
22
- return @data.include?(key)
43
+
44
+ @data.include?(compatible_key(key))
23
45
  end
24
46
 
25
47
  # If no :process_results is specified always respond else respond
26
48
  # based on the supplied property
27
49
  def should_respond?
50
+ return false unless @data.is_a?(Hash)
28
51
  return @data[:process_results] if @data.include?(:process_results)
52
+ return @data["process_results"] if @data.include?("process_results")
29
53
 
30
- return true
54
+ true
31
55
  end
32
56
 
33
57
  # If data is a hash, gives easy access to its members, else returns nil
34
58
  def [](key)
35
59
  return nil unless @data.is_a?(Hash)
36
- return @data[key]
60
+ return @data[compatible_key(key)]
37
61
  end
38
62
 
39
63
  def fetch(key, default)
40
64
  return nil unless @data.is_a?(Hash)
41
- return @data.fetch(key, default)
65
+ return @data.fetch(compatible_key(key), default)
42
66
  end
43
67
 
44
68
  def to_hash
45
- return {:agent => @agent,
46
- :action => @action,
47
- :data => @data}
69
+ {:agent => @agent,
70
+ :action => @action,
71
+ :data => @data}
48
72
  end
49
73
 
50
74
  # Validate the request against the DDL
@@ -14,18 +14,59 @@ module MCollective
14
14
  @agent = agent
15
15
  @action = action
16
16
  @results = result
17
+
18
+ convert_data_based_on_ddl if ddl
19
+ end
20
+
21
+ def ddl
22
+ @_ddl ||= DDL.new(agent)
23
+ rescue
24
+ nil
25
+ end
26
+
27
+ def data
28
+ @results[:data] = @results.delete("data") if @results.include?("data")
29
+
30
+ self[:data]
31
+ end
32
+
33
+ # Converts keys on the supplied data to those listed as outputs
34
+ # in the DDL. This is to facilitate JSON based transports
35
+ # without forcing everyone to rewrite DDLs and clients to
36
+ # convert symbols to strings, the data will be on symbol keys
37
+ # if the DDL has a symbol and not a string output defined
38
+ def convert_data_based_on_ddl
39
+ interface = ddl.action_interface(action)
40
+
41
+ return if interface.fetch(:output, {}).empty?
42
+
43
+ interface[:output].each do |output, properties|
44
+ next if data.include?(output)
45
+
46
+ if output.is_a?(Symbol) && data.include?(output.to_s)
47
+ data[output] = data.delete(output.to_s)
48
+ end
49
+ end
50
+ end
51
+
52
+ def compatible_key(key)
53
+ if key.is_a?(Symbol) && @results.include?(key.to_s)
54
+ key.to_s
55
+ else
56
+ key
57
+ end
17
58
  end
18
59
 
19
- def [](idx)
20
- @results[idx]
60
+ def [](key)
61
+ @results[compatible_key(key)]
21
62
  end
22
63
 
23
- def []=(idx, item)
24
- @results[idx] = item
64
+ def []=(key, item)
65
+ @results[key] = item
25
66
  end
26
67
 
27
68
  def fetch(key, default)
28
- @results.fetch(key, default)
69
+ @results.fetch(compatible_key(key), default)
29
70
  end
30
71
 
31
72
  def each
@@ -35,10 +76,10 @@ module MCollective
35
76
  def to_json(*a)
36
77
  {:agent => @agent,
37
78
  :action => @action,
38
- :sender => @results[:sender],
39
- :statuscode => @results[:statuscode],
40
- :statusmsg => @results[:statusmsg],
41
- :data => @results[:data]}.to_json(*a)
79
+ :sender => self[:sender],
80
+ :statuscode => self[:statuscode],
81
+ :statusmsg => self[:statusmsg],
82
+ :data => data}.to_json(*a)
42
83
  end
43
84
 
44
85
  def <=>(other)
@@ -36,7 +36,7 @@ module MCollective
36
36
 
37
37
  it 'should log to a default logfile' do
38
38
  Config.any_instance.stubs(:pluginconf).returns({})
39
- File.expects(:open).with("/var/log/mcollective-audit.log", "a").yields(file)
39
+ File.expects(:open).with("/var/log/puppetlabs/mcollective/mcollective-audit.log", "a").yields(file)
40
40
  Logfile.new.audit_request(request, nil)
41
41
  end
42
42
  end
@@ -11,6 +11,54 @@ module MCollective
11
11
  @ddl.metadata(:name => "name", :description => "description", :author => "author", :license => "license", :version => "version", :url => "url", :timeout => "timeout")
12
12
  end
13
13
 
14
+ describe "#symbolize_basic_input_arguments" do
15
+ before(:each) do
16
+ @ddl.action(:test, :description => "rspec")
17
+ @ddl.instance_variable_set("@current_entity", :test)
18
+ @ddl.input(:test, :prompt => "prompt", :description => "descr",
19
+ :type => :string, :optional => true, :validation => "",
20
+ :maxlength => 1, :default => "default")
21
+ end
22
+
23
+ it "should warn when there are string and symbol inputs for the same stringified key" do
24
+ @ddl.input("test", :prompt => "prompt", :description => "descr",
25
+ :type => :string, :optional => true, :validation => "",
26
+ :maxlength => 1, :default => "default")
27
+
28
+ Log.expects(:warn).with("String and Symbol versions of input test found in the DDL for rspec, ensure your DDL keys are unique.")
29
+ @ddl.symbolize_basic_input_arguments(@ddl.action_interface(:test)[:input], {:test => 1, "test" => 2})
30
+ end
31
+
32
+ it "should use the symbol given a string argument when there is not also a matching string input" do
33
+ result = @ddl.symbolize_basic_input_arguments(@ddl.action_interface(:test)[:input], {"test" => 2})
34
+ expect(result).to eq(:test => 2)
35
+ end
36
+
37
+ it "should use the string given a string argument when there is also a matching string input" do
38
+ @ddl.input("test", :prompt => "prompt", :description => "descr",
39
+ :type => :string, :optional => true, :validation => "",
40
+ :maxlength => 1, :default => "default")
41
+
42
+ Log.stubs(:warn)
43
+ result = @ddl.symbolize_basic_input_arguments(@ddl.action_interface(:test)[:input], {"test" => 2})
44
+ expect(result).to eq("test" => 2)
45
+ end
46
+
47
+ it "should not change symbols matching symbol inputs" do
48
+ result = @ddl.symbolize_basic_input_arguments(@ddl.action_interface(:test)[:input], {:test => 2})
49
+ expect(result).to eq(:test => 2)
50
+ end
51
+
52
+ it "should not change strings matching strings inputs" do
53
+ @ddl.input("string_test", :prompt => "prompt", :description => "descr",
54
+ :type => :string, :optional => true, :validation => "",
55
+ :maxlength => 1, :default => "default")
56
+
57
+ result = @ddl.symbolize_basic_input_arguments(@ddl.action_interface(:test)[:input], {"string_test" => 2})
58
+ expect(result).to eq("string_test" => 2)
59
+ end
60
+ end
61
+
14
62
  describe "#input" do
15
63
  it "should validate that an :optional property is set" do
16
64
  expect { @ddl.input(:x, {:y => 1}) }.to raise_error("Input needs a :optional property")
@@ -45,20 +93,29 @@ module MCollective
45
93
 
46
94
  args.should == {:required => "specified"}
47
95
  end
48
- end
49
96
 
50
- describe "#validate_rpc_request" do
51
- it "should ensure the action is known" do
52
- @ddl.action(:test, :description => "rspec")
97
+ it "should detect json primitive key names and consider them present as their symbol equivelants" do
98
+ args = {"required" => "specified"}
53
99
 
54
- expect {
55
- @ddl.validate_rpc_request(:fail, {})
56
- }.to raise_error("Attempted to call action fail for rspec but it's not declared in the DDL")
100
+ @ddl.set_default_input_arguments(:test, args)
57
101
 
58
- @ddl.validate_rpc_request(:test, {})
102
+ args.should == {"required" => "specified"}
59
103
  end
60
104
 
61
- it "should check all required arguments are present" do
105
+ it "should not consider the string key equiv to the symbol one when both symbol and string keys exist" do
106
+ @ddl.input("required", :prompt => "prompt", :description => "descr",
107
+ :type => :string, :optional => false, :validation => "",
108
+ :maxlength => 1, :default => "string default")
109
+
110
+ args = {"required" => "specified"}
111
+ @ddl.set_default_input_arguments(:test, args)
112
+
113
+ args.should == {"required" => "specified", :required=>"default"}
114
+ end
115
+ end
116
+
117
+ describe "#validate_rpc_request" do
118
+ before(:each) do
62
119
  @ddl.action(:test, :description => "rspec")
63
120
  @ddl.instance_variable_set("@current_entity", :test)
64
121
  @ddl.input(:optional, :prompt => "prompt", :description => "descr",
@@ -67,7 +124,17 @@ module MCollective
67
124
  @ddl.input(:required, :prompt => "prompt", :description => "descr",
68
125
  :type => :string, :optional => false, :validation => "",
69
126
  :maxlength => 1)
127
+ end
128
+
129
+ it "should ensure the action is known" do
130
+ expect {
131
+ @ddl.validate_rpc_request(:fail, {})
132
+ }.to raise_error("Attempted to call action fail for rspec but it's not declared in the DDL")
70
133
 
134
+ @ddl.validate_rpc_request(:test, {:required => "f"})
135
+ end
136
+
137
+ it "should check all required arguments are present" do
71
138
  @ddl.stubs(:validate_input_argument).returns(true)
72
139
 
73
140
  expect {
@@ -78,20 +145,17 @@ module MCollective
78
145
  end
79
146
 
80
147
  it "should input validate every supplied key" do
81
- @ddl.action(:test, :description => "rspec")
82
- @ddl.instance_variable_set("@current_entity", :test)
83
- @ddl.input(:optional, :prompt => "prompt", :description => "descr",
84
- :type => :string, :optional => true, :validation => "",
85
- :maxlength => 1)
86
- @ddl.input(:required, :prompt => "prompt", :description => "descr",
87
- :type => :string, :optional => false, :validation => "",
88
- :maxlength => 1)
148
+ @ddl.expects(:validate_input_argument).with(@ddl.entities[:test][:input], :required, "f")
149
+ @ddl.expects(:validate_input_argument).with(@ddl.entities[:test][:input], :optional, "f")
89
150
 
151
+ @ddl.validate_rpc_request(:test, {:required => "f", :optional => "f"}).should == true
152
+ end
90
153
 
154
+ it "should input validate string keys for symbol inputs correctly" do
91
155
  @ddl.expects(:validate_input_argument).with(@ddl.entities[:test][:input], :required, "f")
92
156
  @ddl.expects(:validate_input_argument).with(@ddl.entities[:test][:input], :optional, "f")
93
157
 
94
- @ddl.validate_rpc_request(:test, {:required => "f", :optional => "f"}).should == true
158
+ @ddl.validate_rpc_request(:test, {"required" => "f", "optional" => "f"}).should == true
95
159
  end
96
160
  end
97
161
 
@@ -371,13 +371,17 @@ module MCollective
371
371
 
372
372
  payload = mock
373
373
  payload.expects("[]").with(:filter).returns({})
374
+ payload.expects(:include?).with(:callerid).returns(false)
375
+ payload.expects("[]").with(:senderid).returns('sender')
374
376
 
375
- m = Message.new(payload, "message", :type => :request)
377
+ m = Message.new(payload, "message", {:type => :request, :collective => 'collective',
378
+ :agent => 'rspecagent', :requestid => '1234'})
376
379
  m.instance_variable_set("@msgtime", Time.now.to_i)
377
380
 
378
381
  expect {
379
382
  m.validate
380
- }.to raise_error(NotTargettedAtUs)
383
+ }.to raise_error(NotTargettedAtUs,
384
+ "Message 1234 for agent 'rspecagent' in collective 'collective' from sender does not pass filters. Ignoring message.")
381
385
  end
382
386
 
383
387
  it "should pass for good messages" do
@@ -413,12 +417,16 @@ module MCollective
413
417
 
414
418
  MCollective::PluginManager << {:type => "global_stats", :class => stats}
415
419
 
416
- m = Message.new({:callerid => "caller", :senderid => "sender"}, "message", :type => :request)
417
- m.instance_variable_set("@msgtime", (Time.now.to_i - 120))
420
+ m = Message.new({:callerid => "caller", :senderid => "sender"}, "message",
421
+ {:type => :request, :collective => 'collective',
422
+ :agent => 'rspecagent', :requestid => '1234'})
423
+ mtime = Time.now.to_i - 120
424
+ m.instance_variable_set("@msgtime", mtime)
418
425
 
419
426
  expect {
420
427
  m.validate
421
- }.to raise_error(MsgTTLExpired)
428
+ }.to raise_error(MsgTTLExpired,
429
+ /Message 1234 for agent 'rspecagent' in collective 'collective' from caller@sender created at #{mtime} is 1[12][019] seconds old, TTL is 60. Rejecting message./)
422
430
  end
423
431
  end
424
432
 
@@ -39,6 +39,65 @@ module MCollective
39
39
  @client.stubs(:ddl).returns(ddl)
40
40
  end
41
41
 
42
+ describe "#rpc_result_from_reply" do
43
+ it "should support symbol style replies" do
44
+ raw_result = {
45
+ :senderid => "rspec.id",
46
+ :body => {
47
+ :statuscode => 1,
48
+ :statusmsg => "rspec status",
49
+ :data => {
50
+ :one => 1
51
+ }
52
+ }
53
+ }
54
+
55
+ result = @client.rpc_result_from_reply("rspec", "test", raw_result)
56
+
57
+ expect(result.agent).to eq("rspec")
58
+ expect(result.action).to eq("test")
59
+ expect(result[:sender]).to eq("rspec.id")
60
+ expect(result[:statuscode]).to eq(1)
61
+ expect(result[:statusmsg]).to eq("rspec status")
62
+ expect(result[:data]).to eq(:one => 1)
63
+ expect(result.results).to eq(
64
+ :sender => "rspec.id",
65
+ :statuscode => 1,
66
+ :statusmsg => "rspec status",
67
+ :data => {:one => 1}
68
+ )
69
+ end
70
+
71
+ it "should support string style replies" do
72
+ raw_result = {
73
+ "senderid" => "rspec.id",
74
+ "body" => {
75
+ "statuscode" => 1,
76
+ "statusmsg" => "rspec status",
77
+ "data" => {
78
+ "one" => 1,
79
+ "two" => 2
80
+ }
81
+ }
82
+ }
83
+
84
+ result = @client.rpc_result_from_reply("rspec", "test", raw_result)
85
+
86
+ expect(result.agent).to eq("rspec")
87
+ expect(result.action).to eq("test")
88
+ expect(result[:sender]).to eq("rspec.id")
89
+ expect(result[:statuscode]).to eq(1)
90
+ expect(result[:statusmsg]).to eq("rspec status")
91
+ expect(result[:data]).to eq("one" => 1, "two" => 2)
92
+ expect(result.results).to eq(
93
+ :sender => "rspec.id",
94
+ :statuscode => 1,
95
+ :statusmsg => "rspec status",
96
+ :data => {"one" => 1, "two" => 2}
97
+ )
98
+ end
99
+ end
100
+
42
101
  describe "#detect_and_set_stdin_discovery" do
43
102
  before(:each) do
44
103
  @client = Client.new("foo", {:options => {:filter => Util.empty_filter, :config => "/nonexisting", :stdin => @stdin}})
@@ -20,6 +20,28 @@ module MCollective
20
20
  @request = Request.new(@req, @ddl)
21
21
  end
22
22
 
23
+ describe "#compatible_key" do
24
+ it "should return the key if its a known key already" do
25
+ expect(@request.compatible_key(:foo)).to be(:foo)
26
+ end
27
+
28
+ it "should return the symbol key if the DDL defines both" do
29
+ @ddl.action("test", :description => "rspec")
30
+ @ddl.instance_variable_set("@current_entity", "test")
31
+ @ddl.input(:test, :prompt => "test", :description => "test", :type => :boolean, :optional => true)
32
+ @ddl.input("test", :prompt => "test", :description => "test", :type => :boolean, :optional => true)
33
+
34
+ expect(@request.compatible_key(:test)).to be(:test)
35
+ expect(@request.compatible_key("test")).to eq("test")
36
+ end
37
+
38
+ it "should return the stringified key if a interned version of known string data was requested" do
39
+ expect(@request.compatible_key(:string)).to eq(:string)
40
+ @req[:body][:data]["string"] = "string data"
41
+ expect(@request.compatible_key(:string)).to eq("string")
42
+ end
43
+ end
44
+
23
45
  describe "#validate!" do
24
46
  it "should validate the request using the supplied DDL" do
25
47
  @ddl.expects(:validate_rpc_request).with("test", {:foo => "bar", :process_results => true})
@@ -60,6 +82,18 @@ module MCollective
60
82
  @req.delete(:callerid)
61
83
  Request.new(@req, @ddl).caller.should == "unknown"
62
84
  end
85
+
86
+ it "should support JSON pure inputs" do
87
+ @req[:body] = {"action" => "test",
88
+ "data" => {"foo" => "bar", "process_results" => true},
89
+ "agent" => "tester"}
90
+
91
+ request = Request.new(@req, @ddl)
92
+
93
+ expect(request.action).to eq("test")
94
+ expect(request.agent).to eq("tester")
95
+ expect(request.data).to eq("foo" => "bar", "process_results" => true)
96
+ end
63
97
  end
64
98
 
65
99
  describe "#include?" do
@@ -82,6 +116,8 @@ module MCollective
82
116
  it "should return correct value" do
83
117
  @req[:body][:data][:process_results] = false
84
118
  Request.new(@req, @ddl).should_respond?.should == false
119
+ @req[:body][:data]["process_results"] = false
120
+ Request.new(@req, @ddl).should_respond?.should == false
85
121
  end
86
122
  end
87
123
 
@@ -27,6 +27,34 @@ module MCollective
27
27
  end
28
28
  end
29
29
 
30
+ describe "#convert_data_based_on_ddl" do
31
+ it "should convert string data to symbol data based on the DDL" do
32
+ ddl = DDL.new("rspec", :agent, false)
33
+ ddl.metadata(:name => "name", :description => "description", :author => "author", :license => "license", :version => "version", :url => "url", :timeout => "timeout")
34
+ ddl.action("test", :description => "rspec")
35
+ ddl.instance_variable_set("@current_entity", "test")
36
+ ddl.output(:one, :description => "rspec one", :display_as => "One")
37
+
38
+ DDL.expects(:new).with("rspec").returns(ddl)
39
+
40
+ raw_result = {
41
+ "sender" => "rspec.id",
42
+ "statuscode" => 1,
43
+ "statusmsg" => "rspec status",
44
+ "data" => {
45
+ "one" => 1,
46
+ "two" => 2
47
+ }
48
+ }
49
+
50
+ result = Result.new("rspec", "test", raw_result)
51
+ expect(result.data).to eq(
52
+ :one => 1,
53
+ "two" => 2
54
+ )
55
+ end
56
+ end
57
+
30
58
  describe "#[]" do
31
59
  it "should access the results hash and return correct data" do
32
60
  @result[:foo].should == "bar"
@@ -71,17 +99,17 @@ module MCollective
71
99
 
72
100
  describe "#<=>" do
73
101
  it "should implement the Combined Comparison operator based on sender name" do
74
- result_a = Result.new("tester",
75
- "test",
76
- { :statuscode => 0,
77
- :statusmsg => "OK",
78
- :sender => "a_rspec",
102
+ result_a = Result.new("tester",
103
+ "test",
104
+ { :statuscode => 0,
105
+ :statusmsg => "OK",
106
+ :sender => "a_rspec",
79
107
  :data => {}})
80
- result_b = Result.new("tester",
81
- "test",
82
- { :statuscode => 0,
83
- :statusmsg => "OK",
84
- :sender => "b_rspec",
108
+ result_b = Result.new("tester",
109
+ "test",
110
+ { :statuscode => 0,
111
+ :statusmsg => "OK",
112
+ :sender => "b_rspec",
85
113
  :data => {}})
86
114
 
87
115
  (result_a <=> result_b).should == -1
@@ -259,7 +259,7 @@ module MCollective
259
259
  it 'should log if a message was received by not directed at the server' do
260
260
  runner.expects(:receive).raises(NotTargettedAtUs)
261
261
  runner.instance_variable_set(:@exit_receiver_thread, true)
262
- Log.expects(:debug).with("Message does not pass filters, ignoring")
262
+ Log.expects(:info)
263
263
  runner.send(:receiver_thread)
264
264
  end
265
265
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mcollective-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.6
4
+ version: 2.11.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-11-02 00:00:00.000000000 Z
12
+ date: 2017-06-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: systemu