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