github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/uniter/uniter.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 "math/rand" 10 "path/filepath" 11 "strings" 12 "sync" 13 14 "github.com/juju/loggo" 15 "launchpad.net/tomb" 16 17 corecharm "launchpad.net/juju-core/charm" 18 "launchpad.net/juju-core/charm/hooks" 19 "launchpad.net/juju-core/environs/config" 20 "launchpad.net/juju-core/juju/osenv" 21 "launchpad.net/juju-core/state/api/params" 22 "launchpad.net/juju-core/state/api/uniter" 23 apiwatcher "launchpad.net/juju-core/state/api/watcher" 24 "launchpad.net/juju-core/state/watcher" 25 "launchpad.net/juju-core/utils/exec" 26 "launchpad.net/juju-core/utils/fslock" 27 "launchpad.net/juju-core/worker/uniter/charm" 28 "launchpad.net/juju-core/worker/uniter/hook" 29 "launchpad.net/juju-core/worker/uniter/relation" 30 ) 31 32 var logger = loggo.GetLogger("juju.worker.uniter") 33 34 const ( 35 // These work fine for linux, but should we need to work with windows 36 // workloads in the future, we'll need to move these into a file that is 37 // compiled conditionally for different targets and use tcp (most likely). 38 39 // gsamfira: On Windows this file will be a text file we will use it to store 40 // the TCP port nr until we implement RPC over named pipes 41 RunListenerFile = "run.socket" 42 ) 43 44 // A UniterExecutionObserver gets the appropriate methods called when a hook 45 // is executed and either succeeds or fails. Missing hooks don't get reported 46 // in this way. 47 type UniterExecutionObserver interface { 48 HookCompleted(hookName string) 49 HookFailed(hookName string) 50 } 51 52 // Uniter implements the capabilities of the unit agent. It is not intended to 53 // implement the actual *behaviour* of the unit agent; that responsibility is 54 // delegated to Mode values, which are expected to react to events and direct 55 // the uniter's responses to them. 56 type Uniter struct { 57 tomb tomb.Tomb 58 st *uniter.State 59 f *filter 60 unit *uniter.Unit 61 service *uniter.Service 62 relationers map[int]*Relationer 63 relationHooks chan hook.Info 64 uuid string 65 envName string 66 67 dataDir string 68 baseDir string 69 toolsDir string 70 relationsDir string 71 charm *charm.GitDir 72 deployer charm.Deployer 73 s *State 74 sf *StateFile 75 rand *rand.Rand 76 hookLock *fslock.Lock 77 runListener *RunListener 78 tcpSock string 79 80 proxy osenv.ProxySettings 81 proxyMutex sync.Mutex 82 83 ranConfigChanged bool 84 // The execution observer is only used in tests at this stage. Should this 85 // need to be extended, perhaps a list of observers would be needed. 86 observer UniterExecutionObserver 87 } 88 89 // NewUniter creates a new Uniter which will install, run, and upgrade 90 // a charm on behalf of the unit with the given unitTag, by executing 91 // hooks and operations provoked by changes in st. 92 func NewUniter(st *uniter.State, unitTag string, dataDir string) *Uniter { 93 u := &Uniter{ 94 st: st, 95 dataDir: dataDir, 96 } 97 go func() { 98 defer u.tomb.Done() 99 u.tomb.Kill(u.loop(unitTag)) 100 }() 101 return u 102 } 103 104 func (u *Uniter) loop(unitTag string) (err error) { 105 if err = u.init(unitTag); err != nil { 106 return err 107 } 108 defer u.runListener.Close() 109 logger.Infof("unit %q started", u.unit) 110 111 environWatcher, err := u.st.WatchForEnvironConfigChanges() 112 if err != nil { 113 return err 114 } 115 defer watcher.Stop(environWatcher, &u.tomb) 116 u.watchForProxyChanges(environWatcher) 117 118 // Start filtering state change events for consumption by modes. 119 u.f, err = newFilter(u.st, unitTag) 120 if err != nil { 121 return err 122 } 123 defer watcher.Stop(u.f, &u.tomb) 124 go func() { 125 u.tomb.Kill(u.f.Wait()) 126 }() 127 128 // Run modes until we encounter an error. 129 mode := ModeInit 130 for err == nil { 131 select { 132 case <-u.tomb.Dying(): 133 err = tomb.ErrDying 134 default: 135 mode, err = mode(u) 136 } 137 } 138 logger.Infof("unit %q shutting down: %s", u.unit, err) 139 return err 140 } 141 142 func (u *Uniter) setupLocks() (err error) { 143 lockDir := filepath.Join(u.dataDir, "locks") 144 u.hookLock, err = fslock.NewLock(lockDir, "uniter-hook-execution") 145 if err != nil { 146 return err 147 } 148 if message := u.hookLock.Message(); u.hookLock.IsLocked() && message != "" { 149 // Look to see if it was us that held the lock before. If it was, we 150 // should be safe enough to break it, as it is likely that we died 151 // before unlocking, and have been restarted by upstart. 152 parts := strings.SplitN(message, ":", 2) 153 if len(parts) > 1 && parts[0] == u.unit.Name() { 154 if err := u.hookLock.BreakLock(); err != nil { 155 return err 156 } 157 } 158 } 159 return nil 160 } 161 162 func (u *Uniter) Kill() { 163 u.tomb.Kill(nil) 164 } 165 166 func (u *Uniter) Wait() error { 167 return u.tomb.Wait() 168 } 169 170 func (u *Uniter) Stop() error { 171 u.tomb.Kill(nil) 172 return u.Wait() 173 } 174 175 func (u *Uniter) Dead() <-chan struct{} { 176 return u.tomb.Dead() 177 } 178 179 // writeState saves uniter state with the supplied values, and infers the appropriate 180 // value of Started. 181 func (u *Uniter) writeState(op Op, step OpStep, hi *hook.Info, url *corecharm.URL) error { 182 s := State{ 183 Started: op == RunHook && hi.Kind == hooks.Start || u.s != nil && u.s.Started, 184 Op: op, 185 OpStep: step, 186 Hook: hi, 187 CharmURL: url, 188 } 189 if err := u.sf.Write(s.Started, s.Op, s.OpStep, s.Hook, s.CharmURL); err != nil { 190 return err 191 } 192 u.s = &s 193 return nil 194 } 195 196 // deploy deploys the supplied charm URL, and sets follow-up hook operation state 197 // as indicated by reason. 198 func (u *Uniter) deploy(curl *corecharm.URL, reason Op) error { 199 if reason != Install && reason != Upgrade { 200 panic(fmt.Errorf("%q is not a deploy operation", reason)) 201 } 202 var hi *hook.Info 203 if u.s != nil && (u.s.Op == RunHook || u.s.Op == Upgrade) { 204 // If this upgrade interrupts a RunHook, we need to preserve the hook 205 // info so that we can return to the appropriate error state. However, 206 // if we're resuming (or have force-interrupted) an Upgrade, we also 207 // need to preserve whatever hook info was preserved when we initially 208 // started upgrading, to ensure we still return to the correct state. 209 hi = u.s.Hook 210 } 211 if u.s == nil || u.s.OpStep != Done { 212 // Get the new charm bundle before announcing intention to use it. 213 logger.Infof("fetching charm %q", curl) 214 sch, err := u.st.Charm(curl) 215 if err != nil { 216 return err 217 } 218 if err = u.deployer.Stage(sch, u.tomb.Dying()); err != nil { 219 return err 220 } 221 222 // Set the new charm URL - this returns when the operation is complete, 223 // at which point we can refresh the local copy of the unit to get a 224 // version with the correct charm URL, and can go ahead and deploy 225 // the charm proper. 226 if err := u.f.SetCharm(curl); err != nil { 227 return err 228 } 229 if err := u.unit.Refresh(); err != nil { 230 return err 231 } 232 logger.Infof("deploying charm %q", curl) 233 if err = u.writeState(reason, Pending, hi, curl); err != nil { 234 return err 235 } 236 if err = u.deployer.Deploy(); err != nil { 237 return err 238 } 239 if err = u.writeState(reason, Done, hi, curl); err != nil { 240 return err 241 } 242 } 243 logger.Infof("charm %q is deployed", curl) 244 status := Queued 245 if hi != nil { 246 // If a hook operation was interrupted, restore it. 247 status = Pending 248 } else { 249 // Otherwise, queue the relevant post-deploy hook. 250 hi = &hook.Info{} 251 switch reason { 252 case Install: 253 hi.Kind = hooks.Install 254 case Upgrade: 255 hi.Kind = hooks.UpgradeCharm 256 } 257 } 258 return u.writeState(RunHook, status, hi, nil) 259 } 260 261 // errHookFailed indicates that a hook failed to execute, but that the Uniter's 262 // operation is not affected by the error. 263 var errHookFailed = stderrors.New("hook execution failed") 264 265 func (u *Uniter) getHookContext(hctxId string, relationId int, remoteUnitName string) (context *HookContext, err error) { 266 267 apiAddrs, err := u.st.APIAddresses() 268 if err != nil { 269 return nil, err 270 } 271 ownerTag, err := u.service.GetOwnerTag() 272 if err != nil { 273 return nil, err 274 } 275 ctxRelations := map[int]*ContextRelation{} 276 for id, r := range u.relationers { 277 ctxRelations[id] = r.Context() 278 } 279 280 u.proxyMutex.Lock() 281 defer u.proxyMutex.Unlock() 282 283 // Make a copy of the proxy settings. 284 proxySettings := u.proxy 285 return NewHookContext(u.unit, hctxId, u.uuid, u.envName, relationId, 286 remoteUnitName, ctxRelations, apiAddrs, ownerTag, proxySettings) 287 } 288 289 func (u *Uniter) acquireHookLock(message string) (err error) { 290 // We want to make sure we don't block forever when locking, but take the 291 // tomb into account. 292 checkTomb := func() error { 293 select { 294 case <-u.tomb.Dying(): 295 return tomb.ErrDying 296 default: 297 // no-op to fall through to return. 298 } 299 return nil 300 } 301 if err = u.hookLock.LockWithFunc(message, checkTomb); err != nil { 302 return err 303 } 304 return nil 305 } 306 307 // RunCommands executes the supplied commands in a hook context. 308 func (u *Uniter) RunCommands(commands string) (results *exec.ExecResponse, err error) { 309 logger.Tracef("run commands: %s", commands) 310 hctxId := fmt.Sprintf("%s:run-commands:%d", u.unit.Name(), u.rand.Int63()) 311 lockMessage := fmt.Sprintf("%s: running commands", u.unit.Name()) 312 if err = u.acquireHookLock(lockMessage); err != nil { 313 return nil, err 314 } 315 defer u.hookLock.Unlock() 316 317 hctx, err := u.getHookContext(hctxId, -1, "") 318 if err != nil { 319 return nil, err 320 } 321 srv, socketPath, err := u.startJujucServer(hctx) 322 if err != nil { 323 return nil, err 324 } 325 defer srv.Close() 326 327 result, err := hctx.RunCommands(commands, u.charm.Path(), u.toolsDir, socketPath) 328 if result != nil { 329 logger.Tracef("run commands: rc=%v\nstdout:\n%sstderr:\n%s", result.Code, result.Stdout, result.Stderr) 330 } 331 return result, err 332 } 333 334 func (u *Uniter) notifyHookInternal(hook string, hctx *HookContext, method func(string)) { 335 if r, ok := hctx.HookRelation(); ok { 336 remote, _ := hctx.RemoteUnitName() 337 if remote != "" { 338 remote = " " + remote 339 } 340 hook = hook + remote + " " + r.FakeId() 341 } 342 method(hook) 343 } 344 345 func (u *Uniter) notifyHookCompleted(hook string, hctx *HookContext) { 346 if u.observer != nil { 347 u.notifyHookInternal(hook, hctx, u.observer.HookCompleted) 348 } 349 } 350 351 func (u *Uniter) notifyHookFailed(hook string, hctx *HookContext) { 352 if u.observer != nil { 353 u.notifyHookInternal(hook, hctx, u.observer.HookFailed) 354 } 355 } 356 357 358 // commitHook ensures that state is consistent with the supplied hook, and 359 // that the fact of the hook's completion is persisted. 360 func (u *Uniter) commitHook(hi hook.Info) error { 361 logger.Infof("committing %q hook", hi.Kind) 362 if hi.Kind.IsRelation() { 363 if err := u.relationers[hi.RelationId].CommitHook(hi); err != nil { 364 return err 365 } 366 if hi.Kind == hooks.RelationBroken { 367 delete(u.relationers, hi.RelationId) 368 } 369 } 370 if hi.Kind == hooks.ConfigChanged { 371 u.ranConfigChanged = true 372 } 373 if err := u.writeState(Continue, Pending, &hi, nil); err != nil { 374 return err 375 } 376 logger.Infof("committed %q hook", hi.Kind) 377 return nil 378 } 379 380 // currentHookName returns the current full hook name. 381 func (u *Uniter) currentHookName() string { 382 hookInfo := u.s.Hook 383 hookName := string(hookInfo.Kind) 384 if hookInfo.Kind.IsRelation() { 385 relationer := u.relationers[hookInfo.RelationId] 386 name := relationer.ru.Endpoint().Name 387 hookName = fmt.Sprintf("%s-%s", name, hookInfo.Kind) 388 } 389 return hookName 390 } 391 392 // restoreRelations reconciles the supplied relation state dirs with the 393 // remote state of the corresponding relations. 394 func (u *Uniter) restoreRelations() error { 395 // TODO(dimitern): Get these from state, not from disk. 396 dirs, err := relation.ReadAllStateDirs(u.relationsDir) 397 if err != nil { 398 return err 399 } 400 for id, dir := range dirs { 401 remove := false 402 rel, err := u.st.RelationById(id) 403 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 404 remove = true 405 } else if err != nil { 406 return err 407 } 408 err = u.addRelation(rel, dir) 409 if params.IsCodeCannotEnterScope(err) { 410 remove = true 411 } else if err != nil { 412 return err 413 } 414 if remove { 415 // If the previous execution was interrupted in the process of 416 // joining or departing the relation, the directory will be empty 417 // and the state is sane. 418 if err := dir.Remove(); err != nil { 419 return fmt.Errorf("cannot synchronize relation state: %v", err) 420 } 421 } 422 } 423 return nil 424 } 425 426 // updateRelations responds to changes in the life states of the relations 427 // with the supplied ids. If any id corresponds to an alive relation not 428 // known to the unit, the uniter will join that relation and return its 429 // relationer in the added list. 430 func (u *Uniter) updateRelations(ids []int) (added []*Relationer, err error) { 431 for _, id := range ids { 432 if r, found := u.relationers[id]; found { 433 rel := r.ru.Relation() 434 if err := rel.Refresh(); err != nil { 435 return nil, fmt.Errorf("cannot update relation %q: %v", rel, err) 436 } 437 if rel.Life() == params.Dying { 438 if err := r.SetDying(); err != nil { 439 return nil, err 440 } else if r.IsImplicit() { 441 delete(u.relationers, id) 442 } 443 } 444 continue 445 } 446 // Relations that are not alive are simply skipped, because they 447 // were not previously known anyway. 448 rel, err := u.st.RelationById(id) 449 if err != nil { 450 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 451 continue 452 } 453 return nil, err 454 } 455 if rel.Life() != params.Alive { 456 continue 457 } 458 // Make sure we ignore relations not implemented by the unit's charm 459 ch, err := corecharm.ReadDir(u.charm.Path()) 460 if err != nil { 461 return nil, err 462 } 463 if ep, err := rel.Endpoint(); err != nil { 464 return nil, err 465 } else if !ep.ImplementedBy(ch) { 466 logger.Warningf("skipping relation with unknown endpoint %q", ep.Name) 467 continue 468 } 469 dir, err := relation.ReadStateDir(u.relationsDir, id) 470 if err != nil { 471 return nil, err 472 } 473 err = u.addRelation(rel, dir) 474 if err == nil { 475 added = append(added, u.relationers[id]) 476 continue 477 } 478 e := dir.Remove() 479 if !params.IsCodeCannotEnterScope(err) { 480 return nil, err 481 } 482 if e != nil { 483 return nil, e 484 } 485 } 486 if ok, err := u.unit.IsPrincipal(); err != nil { 487 return nil, err 488 } else if ok { 489 return added, nil 490 } 491 // If no Alive relations remain between a subordinate unit's service 492 // and its principal's service, the subordinate must become Dying. 493 keepAlive := false 494 for _, r := range u.relationers { 495 scope := r.ru.Endpoint().Scope 496 if scope == corecharm.ScopeContainer && !r.dying { 497 keepAlive = true 498 break 499 } 500 } 501 if !keepAlive { 502 if err := u.unit.Destroy(); err != nil { 503 return nil, err 504 } 505 } 506 return added, nil 507 } 508 509 // addRelation causes the unit agent to join the supplied relation, and to 510 // store persistent state in the supplied dir. 511 func (u *Uniter) addRelation(rel *uniter.Relation, dir *relation.StateDir) error { 512 logger.Infof("joining relation %q", rel) 513 ru, err := rel.Unit(u.unit) 514 if err != nil { 515 return err 516 } 517 r := NewRelationer(ru, dir, u.relationHooks) 518 w, err := u.unit.Watch() 519 if err != nil { 520 return err 521 } 522 defer watcher.Stop(w, &u.tomb) 523 for { 524 select { 525 case <-u.tomb.Dying(): 526 return tomb.ErrDying 527 case _, ok := <-w.Changes(): 528 if !ok { 529 return watcher.MustErr(w) 530 } 531 err := r.Join() 532 if params.IsCodeCannotEnterScopeYet(err) { 533 logger.Infof("cannot enter scope for relation %q; waiting for subordinate to be removed", rel) 534 continue 535 } else if err != nil { 536 return err 537 } 538 logger.Infof("joined relation %q", rel) 539 u.relationers[rel.Id()] = r 540 return nil 541 } 542 } 543 } 544 545 // updatePackageProxy updates the package proxy settings from the 546 // environment. 547 func (u *Uniter) updatePackageProxy(cfg *config.Config) { 548 u.proxyMutex.Lock() 549 defer u.proxyMutex.Unlock() 550 551 newSettings := cfg.ProxySettings() 552 if u.proxy != newSettings { 553 u.proxy = newSettings 554 logger.Debugf("Updated proxy settings: %#v", u.proxy) 555 // Update the environment values used by the process. 556 u.proxy.SetEnvironmentValues() 557 } 558 } 559 560 // watchForProxyChanges kicks off a go routine to listen to the watcher and 561 // update the proxy settings. 562 func (u *Uniter) watchForProxyChanges(environWatcher apiwatcher.NotifyWatcher) { 563 go func() { 564 for { 565 select { 566 case <-u.tomb.Dying(): 567 return 568 case _, ok := <-environWatcher.Changes(): 569 logger.Debugf("new environment change") 570 if !ok { 571 return 572 } 573 environConfig, err := u.st.EnvironConfig() 574 if err != nil { 575 logger.Errorf("cannot load environment configuration: %v", err) 576 } else { 577 u.updatePackageProxy(environConfig) 578 } 579 } 580 } 581 }() 582 }