Class: Maestro::MaestroWorker

Inherits:
Object
  • Object
show all
Defined in:
lib/maestro_plugin/maestro_worker.rb

Overview

Helper Class for Maestro Plugins written in Ruby. The lifecycle of the plugin starts with a call to the main entry point perform called by the Maestro agent, all the other methods are helpers that can be used to deal with parsing, errors, etc. The lifecycle ends with a call to run_callbacks which can be customized by specifying an on_complete_handler_method, or on_complete_handler_block.

Constant Summary collapse

CONTEXT_OUTPUTS_META =

Workitem constants

'__context_outputs__'
OUTPUT_META =
'__output__'
PREVIOUS_CONTEXT_OUTPUTS_META =
'__previous_context_outputs__'
STREAMING_META =
'__streaming__'
ERROR_META =
'__error__'
WAITING_META =
'__waiting__'
CANCEL_META =
'__cancel__'
NOT_NEEDED =
'__not_needed__'
'__links__'
PERSIST_META =
'__persist__'
MODEL_META =
'__model__'
RECORD_ID_META =
'__record_id__'
RECORD_FIELD_META =
'__record_field__'
RECORD_VALUE_META =
'__record_value__'
RECORD_FIELDS_META =
'__record_fields__'
RECORD_VALUES_META =
'__record_values__'
FILTER_META =
'__filter__'
NAME_META =
'__name__'
CREATE_META =
'__create__'
UPDATE_META =
'__update__'
DELETE_META =
'__delete__'

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.exception_handler_blockObject (readonly)

Returns the value of attribute exception_handler_block.



51
52
53
# File 'lib/maestro_plugin/maestro_worker.rb', line 51

def exception_handler_block
  @exception_handler_block
end

.exception_handler_methodObject (readonly)

Returns the value of attribute exception_handler_method.



51
52
53
# File 'lib/maestro_plugin/maestro_worker.rb', line 51

def exception_handler_method
  @exception_handler_method
end

.on_complete_handler_blockObject (readonly)

Returns the value of attribute on_complete_handler_block.



51
52
53
# File 'lib/maestro_plugin/maestro_worker.rb', line 51

def on_complete_handler_block
  @on_complete_handler_block
end

.on_complete_handler_methodObject (readonly)

Returns the value of attribute on_complete_handler_method.



51
52
53
# File 'lib/maestro_plugin/maestro_worker.rb', line 51

def on_complete_handler_method
  @on_complete_handler_method
end

Instance Attribute Details

#actionObject

Returns the value of attribute action.



87
88
89
# File 'lib/maestro_plugin/maestro_worker.rb', line 87

def action
  @action
end

#workitemObject

Returns the value of attribute workitem.



87
88
89
# File 'lib/maestro_plugin/maestro_worker.rb', line 87

def workitem
  @workitem
end

Class Method Details

.mock!Object

Call this to mock calls to outbound systems.



72
73
74
# File 'lib/maestro_plugin/maestro_worker.rb', line 72

def mock!
  @mock = true
end

.mock?Boolean



81
82
83
# File 'lib/maestro_plugin/maestro_worker.rb', line 81

def mock?
  @mock
end

.on_complete(handler = nil, &block) ⇒ Object

Register a callback method or block that gets called when the action was successfully completed. Block callbacks get the workitem as parameter.



66
67
68
69
# File 'lib/maestro_plugin/maestro_worker.rb', line 66

def on_complete(handler = nil, &block)
  @on_complete_handler_method = handler
  @on_complete_handler_block = block
end

.on_exception(handler = nil, &block) ⇒ Object

Register a callback method or block that gets called when an exception occurs during the processing of an action. handler can be a symbol or string with a method name, or a block. Both will get the exception as the first parameter, and the block handler will receive the participant instance as the second parameter



58
59
60
61
# File 'lib/maestro_plugin/maestro_worker.rb', line 58

def on_exception(handler = nil, &block)
  @exception_handler_method = handler
  @exception_handler_block = block
end

.unmock!Object

Call this to unmock calls to outbound systems.



77
78
79
# File 'lib/maestro_plugin/maestro_worker.rb', line 77

def unmock!
  @mock = false
end

Instance Method Details

Adds a link to be displayed in the Maestro UI.



344
345
346
347
# File 'lib/maestro_plugin/maestro_worker.rb', line 344

def add_link(name, url)
  set_field(LINKS_META, []) if fields[LINKS_META].nil?
  fields[LINKS_META] << {'name' => name, 'url' => url}
end

#as_boolean(value) ⇒ Object

Return boolean version of a value



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/maestro_plugin/maestro_worker.rb', line 367

def as_boolean(value)
  res = false

  if value
    if value.is_a?(TrueClass) || value.is_a?(FalseClass)
      res = value
    elsif value.is_a?(Fixnum)
      res = value != 0
    elsif value.respond_to?(:to_s)
      value = value.to_s.downcase

      res = (value == 't' || value == 'true')
    end
  end

  res
end

#as_int(value, default = 0) ⇒ Object

Return numeric version of value



352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/maestro_plugin/maestro_worker.rb', line 352

def as_int(value, default = 0)
  res = default

  if value
    if value.is_a?(Fixnum)
      res = value
    elsif value.respond_to?(:to_i)
      res = value.to_i
    end
  end

  res
end

#cancelObject

Send the “cancel” message to the server



238
239
240
241
242
243
244
245
# File 'lib/maestro_plugin/maestro_worker.rb', line 238

def cancel
  workitem[CANCEL_META] = true
  send_workitem_message
rescue Exception => e
  Maestro.log.warn "Failed To Send Cancel Message To Server #{e.class} #{e}: #{e.backtrace.join("\n")}"
ensure
  workitem.delete(CANCEL_META)
end

#create_record_with_fields(model, record_fields, record_values = nil) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/maestro_plugin/maestro_worker.rb', line 276

def create_record_with_fields(model, record_fields, record_values = nil)
  workitem[PERSIST_META] = true
  workitem[CREATE_META] = true
  workitem[MODEL_META] = model
  unless record_fields.is_a? Hash
    Maestro.log.warn 'deprecation: create_record_with_fields should be called with a Hash'
    record_fields = record_fields.join(',') if record_fields.respond_to? 'join'
    record_values = record_values.join(',') if record_values.respond_to? 'join'
  end

  workitem[RECORD_FIELDS_META] = record_fields
  workitem[RECORD_VALUES_META] = record_values
  send_workitem_message

  workitem.delete(PERSIST_META)
  workitem.delete(CREATE_META)
end

#delete_record(model, filter) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/maestro_plugin/maestro_worker.rb', line 295

def delete_record(model, filter)
  workitem[PERSIST_META] = true
  workitem[DELETE_META] = true
  workitem[MODEL_META] = model

  if filter.is_a? Hash
    workitem[FILTER_META] = filter
  else
    Maestro.log.warn 'deprecation: delete_record should be called with a Hash'
    workitem[NAME_META] = filter.to_s
  end

  send_workitem_message

  workitem.delete(PERSIST_META)
  workitem.delete(DELETE_META)
end

#errorObject



213
214
215
# File 'lib/maestro_plugin/maestro_worker.rb', line 213

def error
  fields[ERROR_META]
end

#error?Boolean



217
218
219
# File 'lib/maestro_plugin/maestro_worker.rb', line 217

def error?
  !(error.nil? or error.empty?)
end

#fieldsObject Also known as: get_fields



332
333
334
# File 'lib/maestro_plugin/maestro_worker.rb', line 332

def fields
  workitem['fields']
end

#get_boolean_field(field) ⇒ Object

Helper that renders a field as a boolean



328
329
330
# File 'lib/maestro_plugin/maestro_worker.rb', line 328

def get_boolean_field(field)
  as_boolean(get_field(field))
end

#get_field(field, default = nil) ⇒ Object

Get a field from workitem, supporting default value



316
317
318
319
320
# File 'lib/maestro_plugin/maestro_worker.rb', line 316

def get_field(field, default = nil)
  value = fields[field]
  value = default if !default.nil? && (value.nil? || (value.respond_to?(:empty?) && value.empty?))
  value
end

#get_int_field(field, default = 0) ⇒ Object

Helper that renders a field as an int



323
324
325
# File 'lib/maestro_plugin/maestro_worker.rb', line 323

def get_int_field(field, default = 0)
  as_int(get_field(field), default)
end

#handle_exception(e) ⇒ Object

Fire supplied exception handlers if supplied, otherwise do nothing



126
127
128
129
130
131
132
# File 'lib/maestro_plugin/maestro_worker.rb', line 126

def handle_exception(e)
  if self.class.exception_handler_method
    send(self.class.exception_handler_method, e)
  elsif self.class.exception_handler_block
    self.class.exception_handler_block.call(e, self)
  end
end

#is_json?(string) ⇒ Boolean Also known as: is_json



89
90
91
92
93
94
# File 'lib/maestro_plugin/maestro_worker.rb', line 89

def is_json?(string)
  JSON.parse string
  true
rescue Exception
  false
end

#not_neededObject

Send the “not needed” message to the server.



249
250
251
252
253
254
255
256
# File 'lib/maestro_plugin/maestro_worker.rb', line 249

def not_needed
  workitem[NOT_NEEDED] = true
  send_workitem_message
rescue Exception => e
  Maestro.log.warn "Failed To Send Not Needed Message To Server #{e.class} #{e}: #{e.backtrace.join("\n")}"
ensure
  workitem.delete(NOT_NEEDED)
end

#outputObject



204
205
206
207
208
209
210
211
# File 'lib/maestro_plugin/maestro_worker.rb', line 204

def output
  if MaestroWorker.mock?
    workitem[OUTPUT_META]
  else
    Maestro.log.warn "Output is only accessible when mock is enabled in tests. Otherwise is directly sent to Maestro"
    nil
  end
end

#perform(action, workitem) ⇒ Object

Perform the specified action with the provided workitem. Invokes the method specified by the action parameter.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/maestro_plugin/maestro_worker.rb', line 100

def perform(action, workitem)
  @action, @workitem = action, workitem
  send(action)
  write_output('') # Triggers any remaining buffered output to be sent
  run_callbacks
rescue MaestroDev::Plugin::PluginError => e
  write_output('') # Triggers any remaining buffered output to be sent
  set_error(e.message)
rescue Exception => e
  write_output('') # Triggers any remaining buffered output to be sent
  lowerstack = e.backtrace.find_index(caller[0])
  stack = lowerstack ? e.backtrace[0..lowerstack - 1] : e.backtrace
  msg = "Unexpected error executing task: #{e.class} #{e} at\n" + stack.join("\n")
  Maestro.log.warn("#{msg}\nFull stack:\n" + e.backtrace.join("\n"))

  # Let user-supplied exception handler do its thing
  handle_exception(e)
  set_error(msg)
ensure
  # Older agents expected this method to *maybe* return something
  # .. something that no longer exists, but if we return anything
  # it will be *wrong* :P
  return nil
end

#read_output_value(name) ⇒ Object

Read a value from the context output



151
152
153
154
155
156
157
158
# File 'lib/maestro_plugin/maestro_worker.rb', line 151

def read_output_value(name)
  if get_field(PREVIOUS_CONTEXT_OUTPUTS_META).nil?
    set_field(CONTEXT_OUTPUTS_META, {}) if get_field(CONTEXT_OUTPUTS_META).nil?
    get_field(CONTEXT_OUTPUTS_META)[name]
  else
    get_field(PREVIOUS_CONTEXT_OUTPUTS_META)[name]
  end
end

#reset_buffered_outputObject



199
200
201
202
# File 'lib/maestro_plugin/maestro_worker.rb', line 199

def reset_buffered_output
  @buffered_output = ''
  @last_write_output = Time.now
end

#run_callbacksObject



134
135
136
137
138
139
140
141
142
# File 'lib/maestro_plugin/maestro_worker.rb', line 134

def run_callbacks
  return if self.class.on_complete_handler_block.nil? && self.class.on_complete_handler_method.nil?

  if self.class.on_complete_handler_method
    send(self.class.on_complete_handler_method)
  else
    self.class.on_complete_handler_block.call(workitem)
  end
end

#save_output_value(name, value) ⇒ Object

Set a value in the context output



145
146
147
148
# File 'lib/maestro_plugin/maestro_worker.rb', line 145

def save_output_value(name, value)
  set_field(CONTEXT_OUTPUTS_META, {}) if get_field(CONTEXT_OUTPUTS_META).nil?
  get_field(CONTEXT_OUTPUTS_META)[name] = value
end

#set_error(error) ⇒ Object



221
222
223
# File 'lib/maestro_plugin/maestro_worker.rb', line 221

def set_error(error)
  set_field(ERROR_META, error)
end

#set_field(field, value) ⇒ Object



339
340
341
# File 'lib/maestro_plugin/maestro_worker.rb', line 339

def set_field(field, value)
  fields[field] = value
end

#set_waiting(should_wait) ⇒ Object

Sets the current task as waiting



228
229
230
231
232
233
234
235
# File 'lib/maestro_plugin/maestro_worker.rb', line 228

def set_waiting(should_wait)
  workitem[WAITING_META] = should_wait
  send_workitem_message
rescue Exception => e
  Maestro.log.warn "Failed To Send Waiting Message To Server #{e.class} #{e}: #{e.backtrace.join("\n")}"
ensure
  workitem.delete(WAITING_META) unless should_wait
end

#update_fields_in_record(model, name_or_id, record_field, record_value) ⇒ Object

persistence



261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/maestro_plugin/maestro_worker.rb', line 261

def update_fields_in_record(model, name_or_id, record_field, record_value)
  workitem[PERSIST_META] = true
  workitem[UPDATE_META] = true
  workitem[MODEL_META] = model
  workitem[RECORD_ID_META] = name_or_id.to_s
  workitem[RECORD_FIELD_META] = record_field
  workitem[RECORD_VALUE_META] = record_value

  send_workitem_message

  workitem.delete(PERSIST_META)
  workitem.delete(UPDATE_META)
end

#write_output(output, options = {}) ⇒ Object

Sends the specified ouput string to the server for persistence If called with :buffer as an option, the output will be queued up until a number of writes has occurred, or a reasonable period since the last sent occurred. Any call without the :buffer option will cause any buffered output to be sent immediately.

Example:

write_output("I am Sam\n")                      <-- send immediately
write_output("Sam I am\n", :buffer => true)     <-- buffer for later
write_output("I like Ham\n")                    <-- sends 'Sam I am\nI like Ham\n'


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
194
195
196
197
# File 'lib/maestro_plugin/maestro_worker.rb', line 169

def write_output(output, options = {})
  # First time thru?  We need to do some setup!
  reset_buffered_output if @buffered_output.nil?

  @buffered_output += output

  # If a) we have data to write, and
  #    b) its been > 2 seconds since we last sent
  #
  # The 2 second factor is there to allow slowly accumulating data to be sent out more regularly.
  if !@buffered_output.empty? && (!options[:buffer] || Time.now - @last_write_output > 2)
    if !MaestroWorker.mock?
      workitem[OUTPUT_META] = @buffered_output
    else
      # Test mode, we want to retain output - normal operation clears out
      # data after it is sent
      workitem[OUTPUT_META] = '' if !workitem[OUTPUT_META]
      workitem[OUTPUT_META] = workitem[OUTPUT_META] + @buffered_output
    end

    workitem[STREAMING_META] = true
    send_workitem_message
    reset_buffered_output
  end
rescue Exception => e
  Maestro.log.warn "Unable To Write Output To Server #{e.class} #{e}: #{e.backtrace.join("\n")}"
ensure
  workitem.delete(STREAMING_META)
end