]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | ||
7 | "github.com/hashicorp/terraform/config/module" | |
8 | "github.com/hashicorp/terraform/dag" | |
9 | ) | |
10 | ||
11 | // GraphNodeDestroyerCBD must be implemented by nodes that might be | |
12 | // create-before-destroy destroyers. | |
13 | type GraphNodeDestroyerCBD interface { | |
14 | GraphNodeDestroyer | |
15 | ||
16 | // CreateBeforeDestroy returns true if this node represents a node | |
17 | // that is doing a CBD. | |
18 | CreateBeforeDestroy() bool | |
19 | ||
20 | // ModifyCreateBeforeDestroy is called when the CBD state of a node | |
21 | // is changed dynamically. This can return an error if this isn't | |
22 | // allowed. | |
23 | ModifyCreateBeforeDestroy(bool) error | |
24 | } | |
25 | ||
26 | // CBDEdgeTransformer modifies the edges of CBD nodes that went through | |
27 | // the DestroyEdgeTransformer to have the right dependencies. There are | |
28 | // two real tasks here: | |
29 | // | |
30 | // 1. With CBD, the destroy edge is inverted: the destroy depends on | |
31 | // the creation. | |
32 | // | |
33 | // 2. A_d must depend on resources that depend on A. This is to enable | |
34 | // the destroy to only happen once nodes that depend on A successfully | |
35 | // update to A. Example: adding a web server updates the load balancer | |
36 | // before deleting the old web server. | |
37 | // | |
38 | type CBDEdgeTransformer struct { | |
39 | // Module and State are only needed to look up dependencies in | |
40 | // any way possible. Either can be nil if not availabile. | |
41 | Module *module.Tree | |
42 | State *State | |
43 | } | |
44 | ||
45 | func (t *CBDEdgeTransformer) Transform(g *Graph) error { | |
46 | log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...") | |
47 | ||
48 | // Go through and reverse any destroy edges | |
49 | destroyMap := make(map[string][]dag.Vertex) | |
50 | for _, v := range g.Vertices() { | |
51 | dn, ok := v.(GraphNodeDestroyerCBD) | |
52 | if !ok { | |
53 | continue | |
54 | } | |
55 | ||
56 | if !dn.CreateBeforeDestroy() { | |
57 | // If there are no CBD ancestors (dependent nodes), then we | |
58 | // do nothing here. | |
59 | if !t.hasCBDAncestor(g, v) { | |
60 | continue | |
61 | } | |
62 | ||
63 | // If this isn't naturally a CBD node, this means that an ancestor is | |
64 | // and we need to auto-upgrade this node to CBD. We do this because | |
65 | // a CBD node depending on non-CBD will result in cycles. To avoid this, | |
66 | // we always attempt to upgrade it. | |
67 | if err := dn.ModifyCreateBeforeDestroy(true); err != nil { | |
68 | return fmt.Errorf( | |
69 | "%s: must have create before destroy enabled because "+ | |
70 | "a dependent resource has CBD enabled. However, when "+ | |
71 | "attempting to automatically do this, an error occurred: %s", | |
72 | dag.VertexName(v), err) | |
73 | } | |
74 | } | |
75 | ||
76 | // Find the destroy edge. There should only be one. | |
77 | for _, e := range g.EdgesTo(v) { | |
78 | // Not a destroy edge, ignore it | |
79 | de, ok := e.(*DestroyEdge) | |
80 | if !ok { | |
81 | continue | |
82 | } | |
83 | ||
84 | log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s", | |
85 | dag.VertexName(de.Source()), dag.VertexName(de.Target())) | |
86 | ||
87 | // Found it! Invert. | |
88 | g.RemoveEdge(de) | |
89 | g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()}) | |
90 | } | |
91 | ||
92 | // If the address has an index, we strip that. Our depMap creation | |
93 | // graph doesn't expand counts so we don't currently get _exact_ | |
94 | // dependencies. One day when we limit dependencies more exactly | |
95 | // this will have to change. We have a test case covering this | |
96 | // (depNonCBDCountBoth) so it'll be caught. | |
97 | addr := dn.DestroyAddr() | |
98 | if addr.Index >= 0 { | |
99 | addr = addr.Copy() // Copy so that we don't modify any pointers | |
100 | addr.Index = -1 | |
101 | } | |
102 | ||
103 | // Add this to the list of nodes that we need to fix up | |
104 | // the edges for (step 2 above in the docs). | |
105 | key := addr.String() | |
106 | destroyMap[key] = append(destroyMap[key], v) | |
107 | } | |
108 | ||
109 | // If we have no CBD nodes, then our work here is done | |
110 | if len(destroyMap) == 0 { | |
111 | return nil | |
112 | } | |
113 | ||
114 | // We have CBD nodes. We now have to move on to the much more difficult | |
115 | // task of connecting dependencies of the creation side of the destroy | |
116 | // to the destruction node. The easiest way to explain this is an example: | |
117 | // | |
118 | // Given a pre-destroy dependence of: A => B | |
119 | // And A has CBD set. | |
120 | // | |
121 | // The resulting graph should be: A => B => A_d | |
122 | // | |
123 | // They key here is that B happens before A is destroyed. This is to | |
124 | // facilitate the primary purpose for CBD: making sure that downstreams | |
125 | // are properly updated to avoid downtime before the resource is destroyed. | |
126 | // | |
127 | // We can't trust that the resource being destroyed or anything that | |
128 | // depends on it is actually in our current graph so we make a new | |
129 | // graph in order to determine those dependencies and add them in. | |
130 | log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...") | |
131 | depMap, err := t.depMap(destroyMap) | |
132 | if err != nil { | |
133 | return err | |
134 | } | |
135 | ||
136 | // We now have the mapping of resource addresses to the destroy | |
137 | // nodes they need to depend on. We now go through our own vertices to | |
138 | // find any matching these addresses and make the connection. | |
139 | for _, v := range g.Vertices() { | |
140 | // We're looking for creators | |
141 | rn, ok := v.(GraphNodeCreator) | |
142 | if !ok { | |
143 | continue | |
144 | } | |
145 | ||
146 | // Get the address | |
147 | addr := rn.CreateAddr() | |
148 | ||
149 | // If the address has an index, we strip that. Our depMap creation | |
150 | // graph doesn't expand counts so we don't currently get _exact_ | |
151 | // dependencies. One day when we limit dependencies more exactly | |
152 | // this will have to change. We have a test case covering this | |
153 | // (depNonCBDCount) so it'll be caught. | |
154 | if addr.Index >= 0 { | |
155 | addr = addr.Copy() // Copy so that we don't modify any pointers | |
156 | addr.Index = -1 | |
157 | } | |
158 | ||
159 | // If there is nothing this resource should depend on, ignore it | |
160 | key := addr.String() | |
161 | dns, ok := depMap[key] | |
162 | if !ok { | |
163 | continue | |
164 | } | |
165 | ||
166 | // We have nodes! Make the connection | |
167 | for _, dn := range dns { | |
168 | log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s", | |
169 | dag.VertexName(dn), dag.VertexName(v)) | |
170 | g.Connect(dag.BasicEdge(dn, v)) | |
171 | } | |
172 | } | |
173 | ||
174 | return nil | |
175 | } | |
176 | ||
177 | func (t *CBDEdgeTransformer) depMap( | |
178 | destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) { | |
179 | // Build the graph of our config, this ensures that all resources | |
180 | // are present in the graph. | |
181 | g, err := (&BasicGraphBuilder{ | |
182 | Steps: []GraphTransformer{ | |
183 | &FlatConfigTransformer{Module: t.Module}, | |
184 | &AttachResourceConfigTransformer{Module: t.Module}, | |
185 | &AttachStateTransformer{State: t.State}, | |
186 | &ReferenceTransformer{}, | |
187 | }, | |
188 | Name: "CBDEdgeTransformer", | |
189 | }).Build(nil) | |
190 | if err != nil { | |
191 | return nil, err | |
192 | } | |
193 | ||
194 | // Using this graph, build the list of destroy nodes that each resource | |
195 | // address should depend on. For example, when we find B, we map the | |
196 | // address of B to A_d in the "depMap" variable below. | |
197 | depMap := make(map[string][]dag.Vertex) | |
198 | for _, v := range g.Vertices() { | |
199 | // We're looking for resources. | |
200 | rn, ok := v.(GraphNodeResource) | |
201 | if !ok { | |
202 | continue | |
203 | } | |
204 | ||
205 | // Get the address | |
206 | addr := rn.ResourceAddr() | |
207 | key := addr.String() | |
208 | ||
209 | // Get the destroy nodes that are destroying this resource. | |
210 | // If there aren't any, then we don't need to worry about | |
211 | // any connections. | |
212 | dns, ok := destroyMap[key] | |
213 | if !ok { | |
214 | continue | |
215 | } | |
216 | ||
217 | // Get the nodes that depend on this on. In the example above: | |
218 | // finding B in A => B. | |
219 | for _, v := range g.UpEdges(v).List() { | |
220 | // We're looking for resources. | |
221 | rn, ok := v.(GraphNodeResource) | |
222 | if !ok { | |
223 | continue | |
224 | } | |
225 | ||
226 | // Keep track of the destroy nodes that this address | |
227 | // needs to depend on. | |
228 | key := rn.ResourceAddr().String() | |
229 | depMap[key] = append(depMap[key], dns...) | |
230 | } | |
231 | } | |
232 | ||
233 | return depMap, nil | |
234 | } | |
235 | ||
236 | // hasCBDAncestor returns true if any ancestor (node that depends on this) | |
237 | // has CBD set. | |
238 | func (t *CBDEdgeTransformer) hasCBDAncestor(g *Graph, v dag.Vertex) bool { | |
239 | s, _ := g.Ancestors(v) | |
240 | if s == nil { | |
241 | return true | |
242 | } | |
243 | ||
244 | for _, v := range s.List() { | |
245 | dn, ok := v.(GraphNodeDestroyerCBD) | |
246 | if !ok { | |
247 | continue | |
248 | } | |
249 | ||
250 | if dn.CreateBeforeDestroy() { | |
251 | // some ancestor is CreateBeforeDestroy, so we need to follow suit | |
252 | return true | |
253 | } | |
254 | } | |
255 | ||
256 | return false | |
257 | } |