Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 package uniter | 1 package uniter |
2 | 2 |
3 import ( | 3 import ( |
4 "errors" | 4 "errors" |
5 "fmt" | 5 "fmt" |
6 "launchpad.net/juju-core/environs" | 6 "launchpad.net/juju-core/environs" |
7 "launchpad.net/juju-core/log" | 7 "launchpad.net/juju-core/log" |
8 "launchpad.net/juju-core/state" | 8 "launchpad.net/juju-core/state" |
9 "launchpad.net/juju-core/worker" | |
9 "launchpad.net/juju-core/worker/uniter/charm" | 10 "launchpad.net/juju-core/worker/uniter/charm" |
10 "launchpad.net/juju-core/worker/uniter/hook" | 11 "launchpad.net/juju-core/worker/uniter/hook" |
11 "launchpad.net/tomb" | 12 "launchpad.net/tomb" |
12 ) | 13 ) |
13 | 14 |
14 // Mode defines the signature of the functions that implement the possible | 15 // Mode defines the signature of the functions that implement the possible |
15 // states of a running Uniter. | 16 // states of a running Uniter. |
16 type Mode func(u *Uniter) (Mode, error) | 17 type Mode func(u *Uniter) (Mode, error) |
17 | 18 |
18 // ModeInit is the initial Uniter mode. | 19 // ModeInit is the initial Uniter mode. |
19 func ModeInit(u *Uniter) (next Mode, err error) { | 20 func ModeInit(u *Uniter) (next Mode, err error) { |
20 » defer modeContext("ModeInit", &next, &err)() | 21 » defer modeContext("ModeInit", &err)() |
21 log.Printf("updating unit addresses") | 22 log.Printf("updating unit addresses") |
22 cfg, err := u.st.EnvironConfig() | 23 cfg, err := u.st.EnvironConfig() |
23 if err != nil { | 24 if err != nil { |
24 return nil, err | 25 return nil, err |
25 } | 26 } |
26 provider, err := environs.Provider(cfg.Type()) | 27 provider, err := environs.Provider(cfg.Type()) |
27 if err != nil { | 28 if err != nil { |
28 return nil, err | 29 return nil, err |
29 } | 30 } |
30 if private, err := provider.PrivateAddress(); err != nil { | 31 if private, err := provider.PrivateAddress(); err != nil { |
31 return nil, err | 32 return nil, err |
32 } else if err = u.unit.SetPrivateAddress(private); err != nil { | 33 } else if err = u.unit.SetPrivateAddress(private); err != nil { |
33 return nil, err | 34 return nil, err |
34 } | 35 } |
35 if public, err := provider.PublicAddress(); err != nil { | 36 if public, err := provider.PublicAddress(); err != nil { |
36 return nil, err | 37 return nil, err |
37 } else if err = u.unit.SetPublicAddress(public); err != nil { | 38 } else if err = u.unit.SetPublicAddress(public); err != nil { |
38 return nil, err | 39 return nil, err |
39 } | 40 } |
40 return ModeContinue, nil | 41 return ModeContinue, nil |
41 } | 42 } |
42 | 43 |
43 // ModeContinue determines what action to take based on persistent uniter state. | 44 // ModeContinue determines what action to take based on persistent uniter state. |
44 func ModeContinue(u *Uniter) (next Mode, err error) { | 45 func ModeContinue(u *Uniter) (next Mode, err error) { |
45 » defer modeContext("ModeContinue", &next, &err)() | 46 » defer modeContext("ModeContinue", &err)() |
46 | 47 |
47 // When no charm exists, install it. | 48 // When no charm exists, install it. |
48 s, err := u.sf.Read() | 49 s, err := u.sf.Read() |
49 if err == ErrNoStateFile { | 50 if err == ErrNoStateFile { |
50 log.Printf("charm is not deployed") | 51 log.Printf("charm is not deployed") |
51 sch, _, err := u.service.Charm() | 52 sch, _, err := u.service.Charm() |
52 if err != nil { | 53 if err != nil { |
53 return nil, err | 54 return nil, err |
54 } | 55 } |
55 return ModeInstalling(sch), nil | 56 return ModeInstalling(sch), nil |
56 } else if err != nil { | 57 } else if err != nil { |
57 return nil, err | 58 return nil, err |
58 } | 59 } |
59 | 60 |
60 // Filter out states not related to charm deployment. | 61 // Filter out states not related to charm deployment. |
61 switch s.Op { | 62 switch s.Op { |
62 case Abide: | 63 case Abide: |
63 log.Printf("continuing after %q hook", s.Hook.Kind) | 64 log.Printf("continuing after %q hook", s.Hook.Kind) |
64 switch s.Hook.Kind { | 65 switch s.Hook.Kind { |
65 case hook.Install: | 66 case hook.Install: |
66 return ModeStarting, nil | 67 return ModeStarting, nil |
67 case hook.Stop: | 68 case hook.Stop: |
68 return ModeTerminating, nil | 69 return ModeTerminating, nil |
69 } | 70 } |
70 return ModeAbide, nil | 71 return ModeAbide, nil |
71 case RunHook: | 72 case RunHook: |
72 if s.OpStep == Queued { | 73 if s.OpStep == Queued { |
73 log.Printf("found queued %q hook", s.Hook.Kind) | 74 log.Printf("found queued %q hook", s.Hook.Kind) |
74 » » » return nil, u.runHook(*s.Hook) | 75 » » » if err = u.runHook(*s.Hook); err != nil && err != errHoo kFailed { |
76 » » » » return nil, err | |
77 » » » } | |
78 » » » return ModeContinue, nil | |
75 } | 79 } |
76 if s.OpStep == Done { | 80 if s.OpStep == Done { |
77 log.Printf("found uncommitted %q hook", s.Hook.Kind) | 81 log.Printf("found uncommitted %q hook", s.Hook.Kind) |
78 » » » return nil, u.commitHook(*s.Hook) | 82 » » » if err = u.commitHook(*s.Hook); err != nil { |
83 » » » » return nil, err | |
84 » » » } | |
85 » » » return ModeContinue, nil | |
79 } | 86 } |
80 log.Printf("awaiting error resolution for %q hook", s.Hook.Kind) | 87 log.Printf("awaiting error resolution for %q hook", s.Hook.Kind) |
81 return ModeHookError, nil | 88 return ModeHookError, nil |
82 } | 89 } |
83 | 90 |
84 // Resume interrupted deployment operations. | 91 // Resume interrupted deployment operations. |
85 sch, err := u.st.Charm(s.CharmURL) | 92 sch, err := u.st.Charm(s.CharmURL) |
86 if err != nil { | 93 if err != nil { |
87 return nil, err | 94 return nil, err |
88 } | 95 } |
89 if s.Op == Install { | 96 if s.Op == Install { |
90 log.Printf("resuming charm install") | 97 log.Printf("resuming charm install") |
91 return ModeInstalling(sch), nil | 98 return ModeInstalling(sch), nil |
92 } else if s.Op == Upgrade { | 99 } else if s.Op == Upgrade { |
93 log.Printf("resuming charm upgrade") | 100 log.Printf("resuming charm upgrade") |
94 return ModeUpgrading(sch), nil | 101 return ModeUpgrading(sch), nil |
95 } | 102 } |
96 panic(fmt.Errorf("unhandled uniter operation %q", s.Op)) | 103 panic(fmt.Errorf("unhandled uniter operation %q", s.Op)) |
97 } | 104 } |
98 | 105 |
99 // ModeInstalling is responsible for the initial charm deployment. | 106 // ModeInstalling is responsible for the initial charm deployment. |
100 func ModeInstalling(sch *state.Charm) Mode { | 107 func ModeInstalling(sch *state.Charm) Mode { |
108 name := fmt.Sprintf("ModeInstalling %s", sch.URL()) | |
101 return func(u *Uniter) (next Mode, err error) { | 109 return func(u *Uniter) (next Mode, err error) { |
102 » » name := fmt.Sprintf("ModeInstalling %s", sch.URL()) | 110 » » defer modeContext(name, &err)() |
103 » » defer modeContext(name, &next, &err)() | 111 » » if err = u.deploy(sch, Install); err != nil { |
104 » » return nil, u.deploy(sch, Install) | 112 » » » return nil, err |
113 » » } | |
114 » » return ModeContinue, nil | |
105 } | 115 } |
106 } | 116 } |
107 | 117 |
108 // ModeUpgrading is responsible for upgrading the charm. | 118 // ModeUpgrading is responsible for upgrading the charm. |
109 func ModeUpgrading(sch *state.Charm) Mode { | 119 func ModeUpgrading(sch *state.Charm) Mode { |
120 name := fmt.Sprintf("ModeUpgrading %s", sch.URL()) | |
110 return func(u *Uniter) (next Mode, err error) { | 121 return func(u *Uniter) (next Mode, err error) { |
111 » » name := fmt.Sprintf("ModeUpgrading %s", sch.URL()) | 122 » » defer modeContext(name, &err)() |
112 » » defer modeContext(name, &next, &err)() | |
113 if err = u.deploy(sch, Upgrade); err == charm.ErrConflict { | 123 if err = u.deploy(sch, Upgrade); err == charm.ErrConflict { |
114 return ModeConflicted(sch), nil | 124 return ModeConflicted(sch), nil |
115 » » } | 125 » » } else if err != nil { |
116 » » return nil, err | 126 » » » return nil, err |
127 » » } | |
128 » » return ModeContinue, nil | |
117 } | 129 } |
118 } | 130 } |
119 | 131 |
120 // ModeStarting runs the "start" hook. | 132 // ModeStarting runs the "start" hook. |
121 func ModeStarting(u *Uniter) (next Mode, err error) { | 133 func ModeStarting(u *Uniter) (next Mode, err error) { |
122 » defer modeContext("ModeStarting", &next, &err)() | 134 » defer modeContext("ModeStarting", &err)() |
123 if err = u.unit.SetStatus(state.UnitInstalled, ""); err != nil { | 135 if err = u.unit.SetStatus(state.UnitInstalled, ""); err != nil { |
124 return nil, err | 136 return nil, err |
125 } | 137 } |
126 » return nil, u.runHook(hook.Info{Kind: hook.Start}) | 138 » if err := u.runHook(hook.Info{Kind: hook.Start}); err == errHookFailed { |
139 » » return ModeHookError, nil | |
140 » } else if err != nil { | |
141 » » return nil, err | |
142 » } | |
143 » return ModeContinue, nil | |
127 } | 144 } |
128 | 145 |
129 // ModeStopping runs the "stop" hook. | 146 // ModeStopping runs the "stop" hook. |
130 func ModeStopping(u *Uniter) (next Mode, err error) { | 147 func ModeStopping(u *Uniter) (next Mode, err error) { |
131 » defer modeContext("ModeStopping", &next, &err)() | 148 » defer modeContext("ModeStopping", &err)() |
132 » return nil, u.runHook(hook.Info{Kind: hook.Stop}) | 149 » if err := u.runHook(hook.Info{Kind: hook.Stop}); err == errHookFailed { |
150 » » return ModeHookError, nil | |
151 » } else if err != nil { | |
152 » » return nil, err | |
153 » } | |
154 » return ModeContinue, nil | |
133 } | 155 } |
134 | 156 |
135 // ModeTerminating marks the unit dead and returns ErrDead. | 157 // ModeTerminating marks the unit dead and returns ErrDead. |
136 func ModeTerminating(u *Uniter) (next Mode, err error) { | 158 func ModeTerminating(u *Uniter) (next Mode, err error) { |
137 » defer modeContext("ModeTerminating", &next, &err)() | 159 » defer modeContext("ModeTerminating", &err)() |
138 if err = u.unit.SetStatus(state.UnitStopped, ""); err != nil { | 160 if err = u.unit.SetStatus(state.UnitStopped, ""); err != nil { |
139 return nil, err | 161 return nil, err |
140 } | 162 } |
141 if err = u.unit.EnsureDead(); err != nil { | 163 if err = u.unit.EnsureDead(); err != nil { |
142 return nil, err | 164 return nil, err |
143 } | 165 } |
144 » return nil, ErrDead | 166 » return nil, worker.ErrDead |
145 } | 167 } |
146 | 168 |
147 // ModeAbide is the Uniter's usual steady state. It watches for and responds to: | 169 // ModeAbide is the Uniter's usual steady state. It watches for and responds to: |
148 // * service configuration changes | 170 // * service configuration changes |
149 // * charm upgrade requests | 171 // * charm upgrade requests |
150 // * relation changes (not implemented) | 172 // * relation changes (not implemented) |
151 // * unit death | 173 // * unit death |
152 func ModeAbide(u *Uniter) (next Mode, err error) { | 174 func ModeAbide(u *Uniter) (next Mode, err error) { |
153 » defer modeContext("ModeAbide", &next, &err)() | 175 » defer modeContext("ModeAbide", &err)() |
154 s, err := u.sf.Read() | 176 s, err := u.sf.Read() |
155 if err != nil { | 177 if err != nil { |
156 return nil, err | 178 return nil, err |
157 } | 179 } |
158 if s.Op != Abide { | 180 if s.Op != Abide { |
159 return nil, fmt.Errorf("insane uniter state: %#v", s) | 181 return nil, fmt.Errorf("insane uniter state: %#v", s) |
rog
2012/10/01 11:02:58
"insane uniter state; expected Abide, got %#v", s)
| |
160 } | 182 } |
161 if err = u.unit.SetStatus(state.UnitStarted, ""); err != nil { | 183 if err = u.unit.SetStatus(state.UnitStarted, ""); err != nil { |
162 return nil, err | 184 return nil, err |
163 } | 185 } |
164 | 186 |
165 » // Execute an initial config-changed hook regardless of state. | 187 » // Prime the filter by requesting a config event, then handling it. This |
niemeyer
2012/10/01 22:47:33
s/regardless of state//; We only got here after qu
fwereade
2012/10/02 07:01:35
SGTM
| |
188 » // has two purposes: (1) run a config-change hook before doing anything | |
189 » // else and (2) ensure that subsequent config events received actually | |
190 » // correspond to changes relative to the state at the time we first ran | |
191 » // the hook. | |
166 cc := hook.Info{Kind: hook.ConfigChanged} | 192 cc := hook.Info{Kind: hook.ConfigChanged} |
167 » u.wantConfigEvent() | 193 » u.f.WantConfigEvent() |
168 select { | 194 select { |
169 case <-u.Dying(): | 195 case <-u.Dying(): |
170 return nil, tomb.ErrDying | 196 return nil, tomb.ErrDying |
171 » case <-u.configEvents(): | 197 » case <-u.f.ConfigEvents(): |
niemeyer
2012/10/01 22:47:33
It'd be good to have a comment explaining why we h
fwereade
2012/10/02 07:01:35
The rationale is that we want to run one config-ch
| |
172 » » if err = u.runHook(cc); err != nil { | 198 » » if err = u.runHook(cc); err == errHookFailed { |
173 » » » return nil, err | 199 » » » return ModeHookError, nil |
174 » » } | 200 » » } else if err != nil { |
175 » } | 201 » » » return nil, err |
176 | 202 » » } |
177 » // Watch for everything else (including further config changes). | 203 » } |
niemeyer
2012/10/01 22:47:33
d
It's not watching everything else. It's watchin
fwereade
2012/10/02 07:01:35
Ha, yes.
| |
178 » u.wantCharmEvent() | 204 |
205 » url, err := charm.ReadCharmURL(u.charm) | |
206 » if err != nil { | |
207 » » return nil, err | |
208 » } | |
209 » u.f.WantUpgradeEvent(url, false) | |
179 for { | 210 for { |
180 select { | 211 select { |
181 case <-u.Dying(): | 212 case <-u.Dying(): |
182 return nil, tomb.ErrDying | 213 return nil, tomb.ErrDying |
183 » » case <-u.unitDying(): | 214 » » case <-u.f.UnitDying(): |
184 // TODO don't stop until all relations broken. | 215 // TODO don't stop until all relations broken. |
185 return ModeStopping, nil | 216 return ModeStopping, nil |
186 » » case <-u.configEvents(): | 217 » » case <-u.f.ConfigEvents(): |
187 » » » if err = u.runHook(cc); err != nil { | 218 » » » if err = u.runHook(cc); err == errHookFailed { |
188 » » » » return nil, err | 219 » » » » return ModeHookError, nil |
189 » » » } | |
190 » » case ch := <-u.charmEvents(): | |
191 » » » upgrade, err := u.getUpgrade(ch, false) | |
192 » » » if err == errNoUpgrade { | |
193 » » » » continue | |
194 } else if err != nil { | 220 } else if err != nil { |
195 return nil, err | 221 return nil, err |
196 } | 222 } |
223 case upgrade := <-u.f.UpgradeEvents(): | |
197 return ModeUpgrading(upgrade), nil | 224 return ModeUpgrading(upgrade), nil |
198 } | 225 } |
199 } | 226 } |
200 panic("unreachable") | 227 panic("unreachable") |
201 } | 228 } |
202 | 229 |
203 // ModeHookError is responsible for watching and responding to: | 230 // ModeHookError is responsible for watching and responding to: |
204 // * user resolution of hook errors | 231 // * user resolution of hook errors |
205 // * charm upgrade requests | 232 // * charm upgrade requests |
206 func ModeHookError(u *Uniter) (next Mode, err error) { | 233 func ModeHookError(u *Uniter) (next Mode, err error) { |
207 » defer modeContext("ModeHookError", &next, &err)() | 234 » defer modeContext("ModeHookError", &err)() |
208 s, err := u.sf.Read() | 235 s, err := u.sf.Read() |
209 if err != nil { | 236 if err != nil { |
210 return nil, err | 237 return nil, err |
211 } | 238 } |
212 if s.Op != RunHook || s.OpStep != Pending { | 239 if s.Op != RunHook || s.OpStep != Pending { |
213 return nil, fmt.Errorf("insane uniter state: %#v", s) | 240 return nil, fmt.Errorf("insane uniter state: %#v", s) |
214 } | 241 } |
215 msg := fmt.Sprintf("hook failed: %q", s.Hook.Kind) | 242 msg := fmt.Sprintf("hook failed: %q", s.Hook.Kind) |
216 if err = u.unit.SetStatus(state.UnitError, msg); err != nil { | 243 if err = u.unit.SetStatus(state.UnitError, msg); err != nil { |
217 return nil, err | 244 return nil, err |
218 } | 245 } |
219 » resolveHook := getResolveHook(*s.Hook) | 246 » url, err := charm.ReadCharmURL(u.charm) |
220 » u.wantResolvedEvent() | 247 » if err != nil { |
221 » u.wantCharmEvent() | 248 » » return nil, err |
249 » } | |
250 » u.f.WantResolvedEvent() | |
251 » u.f.WantUpgradeEvent(url, true) | |
222 for { | 252 for { |
223 select { | 253 select { |
224 case <-u.Dying(): | 254 case <-u.Dying(): |
225 return nil, tomb.ErrDying | 255 return nil, tomb.ErrDying |
226 » » case rm := <-u.resolvedEvents(): | 256 » » case rm := <-u.f.ResolvedEvents(): |
227 » » » if success, err := u.resolveError(*rm, resolveHook); suc cess { | 257 » » » switch rm { |
228 » » » » return ModeContinue, nil | 258 » » » case state.ResolvedRetryHooks: |
229 » » » } else if err != nil && err != errHookFailed { | 259 » » » » err = u.runHook(*s.Hook) |
230 » » » » return nil, err | 260 » » » case state.ResolvedNoHooks: |
231 » » » } | 261 » » » » err = u.commitHook(*s.Hook) |
232 » » case ch := <-u.charmEvents(): | 262 » » » default: |
233 » » » upgrade, err := u.getUpgrade(ch, true) | 263 » » » » return nil, fmt.Errorf("unknown resolved mode %q ", rm) |
234 » » » if err == errNoUpgrade { | 264 » » » } |
265 » » » if e := u.unit.ClearResolved(); e != nil { | |
266 » » » » return nil, e | |
267 » » » } | |
268 » » » if err == errHookFailed { | |
235 continue | 269 continue |
236 } else if err != nil { | 270 } else if err != nil { |
237 return nil, err | 271 return nil, err |
238 } | 272 } |
273 return ModeContinue, nil | |
274 case upgrade := <-u.f.UpgradeEvents(): | |
239 return ModeUpgrading(upgrade), nil | 275 return ModeUpgrading(upgrade), nil |
240 } | 276 } |
241 } | 277 } |
242 panic("unreachable") | 278 panic("unreachable") |
243 } | 279 } |
244 | 280 |
245 // ModeConflicted is responsible for watching and responding to: | 281 // ModeConflicted is responsible for watching and responding to: |
246 // * user resolution of charm upgrade conflicts | 282 // * user resolution of charm upgrade conflicts |
247 // * forced charm upgrade requests | 283 // * forced charm upgrade requests |
248 func ModeConflicted(sch *state.Charm) Mode { | 284 func ModeConflicted(sch *state.Charm) Mode { |
249 return func(u *Uniter) (next Mode, err error) { | 285 return func(u *Uniter) (next Mode, err error) { |
250 » » defer modeContext("ModeConflicted", &next, &err)() | 286 » » defer modeContext("ModeConflicted", &err)() |
251 if err = u.unit.SetStatus(state.UnitError, "upgrade failed"); er r != nil { | 287 if err = u.unit.SetStatus(state.UnitError, "upgrade failed"); er r != nil { |
252 return nil, err | 288 return nil, err |
253 } | 289 } |
254 » » u.wantResolvedEvent() | 290 » » u.f.WantResolvedEvent() |
255 » » u.wantCharmEvent() | 291 » » u.f.WantUpgradeEvent(sch.URL(), true) |
256 for { | 292 for { |
257 select { | 293 select { |
258 case <-u.Dying(): | 294 case <-u.Dying(): |
259 return nil, tomb.ErrDying | 295 return nil, tomb.ErrDying |
260 » » » case rm := <-u.resolvedEvents(): | 296 » » » case <-u.f.ResolvedEvents(): |
261 » » » » if success, err := u.resolveError(*rm, resolveCo nflict); success { | 297 » » » » err = u.charm.Snapshotf("Upgrade conflict resolv ed.") |
262 » » » » » return ModeUpgrading(sch), nil | 298 » » » » if e := u.unit.ClearResolved(); e != nil { |
263 » » » » } else if err != nil { | 299 » » » » » return nil, e |
300 » » » » } | |
301 » » » » if err != nil { | |
264 return nil, err | 302 return nil, err |
265 } | 303 } |
266 » » » case ch := <-u.charmEvents(): | 304 » » » » return ModeUpgrading(sch), nil |
267 » » » » upgrade, err := u.getUpgrade(ch, true) | 305 » » » case upgrade := <-u.f.UpgradeEvents(): |
268 » » » » if err != nil { | |
269 » » » » » if err == errNoUpgrade { | |
270 » » » » » » continue | |
271 » » » » » } | |
272 » » » » » return nil, err | |
273 » » » » } | |
274 if err := u.charm.Revert(); err != nil { | 306 if err := u.charm.Revert(); err != nil { |
275 return nil, err | 307 return nil, err |
276 } | 308 } |
277 return ModeUpgrading(upgrade), nil | 309 return ModeUpgrading(upgrade), nil |
278 } | 310 } |
279 } | 311 } |
280 panic("unreachable") | 312 panic("unreachable") |
281 } | 313 } |
282 } | 314 } |
283 | 315 |
284 // modeContext returns a function that implements logging and common error | 316 // modeContext returns a function that implements logging and common error |
285 // manipulation for Mode funcs. | 317 // manipulation for Mode funcs. |
286 func modeContext(name string, next *Mode, err *error) func() { | 318 func modeContext(name string, err *error) func() { |
rog
2012/10/01 11:02:58
as discussed online, most/all of this could be mov
niemeyer
2012/10/01 22:47:33
+1 on turning this into straightforward code. Some
| |
287 log.Printf(name + " starting") | 319 log.Printf(name + " starting") |
288 return func() { | 320 return func() { |
289 log.Debugf(name + " exiting") | 321 log.Debugf(name + " exiting") |
290 switch *err { | 322 switch *err { |
291 » » case nil: | 323 » » case nil, tomb.ErrDying, worker.ErrDead: |
292 » » » if *next == nil { | |
niemeyer
2012/10/01 22:47:33
This seems to obscure the logic without benefit. W
| |
293 » » » » *next = ModeContinue | |
294 » » » } | |
295 » » case errHookFailed: | |
296 » » » *next, *err = ModeHookError, nil | |
niemeyer
2012/10/01 22:47:33
Same idea. The state machine is being hidden behin
fwereade
2012/10/02 07:01:35
Good points all. Will drop the ModeContinue bits e
niemeyer
2012/10/02 14:12:59
I'd much prefer having it close to the logic that
| |
297 » » case tomb.ErrDying, ErrDead: | |
298 » » » log.Printf(name + " shutting down") | |
299 default: | 324 default: |
300 *err = errors.New(name + ": " + (*err).Error()) | 325 *err = errors.New(name + ": " + (*err).Error()) |
301 } | 326 } |
302 } | 327 } |
303 } | 328 } |
LEFT | RIGHT |