github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/context.go (about)

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