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