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
#---------------------------------------------------------------
|