github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/uniter/runner/context/context.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package context contains the ContextFactory and Context definitions. Context implements 5 // jujuc.Context and is used together with uniter.Runner to run hooks, commands and actions. 6 package context 7 8 import ( 9 "fmt" 10 "os" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/names" 18 "github.com/juju/utils/proxy" 19 "gopkg.in/juju/charm.v6-unstable" 20 21 "github.com/juju/juju/api/uniter" 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/network" 24 "github.com/juju/juju/worker/uniter/runner/jujuc" 25 ) 26 27 // Paths exposes the paths needed by Context. 28 type Paths interface { 29 30 // GetToolsDir returns the filesystem path to the dirctory containing 31 // the hook tool symlinks. 32 GetToolsDir() string 33 34 // GetCharmDir returns the filesystem path to the directory in which 35 // the charm is installed. 36 GetCharmDir() string 37 38 // GetJujucSocket returns the path to the socket used by the hook tools 39 // to communicate back to the executing uniter process. It might be a 40 // filesystem path, or it might be abstract. 41 GetJujucSocket() string 42 43 // GetMetricsSpoolDir returns the path to a metrics spool dir, used 44 // to store metrics recorded during a single hook run. 45 GetMetricsSpoolDir() string 46 } 47 48 var logger = loggo.GetLogger("juju.worker.uniter.context") 49 var mutex = sync.Mutex{} 50 var ErrIsNotLeader = errors.Errorf("this unit is not the leader") 51 52 // meterStatus describes the unit's meter status. 53 type meterStatus struct { 54 code string 55 info string 56 } 57 58 // HookContext is the implementation of jujuc.Context. 59 type HookContext struct { 60 unit *uniter.Unit 61 62 // state is the handle to the uniter State so that HookContext can make 63 // API calls on the stateservice. 64 // NOTE: We would like to be rid of the fake-remote-Unit and switch 65 // over fully to API calls on State. This adds that ability, but we're 66 // not fully there yet. 67 state *uniter.State 68 69 // LeadershipContext supplies several jujuc.Context methods. 70 LeadershipContext 71 72 // privateAddress is the cached value of the unit's private 73 // address. 74 privateAddress string 75 76 // publicAddress is the cached value of the unit's public 77 // address. 78 publicAddress string 79 80 // availabilityzone is the cached value of the unit's availability zone name. 81 availabilityzone string 82 83 // configSettings holds the service configuration. 84 configSettings charm.Settings 85 86 // id identifies the context. 87 id string 88 89 // actionData contains the values relevant to the run of an Action: 90 // its tag, its parameters, and its results. 91 actionData *ActionData 92 93 // uuid is the universally unique identifier of the environment. 94 uuid string 95 96 // envName is the human friendly name of the environment. 97 envName string 98 99 // unitName is the human friendly name of the local unit. 100 unitName string 101 102 // status is the status of the local unit. 103 status *jujuc.StatusInfo 104 105 // relationId identifies the relation for which a relation hook is 106 // executing. If it is -1, the context is not running a relation hook; 107 // otherwise, its value must be a valid key into the relations map. 108 relationId int 109 110 // remoteUnitName identifies the changing unit of the executing relation 111 // hook. It will be empty if the context is not running a relation hook, 112 // or if it is running a relation-broken hook. 113 remoteUnitName string 114 115 // relations contains the context for every relation the unit is a member 116 // of, keyed on relation id. 117 relations map[int]*ContextRelation 118 119 // apiAddrs contains the API server addresses. 120 apiAddrs []string 121 122 // proxySettings are the current proxy settings that the uniter knows about. 123 proxySettings proxy.Settings 124 125 // meterStatus is the status of the unit's metering. 126 meterStatus *meterStatus 127 128 // pendingPorts contains a list of port ranges to be opened or 129 // closed when the current hook is committed. 130 pendingPorts map[PortRange]PortRangeInfo 131 132 // machinePorts contains cached information about all opened port 133 // ranges on the unit's assigned machine, mapped to the unit that 134 // opened each range and the relevant relation. 135 machinePorts map[network.PortRange]params.RelationUnit 136 137 // assignedMachineTag contains the tag of the unit's assigned 138 // machine. 139 assignedMachineTag names.MachineTag 140 141 // process is the process of the command that is being run in the local context, 142 // like a juju-run command or a hook 143 process *os.Process 144 145 // rebootPriority tells us when the hook wants to reboot. If rebootPriority is jujuc.RebootNow 146 // the hook will be killed and requeued 147 rebootPriority jujuc.RebootPriority 148 149 // storage provides access to the information about storage attached to the unit. 150 storage StorageContextAccessor 151 152 // storageId is the tag of the storage instance associated with the running hook. 153 storageTag names.StorageTag 154 155 // hasRunSetStatus is true if a call to the status-set was made during the 156 // invocation of a hook. 157 // This attribute is persisted to local uniter state at the end of the hook 158 // execution so that the uniter can ultimately decide if it needs to update 159 // a charm's workload status, or if the charm has already taken care of it. 160 hasRunStatusSet bool 161 162 // storageAddConstraints is a collection of storage constraints 163 // keyed on storage name as specified in the charm. 164 // This collection will be added to the unit on successful 165 // hook run, so the actual add will happen in a flush. 166 storageAddConstraints map[string][]params.StorageConstraints 167 } 168 169 func (ctx *HookContext) RequestReboot(priority jujuc.RebootPriority) error { 170 var err error 171 if priority == jujuc.RebootNow { 172 // At this point, the hook should be running 173 err = ctx.killCharmHook() 174 } 175 176 switch err { 177 case nil, ErrNoProcess: 178 // ErrNoProcess almost certainly means we are running in debug hooks 179 ctx.SetRebootPriority(priority) 180 } 181 return err 182 } 183 184 func (ctx *HookContext) GetRebootPriority() jujuc.RebootPriority { 185 mutex.Lock() 186 defer mutex.Unlock() 187 return ctx.rebootPriority 188 } 189 190 func (ctx *HookContext) SetRebootPriority(priority jujuc.RebootPriority) { 191 mutex.Lock() 192 defer mutex.Unlock() 193 ctx.rebootPriority = priority 194 } 195 196 func (ctx *HookContext) GetProcess() *os.Process { 197 mutex.Lock() 198 defer mutex.Unlock() 199 return ctx.process 200 } 201 202 func (ctx *HookContext) SetProcess(process *os.Process) { 203 mutex.Lock() 204 defer mutex.Unlock() 205 ctx.process = process 206 } 207 208 func (ctx *HookContext) Id() string { 209 return ctx.id 210 } 211 212 func (ctx *HookContext) UnitName() string { 213 return ctx.unitName 214 } 215 216 // UnitStatus will return the status for the current Unit. 217 func (ctx *HookContext) UnitStatus() (*jujuc.StatusInfo, error) { 218 if ctx.status == nil { 219 var err error 220 status, err := ctx.unit.UnitStatus() 221 if err != nil { 222 return nil, err 223 } 224 ctx.status = &jujuc.StatusInfo{ 225 Status: string(status.Status), 226 Info: status.Info, 227 Data: status.Data, 228 } 229 } 230 return ctx.status, nil 231 } 232 233 // ServiceStatus returns the status for the service and all the units on 234 // the service to which this context unit belongs, only if this unit is 235 // the leader. 236 func (ctx *HookContext) ServiceStatus() (jujuc.ServiceStatusInfo, error) { 237 var err error 238 isLeader, err := ctx.IsLeader() 239 if err != nil { 240 return jujuc.ServiceStatusInfo{}, errors.Annotatef(err, "cannot determine leadership") 241 } 242 if !isLeader { 243 return jujuc.ServiceStatusInfo{}, ErrIsNotLeader 244 } 245 service, err := ctx.unit.Service() 246 if err != nil { 247 return jujuc.ServiceStatusInfo{}, errors.Trace(err) 248 } 249 status, err := service.Status(ctx.unit.Name()) 250 if err != nil { 251 return jujuc.ServiceStatusInfo{}, errors.Trace(err) 252 } 253 us := make([]jujuc.StatusInfo, len(status.Units)) 254 i := 0 255 for t, s := range status.Units { 256 us[i] = jujuc.StatusInfo{ 257 Tag: t, 258 Status: string(s.Status), 259 Info: s.Info, 260 Data: s.Data, 261 } 262 i++ 263 } 264 return jujuc.ServiceStatusInfo{ 265 Service: jujuc.StatusInfo{ 266 Tag: service.Tag().String(), 267 Status: string(status.Service.Status), 268 Info: status.Service.Info, 269 Data: status.Service.Data, 270 }, 271 Units: us, 272 }, nil 273 } 274 275 // SetUnitStatus will set the given status for this unit. 276 func (ctx *HookContext) SetUnitStatus(status jujuc.StatusInfo) error { 277 ctx.hasRunStatusSet = true 278 logger.Debugf("[WORKLOAD-STATUS] %s: %s", status.Status, status.Info) 279 return ctx.unit.SetUnitStatus( 280 params.Status(status.Status), 281 status.Info, 282 status.Data, 283 ) 284 } 285 286 // SetServiceStatus will set the given status to the service to which this 287 // unit's belong, only if this unit is the leader. 288 func (ctx *HookContext) SetServiceStatus(status jujuc.StatusInfo) error { 289 logger.Debugf("[SERVICE-STATUS] %s: %s", status.Status, status.Info) 290 isLeader, err := ctx.IsLeader() 291 if err != nil { 292 return errors.Annotatef(err, "cannot determine leadership") 293 } 294 if !isLeader { 295 return ErrIsNotLeader 296 } 297 298 service, err := ctx.unit.Service() 299 if err != nil { 300 return errors.Trace(err) 301 } 302 return service.SetStatus( 303 ctx.unit.Name(), 304 params.Status(status.Status), 305 status.Info, 306 status.Data, 307 ) 308 } 309 310 func (ctx *HookContext) HasExecutionSetUnitStatus() bool { 311 return ctx.hasRunStatusSet 312 } 313 314 func (ctx *HookContext) ResetExecutionSetUnitStatus() { 315 ctx.hasRunStatusSet = false 316 } 317 318 func (ctx *HookContext) PublicAddress() (string, error) { 319 if ctx.publicAddress == "" { 320 return "", errors.NotFoundf("public address") 321 } 322 return ctx.publicAddress, nil 323 } 324 325 func (ctx *HookContext) PrivateAddress() (string, error) { 326 if ctx.privateAddress == "" { 327 return "", errors.NotFoundf("private address") 328 } 329 return ctx.privateAddress, nil 330 } 331 332 func (ctx *HookContext) AvailabilityZone() (string, error) { 333 if ctx.availabilityzone == "" { 334 return "", errors.NotFoundf("availability zone") 335 } 336 return ctx.availabilityzone, nil 337 } 338 339 func (ctx *HookContext) StorageTags() ([]names.StorageTag, error) { 340 return ctx.storage.StorageTags() 341 } 342 343 func (ctx *HookContext) HookStorage() (jujuc.ContextStorageAttachment, error) { 344 return ctx.Storage(ctx.storageTag) 345 } 346 347 func (ctx *HookContext) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, error) { 348 return ctx.storage.Storage(tag) 349 } 350 351 func (ctx *HookContext) AddUnitStorage(cons map[string]params.StorageConstraints) error { 352 // All storage constraints are accumulated before context is flushed. 353 if ctx.storageAddConstraints == nil { 354 ctx.storageAddConstraints = make( 355 map[string][]params.StorageConstraints, 356 len(cons)) 357 } 358 for storage, newConstraints := range cons { 359 // Multiple calls for the same storage are accumulated as well. 360 ctx.storageAddConstraints[storage] = append( 361 ctx.storageAddConstraints[storage], 362 newConstraints) 363 } 364 return nil 365 } 366 367 func (ctx *HookContext) OpenPorts(protocol string, fromPort, toPort int) error { 368 return tryOpenPorts( 369 protocol, fromPort, toPort, 370 ctx.unit.Tag(), 371 ctx.machinePorts, ctx.pendingPorts, 372 ) 373 } 374 375 func (ctx *HookContext) ClosePorts(protocol string, fromPort, toPort int) error { 376 return tryClosePorts( 377 protocol, fromPort, toPort, 378 ctx.unit.Tag(), 379 ctx.machinePorts, ctx.pendingPorts, 380 ) 381 } 382 383 func (ctx *HookContext) OpenedPorts() []network.PortRange { 384 var unitRanges []network.PortRange 385 for portRange, relUnit := range ctx.machinePorts { 386 if relUnit.Unit == ctx.unit.Tag().String() { 387 unitRanges = append(unitRanges, portRange) 388 } 389 } 390 network.SortPortRanges(unitRanges) 391 return unitRanges 392 } 393 394 func (ctx *HookContext) ConfigSettings() (charm.Settings, error) { 395 if ctx.configSettings == nil { 396 var err error 397 ctx.configSettings, err = ctx.unit.ConfigSettings() 398 if err != nil { 399 return nil, err 400 } 401 } 402 result := charm.Settings{} 403 for name, value := range ctx.configSettings { 404 result[name] = value 405 } 406 return result, nil 407 } 408 409 // ActionName returns the name of the action. 410 func (ctx *HookContext) ActionName() (string, error) { 411 if ctx.actionData == nil { 412 return "", errors.New("not running an action") 413 } 414 return ctx.actionData.Name, nil 415 } 416 417 // ActionParams simply returns the arguments to the Action. 418 func (ctx *HookContext) ActionParams() (map[string]interface{}, error) { 419 if ctx.actionData == nil { 420 return nil, errors.New("not running an action") 421 } 422 return ctx.actionData.Params, nil 423 } 424 425 // SetActionMessage sets a message for the Action, usually an error message. 426 func (ctx *HookContext) SetActionMessage(message string) error { 427 if ctx.actionData == nil { 428 return errors.New("not running an action") 429 } 430 ctx.actionData.ResultsMessage = message 431 return nil 432 } 433 434 // SetActionFailed sets the fail state of the action. 435 func (ctx *HookContext) SetActionFailed() error { 436 if ctx.actionData == nil { 437 return errors.New("not running an action") 438 } 439 ctx.actionData.Failed = true 440 return nil 441 } 442 443 // UpdateActionResults inserts new values for use with action-set and 444 // action-fail. The results struct will be delivered to the state server 445 // upon completion of the Action. It returns an error if not called on an 446 // Action-containing HookContext. 447 func (ctx *HookContext) UpdateActionResults(keys []string, value string) error { 448 if ctx.actionData == nil { 449 return errors.New("not running an action") 450 } 451 addValueToMap(keys, value, ctx.actionData.ResultsMap) 452 return nil 453 } 454 455 func (ctx *HookContext) HookRelation() (jujuc.ContextRelation, error) { 456 return ctx.Relation(ctx.relationId) 457 } 458 459 func (ctx *HookContext) RemoteUnitName() (string, error) { 460 if ctx.remoteUnitName == "" { 461 return "", errors.NotFoundf("remote unit") 462 } 463 return ctx.remoteUnitName, nil 464 } 465 466 func (ctx *HookContext) Relation(id int) (jujuc.ContextRelation, error) { 467 r, found := ctx.relations[id] 468 if !found { 469 return nil, errors.NotFoundf("relation") 470 } 471 return r, nil 472 } 473 474 func (ctx *HookContext) RelationIds() ([]int, error) { 475 ids := []int{} 476 for id := range ctx.relations { 477 ids = append(ids, id) 478 } 479 return ids, nil 480 } 481 482 // AddMetric adds metrics to the hook context. 483 func (ctx *HookContext) AddMetric(key, value string, created time.Time) error { 484 return errors.New("metrics not allowed in this context") 485 } 486 487 // ActionData returns the context's internal action data. It's meant to be 488 // transitory; it exists to allow uniter and runner code to keep working as 489 // it did; it should be considered deprecated, and not used by new clients. 490 func (c *HookContext) ActionData() (*ActionData, error) { 491 if c.actionData == nil { 492 return nil, errors.New("not running an action") 493 } 494 return c.actionData, nil 495 } 496 497 // HookVars returns an os.Environ-style list of strings necessary to run a hook 498 // such that it can know what environment it's operating in, and can call back 499 // into context. 500 func (context *HookContext) HookVars(paths Paths) ([]string, error) { 501 vars := context.proxySettings.AsEnvironmentValues() 502 vars = append(vars, 503 "CHARM_DIR="+paths.GetCharmDir(), // legacy, embarrassing 504 "JUJU_CHARM_DIR="+paths.GetCharmDir(), 505 "JUJU_CONTEXT_ID="+context.id, 506 "JUJU_AGENT_SOCKET="+paths.GetJujucSocket(), 507 "JUJU_UNIT_NAME="+context.unitName, 508 "JUJU_ENV_UUID="+context.uuid, 509 "JUJU_ENV_NAME="+context.envName, 510 "JUJU_API_ADDRESSES="+strings.Join(context.apiAddrs, " "), 511 "JUJU_METER_STATUS="+context.meterStatus.code, 512 "JUJU_METER_INFO="+context.meterStatus.info, 513 "JUJU_MACHINE_ID="+context.assignedMachineTag.Id(), 514 "JUJU_AVAILABILITY_ZONE="+context.availabilityzone, 515 ) 516 if r, err := context.HookRelation(); err == nil { 517 vars = append(vars, 518 "JUJU_RELATION="+r.Name(), 519 "JUJU_RELATION_ID="+r.FakeId(), 520 "JUJU_REMOTE_UNIT="+context.remoteUnitName, 521 ) 522 } else if !errors.IsNotFound(err) { 523 return nil, errors.Trace(err) 524 } 525 if context.actionData != nil { 526 vars = append(vars, 527 "JUJU_ACTION_NAME="+context.actionData.Name, 528 "JUJU_ACTION_UUID="+context.actionData.Tag.Id(), 529 "JUJU_ACTION_TAG="+context.actionData.Tag.String(), 530 ) 531 } 532 return append(vars, OSDependentEnvVars(paths)...), nil 533 } 534 535 func (ctx *HookContext) handleReboot(err *error) { 536 logger.Infof("handling reboot") 537 rebootPriority := ctx.GetRebootPriority() 538 switch rebootPriority { 539 case jujuc.RebootSkip: 540 return 541 case jujuc.RebootAfterHook: 542 // Reboot should happen only after hook has finished. 543 if *err != nil { 544 return 545 } 546 *err = ErrReboot 547 case jujuc.RebootNow: 548 *err = ErrRequeueAndReboot 549 } 550 err2 := ctx.unit.SetUnitStatus(params.StatusRebooting, "", nil) 551 if err2 != nil { 552 logger.Errorf("updating agent status: %v", err2) 553 } 554 reqErr := ctx.unit.RequestReboot() 555 if reqErr != nil { 556 *err = reqErr 557 } 558 } 559 560 // Prepare implements the Context interface. 561 func (ctx *HookContext) Prepare() error { 562 if ctx.actionData != nil { 563 err := ctx.state.ActionBegin(ctx.actionData.Tag) 564 if err != nil { 565 return errors.Trace(err) 566 } 567 } 568 return nil 569 } 570 571 // Flush implements the Context interface. 572 func (ctx *HookContext) Flush(process string, ctxErr error) (err error) { 573 writeChanges := ctxErr == nil 574 575 // In the case of Actions, handle any errors using finalizeAction. 576 if ctx.actionData != nil { 577 // If we had an error in err at this point, it's part of the 578 // normal behavior of an Action. Errors which happen during 579 // the finalize should be handed back to the uniter. Close 580 // over the existing err, clear it, and only return errors 581 // which occur during the finalize, e.g. API call errors. 582 defer func(ctxErr error) { 583 err = ctx.finalizeAction(ctxErr, err) 584 }(ctxErr) 585 ctxErr = nil 586 } else { 587 // TODO(gsamfira): Just for now, reboot will not be supported in actions. 588 defer ctx.handleReboot(&err) 589 } 590 591 for id, rctx := range ctx.relations { 592 if writeChanges { 593 if e := rctx.WriteSettings(); e != nil { 594 e = errors.Errorf( 595 "could not write settings from %q to relation %d: %v", 596 process, id, e, 597 ) 598 logger.Errorf("%v", e) 599 if ctxErr == nil { 600 ctxErr = e 601 } 602 } 603 } 604 } 605 606 for rangeKey, rangeInfo := range ctx.pendingPorts { 607 if writeChanges { 608 var e error 609 var op string 610 if rangeInfo.ShouldOpen { 611 e = ctx.unit.OpenPorts( 612 rangeKey.Ports.Protocol, 613 rangeKey.Ports.FromPort, 614 rangeKey.Ports.ToPort, 615 ) 616 op = "open" 617 } else { 618 e = ctx.unit.ClosePorts( 619 rangeKey.Ports.Protocol, 620 rangeKey.Ports.FromPort, 621 rangeKey.Ports.ToPort, 622 ) 623 op = "close" 624 } 625 if e != nil { 626 e = errors.Annotatef(e, "cannot %s %v", op, rangeKey.Ports) 627 logger.Errorf("%v", e) 628 if ctxErr == nil { 629 ctxErr = e 630 } 631 } 632 } 633 } 634 635 // add storage to unit dynamically 636 if len(ctx.storageAddConstraints) > 0 && writeChanges { 637 err := ctx.unit.AddStorage(ctx.storageAddConstraints) 638 if err != nil { 639 err = errors.Annotatef(err, "cannot add storage") 640 logger.Errorf("%v", err) 641 if ctxErr == nil { 642 ctxErr = err 643 } 644 } 645 } 646 647 // TODO (tasdomas) 2014 09 03: context finalization needs to modified to apply all 648 // changes in one api call to minimize the risk 649 // of partial failures. 650 651 if !writeChanges { 652 return ctxErr 653 } 654 655 return ctxErr 656 } 657 658 // finalizeAction passes back the final status of an Action hook to state. 659 // It wraps any errors which occurred in normal behavior of the Action run; 660 // only errors passed in unhandledErr will be returned. 661 func (ctx *HookContext) finalizeAction(err, unhandledErr error) error { 662 // TODO (binary132): synchronize with gsamfira's reboot logic 663 message := ctx.actionData.ResultsMessage 664 results := ctx.actionData.ResultsMap 665 tag := ctx.actionData.Tag 666 status := params.ActionCompleted 667 if ctx.actionData.Failed { 668 status = params.ActionFailed 669 } 670 671 // If we had an action error, we'll simply encapsulate it in the response 672 // and discard the error state. Actions should not error the uniter. 673 if err != nil { 674 message = err.Error() 675 if IsMissingHookError(err) { 676 message = fmt.Sprintf("action not implemented on unit %q", ctx.unitName) 677 } 678 status = params.ActionFailed 679 } 680 681 callErr := ctx.state.ActionFinish(tag, status, results, message) 682 if callErr != nil { 683 unhandledErr = errors.Wrap(unhandledErr, callErr) 684 } 685 return unhandledErr 686 } 687 688 // killCharmHook tries to kill the current running charm hook. 689 func (ctx *HookContext) killCharmHook() error { 690 proc := ctx.GetProcess() 691 if proc == nil { 692 // nothing to kill 693 return ErrNoProcess 694 } 695 logger.Infof("trying to kill context process %d", proc.Pid) 696 697 tick := time.After(0) 698 timeout := time.After(30 * time.Second) 699 for { 700 // We repeatedly try to kill the process until we fail; this is 701 // because we don't control the *Process, and our clients expect 702 // to be able to Wait(); so we can't Wait. We could do better, 703 // but not with a single implementation across all platforms. 704 // TODO(gsamfira): come up with a better cross-platform approach. 705 select { 706 case <-tick: 707 err := proc.Kill() 708 if err != nil { 709 logger.Infof("kill returned: %s", err) 710 logger.Infof("assuming already killed") 711 return nil 712 } 713 case <-timeout: 714 return errors.Errorf("failed to kill context process %d", proc.Pid) 715 } 716 logger.Infof("waiting for context process %d to die", proc.Pid) 717 tick = time.After(100 * time.Millisecond) 718 } 719 }