summaryrefslogtreecommitdiffstats
path: root/lib/puppet/transaction.rb
blob: 9e5a0fe3035a63936e0f9d12c4b44ab2f61d21cf (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
100
101
102
103
104
105
106
107
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
148
149
150
151
152
153
154
155
#!/usr/local/bin/ruby -w

# $Id$

# the class that actually walks our object/state tree, collects the changes,
# and performs them

# there are two directions of walking:
#   - first we recurse down the tree and collect changes
#   - then we walk back up the tree through 'refresh' after the changes

require 'puppet'
require 'puppet/statechange'

#---------------------------------------------------------------
module Puppet
class Transaction
    attr_accessor :toplevel, :component

    #---------------------------------------------------------------
    # a bit of a gross hack; a global list of objects that have failed to sync,
    # so that we can verify during later syncs that our dependencies haven't
    # failed
    def Transaction.init
        @@failures = Hash.new(0)
        Puppet::Metric.init
        @@changed = []
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    # for now, just store the changes for executing linearly
    # later, we might execute them as we receive them
    def change(change)
        @changes.push change
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    # okay, here's the deal:
    # a given transaction maps directly to a component, and each transaction
    # will only ever receive changes from its respective component
    # so, when looking for subscribers, we need to first see if the object
    # that actually changed had any direct subscribers
    # then, we need to pass the event to the object's containing component,
    # to see if it or any of its parents have subscriptions on the event
    def evaluate
        Puppet.debug "executing %s changes " % @changes.length

        events = @changes.collect { |change|
            if change.is_a?(Puppet::StateChange)
                change.transaction = self
                events = nil
                begin
                    # use an array, so that changes can return more than one
                    # event if they want
                    events = [change.forward].flatten.reject { |e| e.nil? }
                    #@@changed.push change.state.parent
                rescue => detail
                    Puppet.err("%s failed: %s" % [change,detail])
                    next
                    # FIXME this should support using onerror to determine behaviour
                end

                if events.nil?
                    Puppet.debug "No events returned?"
                end
                events
            elsif change.is_a?(Puppet::Transaction)
                change.evaluate
            else
                raise "Transactions cannot handle objects of type %s" % child.class
            end
        }.flatten.reject { |event|
            event.nil?
        }

        events.each { |event|
            object = event.source
            object.propagate(event)
        }
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    # this should only be called by a Puppet::Container object now
    # and it should only receive an array
    def initialize(objects)
        @objects = objects
        @toplevel = false

        @triggered = Hash.new { |hash, key|
            hash[key] = Hash.new(0)
        }

        # of course, this won't work on the second run
        unless defined? @@failures
            @toplevel = true
            self.class.init
        end
        # change collection is in-band, and message generation is out-of-band
        # of course, exception raising is also out-of-band
        @changes = @objects.collect { |child|
            # these children are all Puppet::Type instances
            # not all of the children will return a change, and Containers
            # return transactions
            child.evaluate
        }.flatten.reject { |child|
            child.nil? # remove empties
        }
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    def rollback
        @changes.each { |change|
            if change.is_a?(Puppet::StateChange)
                next unless change.run
                #change.transaction = self
                begin
                    change.backward
                    #@@changed.push change.state.parent
                rescue => detail
                    Puppet.err("%s rollback failed: %s" % [change,detail])
                    # at this point, we would normally do error handling
                    # but i haven't decided what to do for that yet
                    # so just record that a sync failed for a given object
                    #@@failures[change.state.parent] += 1
                    # this still could get hairy; what if file contents changed,
                    # but a chmod failed?  how would i handle that error? dern
                end
            elsif change.is_a?(Puppet::Transaction)
                # yay, recursion
                change.rollback
            else
                raise "Transactions cannot handle objects of type %s" % child.class
            end
        }
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    def triggered(object, method)
        @triggered[object][method] += 1
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    def triggered?(object, method)
        @triggered[object][method]
    end
    #---------------------------------------------------------------
end
end
#---------------------------------------------------------------