mcollective-client 2.10.6 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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