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