github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/modes.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 stderrors "errors" 8 "fmt" 9 10 "launchpad.net/tomb" 11 12 "github.com/juju/juju/charm" 13 "github.com/juju/juju/charm/hooks" 14 "github.com/juju/juju/state/api/params" 15 "github.com/juju/juju/state/watcher" 16 "github.com/juju/juju/worker" 17 ucharm "github.com/juju/juju/worker/uniter/charm" 18 "github.com/juju/juju/worker/uniter/hook" 19 ) 20 21 // Mode defines the signature of the functions that implement the possible 22 // states of a running Uniter. 23 type Mode func(u *Uniter) (Mode, error) 24 25 // ModeContinue determines what action to take based on persistent uniter state. 26 func ModeContinue(u *Uniter) (next Mode, err error) { 27 defer modeContext("ModeContinue", &err)() 28 29 // If we haven't yet loaded state, do so. 30 if u.s == nil { 31 logger.Infof("loading uniter state") 32 if u.s, err = u.sf.Read(); err == ErrNoStateFile { 33 // When no state exists, start from scratch. 34 logger.Infof("charm is not deployed") 35 curl, _, err := u.service.CharmURL() 36 if err != nil { 37 return nil, err 38 } 39 return ModeInstalling(curl), nil 40 } else if err != nil { 41 return nil, err 42 } 43 } 44 45 // Filter out states not related to charm deployment. 46 switch u.s.Op { 47 case Continue: 48 logger.Infof("continuing after %q hook", u.s.Hook.Kind) 49 switch u.s.Hook.Kind { 50 case hooks.Stop: 51 return ModeTerminating, nil 52 case hooks.UpgradeCharm: 53 return ModeConfigChanged, nil 54 case hooks.ConfigChanged: 55 if !u.s.Started { 56 return ModeStarting, nil 57 } 58 } 59 if !u.ranConfigChanged { 60 return ModeConfigChanged, nil 61 } 62 return ModeAbide, nil 63 case RunHook: 64 if u.s.OpStep == Queued { 65 logger.Infof("found queued %q hook", u.s.Hook.Kind) 66 if err = u.runHook(*u.s.Hook); err != nil && err != errHookFailed { 67 return nil, err 68 } 69 return ModeContinue, nil 70 } 71 if u.s.OpStep == Done { 72 logger.Infof("found uncommitted %q hook", u.s.Hook.Kind) 73 if err = u.commitHook(*u.s.Hook); err != nil { 74 return nil, err 75 } 76 return ModeContinue, nil 77 } 78 logger.Infof("awaiting error resolution for %q hook", u.s.Hook.Kind) 79 return ModeHookError, nil 80 } 81 82 // Resume interrupted deployment operations. 83 curl := u.s.CharmURL 84 if u.s.Op == Install { 85 logger.Infof("resuming charm install") 86 return ModeInstalling(curl), nil 87 } else if u.s.Op == Upgrade { 88 logger.Infof("resuming charm upgrade") 89 return ModeUpgrading(curl), nil 90 } 91 panic(fmt.Errorf("unhandled uniter operation %q", u.s.Op)) 92 } 93 94 // ModeInstalling is responsible for the initial charm deployment. 95 func ModeInstalling(curl *charm.URL) Mode { 96 name := fmt.Sprintf("ModeInstalling %s", curl) 97 return func(u *Uniter) (next Mode, err error) { 98 defer modeContext(name, &err)() 99 if err = u.deploy(curl, Install); err != nil { 100 return nil, err 101 } 102 return ModeContinue, nil 103 } 104 } 105 106 // ModeUpgrading is responsible for upgrading the charm. 107 func ModeUpgrading(curl *charm.URL) Mode { 108 name := fmt.Sprintf("ModeUpgrading %s", curl) 109 return func(u *Uniter) (next Mode, err error) { 110 defer modeContext(name, &err)() 111 if err = u.deploy(curl, Upgrade); err == ucharm.ErrConflict { 112 return ModeConflicted(curl), nil 113 } else if err != nil { 114 return nil, err 115 } 116 return ModeContinue, nil 117 } 118 } 119 120 // ModeConfigChanged runs the "config-changed" hook. 121 func ModeConfigChanged(u *Uniter) (next Mode, err error) { 122 defer modeContext("ModeConfigChanged", &err)() 123 if !u.s.Started { 124 if err = u.unit.SetStatus(params.StatusInstalled, "", nil); err != nil { 125 return nil, err 126 } 127 } 128 u.f.DiscardConfigEvent() 129 if err := u.runHook(hook.Info{Kind: hooks.ConfigChanged}); err == errHookFailed { 130 return ModeHookError, nil 131 } else if err != nil { 132 return nil, err 133 } 134 return ModeContinue, nil 135 } 136 137 // ModeStarting runs the "start" hook. 138 func ModeStarting(u *Uniter) (next Mode, err error) { 139 defer modeContext("ModeStarting", &err)() 140 if err := u.runHook(hook.Info{Kind: hooks.Start}); err == errHookFailed { 141 return ModeHookError, nil 142 } else if err != nil { 143 return nil, err 144 } 145 return ModeContinue, nil 146 } 147 148 // ModeStopping runs the "stop" hook. 149 func ModeStopping(u *Uniter) (next Mode, err error) { 150 defer modeContext("ModeStopping", &err)() 151 if err := u.runHook(hook.Info{Kind: hooks.Stop}); err == errHookFailed { 152 return ModeHookError, nil 153 } else if err != nil { 154 return nil, err 155 } 156 return ModeContinue, nil 157 } 158 159 // ModeTerminating marks the unit dead and returns ErrTerminateAgent. 160 func ModeTerminating(u *Uniter) (next Mode, err error) { 161 defer modeContext("ModeTerminating", &err)() 162 if err = u.unit.SetStatus(params.StatusStopped, "", nil); err != nil { 163 return nil, err 164 } 165 w, err := u.unit.Watch() 166 if err != nil { 167 return nil, err 168 } 169 defer watcher.Stop(w, &u.tomb) 170 for { 171 select { 172 case <-u.tomb.Dying(): 173 return nil, tomb.ErrDying 174 case _, ok := <-w.Changes(): 175 if !ok { 176 return nil, watcher.MustErr(w) 177 } 178 if err := u.unit.Refresh(); err != nil { 179 return nil, err 180 } 181 if hasSubs, err := u.unit.HasSubordinates(); err != nil { 182 return nil, err 183 } else if hasSubs { 184 continue 185 } 186 // The unit is known to be Dying; so if it didn't have subordinates 187 // just above, it can't acquire new ones before this call. 188 if err := u.unit.EnsureDead(); err != nil { 189 return nil, err 190 } 191 return nil, worker.ErrTerminateAgent 192 } 193 } 194 } 195 196 // ModeAbide is the Uniter's usual steady state. It watches for and responds to: 197 // * service configuration changes 198 // * charm upgrade requests 199 // * relation changes 200 // * unit death 201 func ModeAbide(u *Uniter) (next Mode, err error) { 202 defer modeContext("ModeAbide", &err)() 203 if u.s.Op != Continue { 204 return nil, fmt.Errorf("insane uniter state: %#v", u.s) 205 } 206 if err := u.fixDeployer(); err != nil { 207 return nil, err 208 } 209 if err = u.unit.SetStatus(params.StatusStarted, "", nil); err != nil { 210 return nil, err 211 } 212 u.f.WantUpgradeEvent(false) 213 for _, r := range u.relationers { 214 r.StartHooks() 215 } 216 defer func() { 217 for _, r := range u.relationers { 218 if e := r.StopHooks(); e != nil && err == nil { 219 err = e 220 } 221 } 222 }() 223 select { 224 case <-u.f.UnitDying(): 225 return modeAbideDyingLoop(u) 226 default: 227 } 228 return modeAbideAliveLoop(u) 229 } 230 231 // modeAbideAliveLoop handles all state changes for ModeAbide when the unit 232 // is in an Alive state. 233 func modeAbideAliveLoop(u *Uniter) (Mode, error) { 234 for { 235 hi := hook.Info{} 236 select { 237 case <-u.tomb.Dying(): 238 return nil, tomb.ErrDying 239 case <-u.f.UnitDying(): 240 return modeAbideDyingLoop(u) 241 case <-u.f.ConfigEvents(): 242 hi = hook.Info{Kind: hooks.ConfigChanged} 243 case hi = <-u.relationHooks: 244 case ids := <-u.f.RelationsEvents(): 245 added, err := u.updateRelations(ids) 246 if err != nil { 247 return nil, err 248 } 249 for _, r := range added { 250 r.StartHooks() 251 } 252 continue 253 case curl := <-u.f.UpgradeEvents(): 254 return ModeUpgrading(curl), nil 255 } 256 if err := u.runHook(hi); err == errHookFailed { 257 return ModeHookError, nil 258 } else if err != nil { 259 return nil, err 260 } 261 } 262 } 263 264 // modeAbideDyingLoop handles the proper termination of all relations in 265 // response to a Dying unit. 266 func modeAbideDyingLoop(u *Uniter) (next Mode, err error) { 267 if err := u.unit.Refresh(); err != nil { 268 return nil, err 269 } 270 if err = u.unit.DestroyAllSubordinates(); err != nil { 271 return nil, err 272 } 273 for id, r := range u.relationers { 274 if err := r.SetDying(); err != nil { 275 return nil, err 276 } else if r.IsImplicit() { 277 delete(u.relationers, id) 278 } 279 } 280 for { 281 if len(u.relationers) == 0 { 282 return ModeStopping, nil 283 } 284 hi := hook.Info{} 285 select { 286 case <-u.tomb.Dying(): 287 return nil, tomb.ErrDying 288 case <-u.f.ConfigEvents(): 289 hi = hook.Info{Kind: hooks.ConfigChanged} 290 case hi = <-u.relationHooks: 291 } 292 if err = u.runHook(hi); err == errHookFailed { 293 return ModeHookError, nil 294 } else if err != nil { 295 return nil, err 296 } 297 } 298 } 299 300 // ModeHookError is responsible for watching and responding to: 301 // * user resolution of hook errors 302 // * forced charm upgrade requests 303 func ModeHookError(u *Uniter) (next Mode, err error) { 304 defer modeContext("ModeHookError", &err)() 305 if u.s.Op != RunHook || u.s.OpStep != Pending { 306 return nil, fmt.Errorf("insane uniter state: %#v", u.s) 307 } 308 msg := fmt.Sprintf("hook failed: %q", u.currentHookName()) 309 // Create error information for status. 310 data := params.StatusData{"hook": u.currentHookName()} 311 if u.s.Hook.Kind.IsRelation() { 312 data["relation-id"] = u.s.Hook.RelationId 313 if u.s.Hook.RemoteUnit != "" { 314 data["remote-unit"] = u.s.Hook.RemoteUnit 315 } 316 } 317 if err = u.unit.SetStatus(params.StatusError, msg, data); err != nil { 318 return nil, err 319 } 320 u.f.WantResolvedEvent() 321 u.f.WantUpgradeEvent(true) 322 for { 323 select { 324 case <-u.tomb.Dying(): 325 return nil, tomb.ErrDying 326 case rm := <-u.f.ResolvedEvents(): 327 switch rm { 328 case params.ResolvedRetryHooks: 329 err = u.runHook(*u.s.Hook) 330 case params.ResolvedNoHooks: 331 err = u.commitHook(*u.s.Hook) 332 default: 333 return nil, fmt.Errorf("unknown resolved mode %q", rm) 334 } 335 if e := u.f.ClearResolved(); e != nil { 336 return nil, e 337 } 338 if err == errHookFailed { 339 continue 340 } else if err != nil { 341 return nil, err 342 } 343 return ModeContinue, nil 344 case curl := <-u.f.UpgradeEvents(): 345 return ModeUpgrading(curl), nil 346 } 347 } 348 } 349 350 // ModeConflicted is responsible for watching and responding to: 351 // * user resolution of charm upgrade conflicts 352 // * forced charm upgrade requests 353 func ModeConflicted(curl *charm.URL) Mode { 354 return func(u *Uniter) (next Mode, err error) { 355 defer modeContext("ModeConflicted", &err)() 356 // TODO(mue) Add helpful data here too in later CL. 357 if err = u.unit.SetStatus(params.StatusError, "upgrade failed", nil); err != nil { 358 return nil, err 359 } 360 u.f.WantResolvedEvent() 361 u.f.WantUpgradeEvent(true) 362 select { 363 case <-u.tomb.Dying(): 364 return nil, tomb.ErrDying 365 case curl = <-u.f.UpgradeEvents(): 366 if err := u.deployer.NotifyRevert(); err != nil { 367 return nil, err 368 } 369 // Now the git dir (if it is one) has been reverted, it's safe to 370 // use a manifest deployer to deploy the new charm. 371 if err := u.fixDeployer(); err != nil { 372 return nil, err 373 } 374 case <-u.f.ResolvedEvents(): 375 err = u.deployer.NotifyResolved() 376 if e := u.f.ClearResolved(); e != nil { 377 return nil, e 378 } 379 if err != nil { 380 return nil, err 381 } 382 // We don't fixDeployer at this stage, because we have *no idea* 383 // what (if anything) the user has done to the charm dir before 384 // setting resolved. But the balance of probability is that the 385 // dir is filled with git droppings, that will be considered user 386 // files and hang around forever, so in this case we wait for the 387 // upgrade to complete and fixDeployer in ModeAbide. 388 } 389 return ModeUpgrading(curl), nil 390 } 391 } 392 393 // modeContext returns a function that implements logging and common error 394 // manipulation for Mode funcs. 395 func modeContext(name string, err *error) func() { 396 logger.Infof("%s starting", name) 397 return func() { 398 logger.Debugf("%s exiting", name) 399 switch *err { 400 case nil, tomb.ErrDying, worker.ErrTerminateAgent: 401 default: 402 *err = stderrors.New(name + ": " + (*err).Error()) 403 } 404 } 405 }