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  }