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