summaryrefslogtreecommitdiffstats
path: root/lib/puppet/transaction/event_manager.rb
blob: 3ebb0a9d3cbe14b989cdc076a59762b9f3ff746b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
require 'puppet/transaction'

class Puppet::Transaction::EventManager
  attr_reader :transaction, :events

  def initialize(transaction)
    @transaction = transaction
    @event_queues = {}
    @events = []
  end

  def relationship_graph
    transaction.relationship_graph
  end

  # Respond to any queued events for this resource.
  def process_events(resource)
    restarted = false
    queued_events(resource) do |callback, events|
      r = process_callback(resource, callback, events)
      restarted ||= r
    end

    if restarted
      queue_events(resource, [resource.event(:name => :restarted, :status => "success")])

      transaction.resource_status(resource).restarted = true
    end
  end

  # Queue events for other resources to respond to.  All of these events have
  # to be from the same resource.
  def queue_events(resource, events)
    @events += events

    # Do some basic normalization so we're not doing so many
    # graph queries for large sets of events.
    events.inject({}) do |collection, event|
      collection[event.name] ||= []
      collection[event.name] << event
      collection
    end.collect do |name, list|
      # It doesn't matter which event we use - they all have the same source
      # and name here.
      event = list[0]

      # Collect the targets of any subscriptions to those events.  We pass
      # the parent resource in so it will override the source in the events,
      # since eval_generated children can't have direct relationships.
      relationship_graph.matching_edges(event, resource).each do |edge|
        next unless method = edge.callback
        next unless edge.target.respond_to?(method)

        queue_events_for_resource(resource, edge.target, method, list)
      end

      queue_events_for_resource(resource, resource, :refresh, [event]) if resource.self_refresh? and ! resource.deleting?
    end
  end

  def queue_events_for_resource(source, target, callback, events)
    source.info "Scheduling #{callback} of #{target}"

    @event_queues[target] ||= {}
    @event_queues[target][callback] ||= []
    @event_queues[target][callback] += events
  end

  def queued_events(resource)
    return unless callbacks = @event_queues[resource]
    callbacks.each do |callback, events|
      yield callback, events
    end
  end

  private

  def process_callback(resource, callback, events)
    process_noop_events(resource, callback, events) and return false unless events.detect { |e| e.status != "noop" }
    resource.send(callback)

    resource.notice "Triggered '#{callback}' from #{events.length} events"
    return true
  rescue => detail
    resource.err "Failed to call #{callback}: #{detail}"

    transaction.resource_status(resource).failed_to_restart = true
    puts detail.backtrace if Puppet[:trace]
    return false
  end

  def process_noop_events(resource, callback, events)
    resource.notice "Would have triggered '#{callback}' from #{events.length} events"

    # And then add an event for it.
    queue_events(resource, [resource.event(:status => "noop", :name => :noop_restart)])
    true # so the 'and if' works
  end
end