github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/modes.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 "gopkg.in/juju/charm.v4" 12 "gopkg.in/juju/charm.v4/hooks" 13 "launchpad.net/tomb" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/state/watcher" 17 "github.com/juju/juju/worker" 18 ucharm "github.com/juju/juju/worker/uniter/charm" 19 "github.com/juju/juju/worker/uniter/operation" 20 ) 21 22 // Mode defines the signature of the functions that implement the possible 23 // states of a running Uniter. 24 type Mode func(u *Uniter) (Mode, error) 25 26 // ModeContinue determines what action to take based on persistent uniter state. 27 func ModeContinue(u *Uniter) (next Mode, err error) { 28 defer modeContext("ModeContinue", &err)() 29 opState := u.operationState() 30 31 // Resume interrupted deployment operations. 32 if opState.Kind == operation.Install { 33 logger.Infof("resuming charm install") 34 return ModeInstalling(opState.CharmURL) 35 } else if opState.Kind == operation.Upgrade { 36 logger.Infof("resuming charm upgrade") 37 return ModeUpgrading(opState.CharmURL), nil 38 } 39 40 // If we got this far, we should have an installed charm, 41 // so initialize the metrics collector according to what's 42 // currently deployed. 43 if err := u.initializeMetricsCollector(); err != nil { 44 return nil, errors.Trace(err) 45 } 46 47 var creator creator 48 switch opState.Kind { 49 case operation.RunAction: 50 // TODO(fwereade): we *should* handle interrupted actions, and make sure 51 // they're marked as failed, but that's not for now. 52 logger.Infof("found incomplete action %q; ignoring", opState.ActionId) 53 logger.Infof("recommitting prior %q hook", opState.Hook.Kind) 54 creator = newSkipHookOp(*opState.Hook) 55 case operation.RunHook: 56 switch opState.Step { 57 case operation.Pending: 58 logger.Infof("awaiting error resolution for %q hook", opState.Hook.Kind) 59 return ModeHookError, nil 60 case operation.Queued: 61 logger.Infof("found queued %q hook", opState.Hook.Kind) 62 creator = newRunHookOp(*opState.Hook) 63 case operation.Done: 64 logger.Infof("committing %q hook", opState.Hook.Kind) 65 creator = newSkipHookOp(*opState.Hook) 66 } 67 case operation.Continue: 68 logger.Infof("continuing after %q hook", opState.Hook.Kind) 69 if opState.Hook.Kind == hooks.Stop { 70 return ModeTerminating, nil 71 } 72 return ModeAbide, nil 73 default: 74 return nil, errors.Errorf("unknown operation kind %v", opState.Kind) 75 } 76 return continueAfter(u, creator) 77 } 78 79 // ModeInstalling is responsible for the initial charm deployment. 80 func ModeInstalling(curl *charm.URL) (next Mode, err error) { 81 name := fmt.Sprintf("ModeInstalling %s", curl) 82 return func(u *Uniter) (next Mode, err error) { 83 defer modeContext(name, &err)() 84 // TODO(fwereade) 2015-01-19 85 // This SetStatus call should probably be inside the operation somehow; 86 // which in turn implies that the SetStatus call in PrepareHook is 87 // also misplaced, and should also be explicitly part of the operation. 88 if err = u.unit.SetStatus(params.StatusInstalling, "", nil); err != nil { 89 return nil, errors.Trace(err) 90 } 91 return continueAfter(u, newInstallOp(curl)) 92 }, nil 93 } 94 95 // ModeUpgrading is responsible for upgrading the charm. 96 func ModeUpgrading(curl *charm.URL) Mode { 97 name := fmt.Sprintf("ModeUpgrading %s", curl) 98 return func(u *Uniter) (next Mode, err error) { 99 defer modeContext(name, &err)() 100 // TODO(fwereade) 2015-01-19 101 // If we encoded the failed charm URL in ErrConflict -- or alternatively 102 // if we recorded a bit more info in operation.State -- we could move this 103 // code into the error->mode transform in Uniter.loop(). 104 err = u.runOperation(newUpgradeOp(curl)) 105 if errors.Cause(err) == ucharm.ErrConflict { 106 return ModeConflicted(curl), nil 107 } else if err != nil { 108 return nil, errors.Trace(err) 109 } 110 return ModeContinue, nil 111 } 112 } 113 114 // ModeTerminating marks the unit dead and returns ErrTerminateAgent. 115 func ModeTerminating(u *Uniter) (next Mode, err error) { 116 defer modeContext("ModeTerminating", &err)() 117 if err = u.unit.SetStatus(params.StatusStopping, "", nil); err != nil { 118 return nil, errors.Trace(err) 119 } 120 w, err := u.unit.Watch() 121 if err != nil { 122 return nil, errors.Trace(err) 123 } 124 defer watcher.Stop(w, &u.tomb) 125 for { 126 select { 127 case <-u.tomb.Dying(): 128 return nil, tomb.ErrDying 129 case info := <-u.f.ActionEvents(): 130 creator := newActionOp(info.ActionId) 131 if err := u.runOperation(creator); err != nil { 132 return nil, errors.Trace(err) 133 } 134 case _, ok := <-w.Changes(): 135 if !ok { 136 return nil, watcher.EnsureErr(w) 137 } 138 if err := u.unit.Refresh(); err != nil { 139 return nil, errors.Trace(err) 140 } 141 if hasSubs, err := u.unit.HasSubordinates(); err != nil { 142 return nil, errors.Trace(err) 143 } else if hasSubs { 144 continue 145 } 146 // The unit is known to be Dying; so if it didn't have subordinates 147 // just above, it can't acquire new ones before this call. 148 if err := u.unit.EnsureDead(); err != nil { 149 return nil, errors.Trace(err) 150 } 151 return nil, worker.ErrTerminateAgent 152 } 153 } 154 } 155 156 // ModeAbide is the Uniter's usual steady state. It watches for and responds to: 157 // * service configuration changes 158 // * charm upgrade requests 159 // * relation changes 160 // * unit death 161 func ModeAbide(u *Uniter) (next Mode, err error) { 162 defer modeContext("ModeAbide", &err)() 163 opState := u.operationState() 164 if opState.Kind != operation.Continue { 165 return nil, errors.Errorf("insane uniter state: %#v", opState) 166 } 167 if err := u.deployer.Fix(); err != nil { 168 return nil, errors.Trace(err) 169 } 170 if !u.ranConfigChanged { 171 return continueAfter(u, newSimpleRunHookOp(hooks.ConfigChanged)) 172 } 173 if !opState.Started { 174 return continueAfter(u, newSimpleRunHookOp(hooks.Start)) 175 } 176 if err = u.unit.SetStatus(params.StatusActive, "", nil); err != nil { 177 return nil, errors.Trace(err) 178 } 179 u.f.WantUpgradeEvent(false) 180 u.relations.StartHooks() 181 defer func() { 182 if e := u.relations.StopHooks(); e != nil { 183 if err == nil { 184 err = e 185 } else { 186 logger.Errorf("error while stopping hooks: %v", e) 187 } 188 } 189 }() 190 191 select { 192 case <-u.f.UnitDying(): 193 return modeAbideDyingLoop(u) 194 default: 195 } 196 return modeAbideAliveLoop(u) 197 } 198 199 // modeAbideAliveLoop handles all state changes for ModeAbide when the unit 200 // is in an Alive state. 201 func modeAbideAliveLoop(u *Uniter) (Mode, error) { 202 for { 203 lastCollectMetrics := time.Unix(u.operationState().CollectMetricsTime, 0) 204 collectMetricsSignal := u.collectMetricsAt( 205 time.Now(), lastCollectMetrics, metricsPollInterval, 206 ) 207 var creator creator 208 select { 209 case <-u.tomb.Dying(): 210 return nil, tomb.ErrDying 211 case <-u.f.UnitDying(): 212 return modeAbideDyingLoop(u) 213 case curl := <-u.f.UpgradeEvents(): 214 return ModeUpgrading(curl), nil 215 case ids := <-u.f.RelationsEvents(): 216 creator = newUpdateRelationsOp(ids) 217 case info := <-u.f.ActionEvents(): 218 creator = newActionOp(info.ActionId) 219 case <-u.f.ConfigEvents(): 220 creator = newSimpleRunHookOp(hooks.ConfigChanged) 221 case <-u.f.MeterStatusEvents(): 222 creator = newSimpleRunHookOp(hooks.MeterStatusChanged) 223 case <-collectMetricsSignal: 224 creator = newSimpleRunHookOp(hooks.CollectMetrics) 225 case hookInfo := <-u.relations.Hooks(): 226 creator = newRunHookOp(hookInfo) 227 } 228 if err := u.runOperation(creator); err != nil { 229 return nil, errors.Trace(err) 230 } 231 } 232 } 233 234 // modeAbideDyingLoop handles the proper termination of all relations in 235 // response to a Dying unit. 236 func modeAbideDyingLoop(u *Uniter) (next Mode, err error) { 237 if err := u.unit.Refresh(); err != nil { 238 return nil, errors.Trace(err) 239 } 240 if err = u.unit.DestroyAllSubordinates(); err != nil { 241 return nil, errors.Trace(err) 242 } 243 if err := u.relations.SetDying(); err != nil { 244 return nil, errors.Trace(err) 245 } 246 for { 247 if len(u.relations.GetInfo()) == 0 { 248 return continueAfter(u, newSimpleRunHookOp(hooks.Stop)) 249 } 250 var creator creator 251 select { 252 case <-u.tomb.Dying(): 253 return nil, tomb.ErrDying 254 case info := <-u.f.ActionEvents(): 255 creator = newActionOp(info.ActionId) 256 case <-u.f.ConfigEvents(): 257 creator = newSimpleRunHookOp(hooks.ConfigChanged) 258 case hookInfo := <-u.relations.Hooks(): 259 creator = newRunHookOp(hookInfo) 260 } 261 if err := u.runOperation(creator); err != nil { 262 return nil, errors.Trace(err) 263 } 264 } 265 } 266 267 // ModeHookError is responsible for watching and responding to: 268 // * user resolution of hook errors 269 // * forced charm upgrade requests 270 func ModeHookError(u *Uniter) (next Mode, err error) { 271 defer modeContext("ModeHookError", &err)() 272 opState := u.operationState() 273 if opState.Kind != operation.RunHook || opState.Step != operation.Pending { 274 return nil, errors.Errorf("insane uniter state: %#v", u.operationState()) 275 } 276 // Create error information for status. 277 hookInfo := *opState.Hook 278 hookName := string(hookInfo.Kind) 279 statusData := map[string]interface{}{} 280 if hookInfo.Kind.IsRelation() { 281 statusData["relation-id"] = hookInfo.RelationId 282 if hookInfo.RemoteUnit != "" { 283 statusData["remote-unit"] = hookInfo.RemoteUnit 284 } 285 relationName, err := u.relations.Name(hookInfo.RelationId) 286 if err != nil { 287 return nil, errors.Trace(err) 288 } 289 hookName = fmt.Sprintf("%s-%s", relationName, hookInfo.Kind) 290 } 291 statusData["hook"] = hookName 292 statusMessage := fmt.Sprintf("hook failed: %q", hookName) 293 u.f.WantResolvedEvent() 294 u.f.WantUpgradeEvent(true) 295 for { 296 if err = u.unit.SetStatus(params.StatusError, statusMessage, statusData); err != nil { 297 return nil, errors.Trace(err) 298 } 299 select { 300 case <-u.tomb.Dying(): 301 return nil, tomb.ErrDying 302 case curl := <-u.f.UpgradeEvents(): 303 return ModeUpgrading(curl), nil 304 case rm := <-u.f.ResolvedEvents(): 305 var creator creator 306 switch rm { 307 case params.ResolvedRetryHooks: 308 creator = newRetryHookOp(hookInfo) 309 case params.ResolvedNoHooks: 310 creator = newSkipHookOp(hookInfo) 311 default: 312 return nil, errors.Errorf("unknown resolved mode %q", rm) 313 } 314 err := u.runOperation(creator) 315 if errors.Cause(err) == operation.ErrHookFailed { 316 continue 317 } else if err != nil { 318 return nil, errors.Trace(err) 319 } 320 return ModeContinue, nil 321 } 322 } 323 } 324 325 // ModeConflicted is responsible for watching and responding to: 326 // * user resolution of charm upgrade conflicts 327 // * forced charm upgrade requests 328 func ModeConflicted(curl *charm.URL) Mode { 329 return func(u *Uniter) (next Mode, err error) { 330 defer modeContext("ModeConflicted", &err)() 331 // TODO(mue) Add helpful data here too in later CL. 332 if err = u.unit.SetStatus(params.StatusError, "upgrade failed", nil); err != nil { 333 return nil, errors.Trace(err) 334 } 335 u.f.WantResolvedEvent() 336 u.f.WantUpgradeEvent(true) 337 var creator creator 338 select { 339 case <-u.tomb.Dying(): 340 return nil, tomb.ErrDying 341 case curl = <-u.f.UpgradeEvents(): 342 creator = newRevertUpgradeOp(curl) 343 case <-u.f.ResolvedEvents(): 344 creator = newResolvedUpgradeOp(curl) 345 } 346 err = u.runOperation(creator) 347 // TODO(fwereade) 2015-01-19 348 // If we encoded the failed charm URL in ErrConflict -- or alternatively 349 // if we recorded a bit more info in operation.State -- we could move this 350 // code into the error->mode transform in Uniter.loop(). 351 if errors.Cause(err) == ucharm.ErrConflict { 352 return ModeConflicted(curl), nil 353 } else if err != nil { 354 return nil, errors.Trace(err) 355 } 356 return ModeContinue, nil 357 } 358 } 359 360 // modeContext returns a function that implements logging and common error 361 // manipulation for Mode funcs. 362 func modeContext(name string, err *error) func() { 363 logger.Infof("%s starting", name) 364 return func() { 365 logger.Debugf("%s exiting", name) 366 *err = errors.Annotatef(*err, name) 367 } 368 } 369 370 // continueAfter is commonly used at the end of a Mode func to execute the 371 // operation returned by creator and return ModeContinue (or any error). 372 func continueAfter(u *Uniter, creator creator) (Mode, error) { 373 if err := u.runOperation(creator); err != nil { 374 return nil, errors.Trace(err) 375 } 376 return ModeContinue, nil 377 }