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  }