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