Class: MCollective::Util::Playbook::Tasks::McollectiveTask

Inherits:
Base
  • Object
show all
Defined in:
lib/mcollective/util/playbook/tasks/mcollective_task.rb

Instance Attribute Summary

Attributes inherited from Base

#description, #fail_ok

Instance Method Summary collapse

Methods inherited from Base

#initialize, #run_task, #to_s

Methods included from MCollective::Util::Playbook::TemplateUtil

#__template_process_string, #__template_resolve, #t

Constructor Details

This class inherits a constructor from MCollective::Util::Playbook::Tasks::Base

Instance Method Details

#assert_replies(replies) ⇒ Boolean, Integer

Runs the reply data through the assertion, true when all replies match

Returns:

  • (Boolean, Integer)

    boolean indicating overall success, the integer shows how many nodes failed



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 279

def assert_replies(replies)
  return [false, replies.size] unless @assertion
  return [false, replies.size] unless jgrep?

  result = []
  expected = replies.size
  jdata = replies.map {|reply| reply.results[:data]}.to_json

  begin
    Log.debug("Asserting '%s' over %d replies" % [@assertion, replies.size])
    result = JGrep.jgrep(jdata, @assertion)
  rescue
    Log.error("Assertion '%s' could not be matched: %s" % [@asssertion, expected])
  end

  [result.size == expected, expected - result.size]
end

#client(from_cache = true) ⇒ RPC::Client

Creates and cache an RPC::Client for the configured agent

Parameters:

  • from_cache (Boolean) (defaults to: true)

    when false a new instance is always returned

Returns:



35
36
37
38
39
40
41
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 35

def client(from_cache=true)
  if from_cache
    @_rpc_client ||= create_and_configure_client
  else
    create_and_configure_client
  end
end

#create_and_configure_clientRPC::Client

TODO:

discovery

Creates a new RPC::Client and configures it with the configured settings

Returns:



47
48
49
50
51
52
53
54
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 47

def create_and_configure_client
  client = RPC::Client.new(@agent, :configfile => Util.config_file_for_user, :options => Util.default_options)
  client.batch_size = @batch_size if @batch_size
  client.batch_sleep_time = @batch_sleep_time if @batch_sleep_time
  client.discover(:nodes => @nodes)
  client.progress = false
  client
end

#from_hash(data) ⇒ McollectiveTask

Initialize the task from a hash

Parameters:

  • data (Hash)

    input data matching tasks/rpc.json schema

Returns:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 88

def from_hash(data)
  @nodes = data.fetch("nodes", [])
  @agent, @action = parse_action(data["action"])
  @batch_size = data["batch_size"]
  @batch_sleep_time = data["batch_sleep_time"]
  @properties = data.fetch("properties", {})
  @post = data.fetch("post", [])
  @log_replies = !data.fetch("silent", false)
  @assertion = data["assert"]

  @_rpc_client = nil

  self
end

#jgrep?Boolean

Determines if jgrep is available and loads it

Returns:

  • (Boolean)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 15

def jgrep?
  @_jgrep = false

  require "jgrep"

  if JGrep.respond_to?("validate_expression")
    @_jgrep = true
  else
    Log.warn("Please install a version of JGrep greater than or equal to 1.5.0")
  end

  @_jgrep
rescue LoadError
  @_jgrep = false
end

#log_failure(stats, replies) ⇒ Object

Logs a failed request

Parameters:



243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 243

def log_failure(stats, replies)
  stats ||= client.stats

  Log.warn("Failed request %s for %s#%s in %0.2fs" % [stats.requestid, @agent, @action, stats.totaltime])

  Log.warn("No responses from: %s" % stats.noresponsefrom.join(", ")) unless stats.noresponsefrom.empty?

  if stats.failcount > 0
    replies.each do |reply|
      Log.warn("Failed result from %s: %s" % [reply.results[:sender], reply.results[:statusmsg]]) if reply.results[:statuscode] > 0
    end
  end
end

#log_reply(reply) ⇒ Object



257
258
259
260
261
262
263
264
265
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 257

def log_reply(reply)
  if reply.results[:statuscode] == 0
    return unless @log_replies

    Log.info("%s %s#%s success: %s" % [reply.results[:sender], @agent, @action, reply.results[:data].inspect])
  else
    Log.warn("%s %s#%s failure: %s" % [reply.results[:sender], @agent, @action, reply.results[:data].inspect])
  end
end

#log_results(stats, replies) ⇒ Object

Logs the result of a request

Parameters:



199
200
201
202
203
204
205
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 199

def log_results(stats, replies)
  if request_success?(stats)
    log_success(stats)
  else
    log_failure(stats, replies)
  end
end

#log_success(stats) ⇒ Object

Logs a successfull request

Parameters:



210
211
212
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 210

def log_success(stats)
  Log.debug(success_message(stats))
end

#parse_action(action) ⇒ Array<String, String>

TODO:

check it complies to format

Parse an action in the form agent.action

Parameters:

  • action (String)

    in the form agent.action

Returns:



80
81
82
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 80

def parse_action(action)
  action.split(".")
end

#parse_assertiontrue, String

Validates the supplied assertion using JGrep’s parser

Returns:

  • (true, String)

    true when its valid, else a string with any errors



270
271
272
273
274
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 270

def parse_assertion
  return "JGrep not installed" unless jgrep?

  JGrep.validate_expression(@assertion)
end

#request_success?(stats) ⇒ Boolean

Determines if a request was succesfull

Parameters:

Returns:

  • (Boolean)


301
302
303
304
305
306
307
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 301

def request_success?(stats)
  return false if stats.failcount > 0
  return false if stats.okcount == 0
  return false unless stats.noresponsefrom.empty?

  true
end

#runArray<Boolean, String, Array<RPC::Result>>

TODO:

should return some kind of task status object

Logs a single RPC reply Performs a single attempt at calling the agent

Returns:



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 331

def run
  Log.info("Starting request for %s#%s against %d nodes" % [@agent, @action, @nodes.size])

  begin
    replies = []

    client.send(@action, symbolize_basic_input_arguments(@properties)) do |_, s|
      replies << s
      log_reply(s)
    end

    log_results(client.stats, replies)
    run_result(client.stats, replies)
  rescue
    msg = "Could not create request for %s#%s: %s: %s" % [@agent, @action, $!.class, $!.to_s]
    Log.debug(msg)
    Log.debug($!.backtrace.join("\t\n"))

    [false, msg, []]
  end
end

#run_result(stats, replies) ⇒ Array<Boolean, String, Array<Hash>>

Determines the run result

Parameters:

Returns:



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 108

def run_result(stats, replies)
  reply_data = replies.map do |reply|
    {
      "agent" => reply.agent,
      "action" => reply.action,
      "sender" => reply.results[:sender],
      "statuscode" => reply.results[:statuscode],
      "statusmsg" => reply.results[:statusmsg],
      "data" => reply.results[:data],
      "requestid" => stats.requestid
    }
  end

  success = request_success?(stats)
  assert_failed = -1

  if @assertion && success
    passed, assert_failed = assert_replies(replies)
    if passed
      Log.info("Assertion '%s' passed on all %d nodes" % [@assertion, replies.size])
    else
      Log.warn("Assertion '%s' failed on %d/%d nodes" % [@assertion, assert_failed, replies.size])
      success = false
    end
  end

  if success
    msg = nil

    msg = summary_message(stats) if should_summarize?
    msg ||= success_message(stats)

    [true, msg, reply_data]
  elsif assert_failed > -1
    [false, "Failed request %s for %s#%s assertion failed on %d node(s)" % [stats.requestid, @agent, @action, assert_failed], reply_data]
  else
    failed = stats.failcount + stats.noresponsefrom.size
    [false, "Failed request %s for %s#%s on %d failed node(s)" % [stats.requestid, @agent, @action, failed], reply_data]
  end
end

#should_summarize?Boolean

Determines if a summary was requested

Returns:

  • (Boolean)


312
313
314
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 312

def should_summarize?
  @post.include?("summarize")
end

#startup_hookObject



6
7
8
9
10
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 6

def startup_hook
  @properties = {}
  @post = []
  @nodes = []
end

#success_message(stats) ⇒ String

Creates the messages logged on success

Returns:



217
218
219
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 217

def success_message(stats)
  "Successful request %s for %s#%s in %0.2fs against %d node(s)" % [stats.requestid, @agent, @action, stats.totaltime, stats.okcount]
end

#summary_message(stats) ⇒ String?

Creates the summary message

Parameters:

  • stats (RPC::Stat)

Returns:



225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 225

def summary_message(stats)
  summary = {}

  if stats.aggregate_summary.size + stats.aggregate_failures.size > 0
    stats.aggregate_summary.each do |aggregate|
      summary.merge!(aggregate.result[:value])
    end
  end

  return nil if summary.empty?

  "Summary for %s: %s" % [@description, summary.to_json]
end

#symbolize_basic_input_arguments(properties) ⇒ Hash

Converts the string keys to symbols if the DDL is based on symbols

This is to be compatible with either JSON or non JSON mcollectives

Parameters:

  • properties (Hash)

    the properties for the task

Returns:

  • (Hash)

    mapped to what the DDL expects



322
323
324
325
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 322

def symbolize_basic_input_arguments(properties)
  input = client.ddl.action_interface(@action)[:input] || {}
  client.ddl.symbolize_basic_input_arguments(input, properties)
end

#to_execution_result(results) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 149

def to_execution_result(results)
  e_result = {}

  results[2].each do |data|
    result = {
      "value" => JSON.parse(JSON.dump(data)),
      "type" => "mcollective",
      "fail_ok" => @fail_ok
    }

    unless data["statuscode"] == 0
      result["error"] = {
        "msg" => data["statusmsg"],
        "kind" => "choria.playbook/taskerror",
        "details" => {
          "agent" => @agent,
          "action" => @action,
          "issue_code" => data["statuscode"]
        }
      }
    end

    e_result[data["sender"]] = result
  end

  if client.stats
    client.stats.noresponsefrom.each do |nr|
      e_result[nr] = {
        "value" => {},
        "type" => "mcollective",
        "fail_ok" => @fail_ok,
        "error" => {
          "msg" => "No response from node %s" % nr,
          "kind" => "choria.playbook/taskerror",
          "details" => {
            "agent" => @agent,
            "action" => @action
          }
        }
      }
    end
  end

  e_result
end

#validate_configuration!Object

Validates the internal configuration of the task

Raises:

  • (StandardError)

    on failure of the internal state



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/mcollective/util/playbook/tasks/mcollective_task.rb', line 59

def validate_configuration!
  raise("Nodes have to be an array") unless @nodes.is_a?(Array)
  raise("Nodes need to be supplied, refusing to run against empty node list") if @nodes.empty?

  if @assertion
    raise("jgrep is required for tasks with an assertion") unless jgrep?

    parse_result = parse_assertion

    unless parse_result == true
      parse_result.tr!("\n", " ")
      raise("Could not parse the assertion %s: %s" % [@assertion, parse_result])
    end
  end
end