github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4"
    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/storage"
    23  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    24  )
    25  
    26  var logger = loggo.GetLogger("juju.worker.uniter.context")
    27  var mutex = sync.Mutex{}
    28  
    29  // meterStatus describes the unit's meter status.
    30  type meterStatus struct {
    31  	code string
    32  	info string
    33  }
    34  
    35  // HookContext is the implementation of jujuc.Context.
    36  type HookContext struct {
    37  	unit *uniter.Unit
    38  
    39  	// state is the handle to the uniter State so that HookContext can make
    40  	// API calls on the stateservice.
    41  	// NOTE: We would like to be rid of the fake-remote-Unit and switch
    42  	// over fully to API calls on State.  This adds that ability, but we're
    43  	// not fully there yet.
    44  	state *uniter.State
    45  
    46  	// privateAddress is the cached value of the unit's private
    47  	// address.
    48  	privateAddress string
    49  
    50  	// publicAddress is the cached value of the unit's public
    51  	// address.
    52  	publicAddress string
    53  
    54  	// availabilityzone is the cached value of the unit's availability zone name.
    55  	availabilityzone string
    56  
    57  	// configSettings holds the service configuration.
    58  	configSettings charm.Settings
    59  
    60  	// id identifies the context.
    61  	id string
    62  
    63  	// actionData contains the values relevant to the run of an Action:
    64  	// its tag, its parameters, and its results.
    65  	actionData *ActionData
    66  
    67  	// uuid is the universally unique identifier of the environment.
    68  	uuid string
    69  
    70  	// envName is the human friendly name of the environment.
    71  	envName string
    72  
    73  	// unitName is the human friendly name of the local unit.
    74  	unitName string
    75  
    76  	// relationId identifies the relation for which a relation hook is
    77  	// executing. If it is -1, the context is not running a relation hook;
    78  	// otherwise, its value must be a valid key into the relations map.
    79  	relationId int
    80  
    81  	// remoteUnitName identifies the changing unit of the executing relation
    82  	// hook. It will be empty if the context is not running a relation hook,
    83  	// or if it is running a relation-broken hook.
    84  	remoteUnitName string
    85  
    86  	// relations contains the context for every relation the unit is a member
    87  	// of, keyed on relation id.
    88  	relations map[int]*ContextRelation
    89  
    90  	// apiAddrs contains the API server addresses.
    91  	apiAddrs []string
    92  
    93  	// serviceOwner contains the user tag of the service owner.
    94  	serviceOwner names.UserTag
    95  
    96  	// proxySettings are the current proxy settings that the uniter knows about.
    97  	proxySettings proxy.Settings
    98  
    99  	// metrics are the metrics recorded by calls to add-metric.
   100  	metrics []jujuc.Metric
   101  
   102  	// canAddMetrics specifies whether the hook allows recording metrics.
   103  	canAddMetrics bool
   104  
   105  	// definedMetrics specifies the metrics the charm has defined in its metrics.yaml file.
   106  	definedMetrics *charm.Metrics
   107  
   108  	// meterStatus is the status of the unit's metering.
   109  	meterStatus *meterStatus
   110  
   111  	// pendingPorts contains a list of port ranges to be opened or
   112  	// closed when the current hook is committed.
   113  	pendingPorts map[PortRange]PortRangeInfo
   114  
   115  	// machinePorts contains cached information about all opened port
   116  	// ranges on the unit's assigned machine, mapped to the unit that
   117  	// opened each range and the relevant relation.
   118  	machinePorts map[network.PortRange]params.RelationUnit
   119  
   120  	// assignedMachineTag contains the tag of the unit's assigned
   121  	// machine.
   122  	assignedMachineTag names.MachineTag
   123  
   124  	// process is the process of the command that is being run in the local context,
   125  	// like a juju-run command or a hook
   126  	process *os.Process
   127  
   128  	// rebootPriority tells us when the hook wants to reboot. If rebootPriority is jujuc.RebootNow
   129  	// the hook will be killed and requeued
   130  	rebootPriority jujuc.RebootPriority
   131  
   132  	// storageInstances contains the storageInstances associated with a unit,
   133  	storageInstances []storage.StorageInstance
   134  
   135  	// storageId is the id of the storage instance associated with the running hook.
   136  	storageId string
   137  }
   138  
   139  func (ctx *HookContext) RequestReboot(priority jujuc.RebootPriority) error {
   140  	var err error
   141  	if priority == jujuc.RebootNow {
   142  		// At this point, the hook should be running
   143  		err = ctx.killCharmHook()
   144  	}
   145  
   146  	switch err {
   147  	case nil, ErrNoProcess:
   148  		// ErrNoProcess almost certainly means we are running in debug hooks
   149  		ctx.SetRebootPriority(priority)
   150  	}
   151  	return err
   152  }
   153  
   154  func (ctx *HookContext) GetRebootPriority() jujuc.RebootPriority {
   155  	mutex.Lock()
   156  	defer mutex.Unlock()
   157  	return ctx.rebootPriority
   158  }
   159  
   160  func (ctx *HookContext) SetRebootPriority(priority jujuc.RebootPriority) {
   161  	mutex.Lock()
   162  	defer mutex.Unlock()
   163  	ctx.rebootPriority = priority
   164  }
   165  
   166  func (ctx *HookContext) GetProcess() *os.Process {
   167  	mutex.Lock()
   168  	defer mutex.Unlock()
   169  	return ctx.process
   170  }
   171  
   172  func (ctx *HookContext) SetProcess(process *os.Process) {
   173  	mutex.Lock()
   174  	defer mutex.Unlock()
   175  	ctx.process = process
   176  }
   177  
   178  func (ctx *HookContext) Id() string {
   179  	return ctx.id
   180  }
   181  
   182  func (ctx *HookContext) UnitName() string {
   183  	return ctx.unitName
   184  }
   185  
   186  func (ctx *HookContext) PublicAddress() (string, bool) {
   187  	return ctx.publicAddress, ctx.publicAddress != ""
   188  }
   189  
   190  func (ctx *HookContext) PrivateAddress() (string, bool) {
   191  	return ctx.privateAddress, ctx.privateAddress != ""
   192  }
   193  
   194  func (ctx *HookContext) AvailabilityZone() (string, bool) {
   195  	return ctx.availabilityzone, ctx.availabilityzone != ""
   196  }
   197  
   198  func (ctx *HookContext) HookStorageInstance() (*storage.StorageInstance, bool) {
   199  	return ctx.StorageInstance(ctx.storageId)
   200  }
   201  
   202  func (ctx *HookContext) StorageInstance(storageId string) (*storage.StorageInstance, bool) {
   203  	for _, storageInstance := range ctx.storageInstances {
   204  		if storageInstance.Id == ctx.storageId {
   205  			return &storageInstance, true
   206  		}
   207  	}
   208  	return nil, false
   209  }
   210  
   211  func (ctx *HookContext) OpenPorts(protocol string, fromPort, toPort int) error {
   212  	return tryOpenPorts(
   213  		protocol, fromPort, toPort,
   214  		ctx.unit.Tag(),
   215  		ctx.machinePorts, ctx.pendingPorts,
   216  	)
   217  }
   218  
   219  func (ctx *HookContext) ClosePorts(protocol string, fromPort, toPort int) error {
   220  	return tryClosePorts(
   221  		protocol, fromPort, toPort,
   222  		ctx.unit.Tag(),
   223  		ctx.machinePorts, ctx.pendingPorts,
   224  	)
   225  }
   226  
   227  func (ctx *HookContext) OpenedPorts() []network.PortRange {
   228  	var unitRanges []network.PortRange
   229  	for portRange, relUnit := range ctx.machinePorts {
   230  		if relUnit.Unit == ctx.unit.Tag().String() {
   231  			unitRanges = append(unitRanges, portRange)
   232  		}
   233  	}
   234  	network.SortPortRanges(unitRanges)
   235  	return unitRanges
   236  }
   237  
   238  func (ctx *HookContext) OwnerTag() string {
   239  	return ctx.serviceOwner.String()
   240  }
   241  
   242  func (ctx *HookContext) ConfigSettings() (charm.Settings, error) {
   243  	if ctx.configSettings == nil {
   244  		var err error
   245  		ctx.configSettings, err = ctx.unit.ConfigSettings()
   246  		if err != nil {
   247  			return nil, err
   248  		}
   249  	}
   250  	result := charm.Settings{}
   251  	for name, value := range ctx.configSettings {
   252  		result[name] = value
   253  	}
   254  	return result, nil
   255  }
   256  
   257  // ActionName returns the name of the action.
   258  func (ctx *HookContext) ActionName() (string, error) {
   259  	if ctx.actionData == nil {
   260  		return "", errors.New("not running an action")
   261  	}
   262  	return ctx.actionData.ActionName, nil
   263  }
   264  
   265  // ActionParams simply returns the arguments to the Action.
   266  func (ctx *HookContext) ActionParams() (map[string]interface{}, error) {
   267  	if ctx.actionData == nil {
   268  		return nil, errors.New("not running an action")
   269  	}
   270  	return ctx.actionData.ActionParams, nil
   271  }
   272  
   273  // SetActionMessage sets a message for the Action, usually an error message.
   274  func (ctx *HookContext) SetActionMessage(message string) error {
   275  	if ctx.actionData == nil {
   276  		return errors.New("not running an action")
   277  	}
   278  	ctx.actionData.ResultsMessage = message
   279  	return nil
   280  }
   281  
   282  // SetActionFailed sets the fail state of the action.
   283  func (ctx *HookContext) SetActionFailed() error {
   284  	if ctx.actionData == nil {
   285  		return errors.New("not running an action")
   286  	}
   287  	ctx.actionData.ActionFailed = true
   288  	return nil
   289  }
   290  
   291  // UpdateActionResults inserts new values for use with action-set and
   292  // action-fail.  The results struct will be delivered to the state server
   293  // upon completion of the Action.  It returns an error if not called on an
   294  // Action-containing HookContext.
   295  func (ctx *HookContext) UpdateActionResults(keys []string, value string) error {
   296  	if ctx.actionData == nil {
   297  		return errors.New("not running an action")
   298  	}
   299  	addValueToMap(keys, value, ctx.actionData.ResultsMap)
   300  	return nil
   301  }
   302  
   303  func (ctx *HookContext) HookRelation() (jujuc.ContextRelation, bool) {
   304  	return ctx.Relation(ctx.relationId)
   305  }
   306  
   307  func (ctx *HookContext) RemoteUnitName() (string, bool) {
   308  	return ctx.remoteUnitName, ctx.remoteUnitName != ""
   309  }
   310  
   311  func (ctx *HookContext) Relation(id int) (jujuc.ContextRelation, bool) {
   312  	r, found := ctx.relations[id]
   313  	return r, found
   314  }
   315  
   316  func (ctx *HookContext) RelationIds() []int {
   317  	ids := []int{}
   318  	for id := range ctx.relations {
   319  		ids = append(ids, id)
   320  	}
   321  	return ids
   322  }
   323  
   324  // AddMetrics adds metrics to the hook context.
   325  func (ctx *HookContext) AddMetric(key, value string, created time.Time) error {
   326  	if !ctx.canAddMetrics || ctx.definedMetrics == nil {
   327  		return errors.New("metrics disabled")
   328  	}
   329  	err := ctx.definedMetrics.ValidateMetric(key, value)
   330  	if err != nil {
   331  		return errors.Annotatef(err, "invalid metric %q", key)
   332  	}
   333  	ctx.metrics = append(ctx.metrics, jujuc.Metric{key, value, created})
   334  	return nil
   335  }
   336  
   337  // ActionData returns the context's internal action data. It's meant to be
   338  // transitory; it exists to allow uniter and runner code to keep working as
   339  // it did; it should be considered deprecated, and not used by new clients.
   340  func (c *HookContext) ActionData() (*ActionData, error) {
   341  	if c.actionData == nil {
   342  		return nil, errors.New("not running an action")
   343  	}
   344  	return c.actionData, nil
   345  }
   346  
   347  // HookVars returns an os.Environ-style list of strings necessary to run a hook
   348  // such that it can know what environment it's operating in, and can call back
   349  // into context.
   350  func (context *HookContext) HookVars(paths Paths) []string {
   351  	vars := context.proxySettings.AsEnvironmentValues()
   352  	vars = append(vars,
   353  		"CHARM_DIR="+paths.GetCharmDir(), // legacy, embarrassing
   354  		"JUJU_CHARM_DIR="+paths.GetCharmDir(),
   355  		"JUJU_CONTEXT_ID="+context.id,
   356  		"JUJU_AGENT_SOCKET="+paths.GetJujucSocket(),
   357  		"JUJU_UNIT_NAME="+context.unitName,
   358  		"JUJU_ENV_UUID="+context.uuid,
   359  		"JUJU_ENV_NAME="+context.envName,
   360  		"JUJU_API_ADDRESSES="+strings.Join(context.apiAddrs, " "),
   361  		"JUJU_METER_STATUS="+context.meterStatus.code,
   362  		"JUJU_METER_INFO="+context.meterStatus.info,
   363  		"JUJU_MACHINE_ID="+context.assignedMachineTag.Id(),
   364  		"JUJU_AVAILABILITY_ZONE="+context.availabilityzone,
   365  	)
   366  	if r, found := context.HookRelation(); found {
   367  		vars = append(vars,
   368  			"JUJU_RELATION="+r.Name(),
   369  			"JUJU_RELATION_ID="+r.FakeId(),
   370  			"JUJU_REMOTE_UNIT="+context.remoteUnitName,
   371  		)
   372  	}
   373  	if context.actionData != nil {
   374  		vars = append(vars,
   375  			"JUJU_ACTION_NAME="+context.actionData.ActionName,
   376  			"JUJU_ACTION_UUID="+context.actionData.ActionTag.Id(),
   377  			"JUJU_ACTION_TAG="+context.actionData.ActionTag.String(),
   378  		)
   379  	}
   380  	return append(vars, osDependentEnvVars(paths)...)
   381  }
   382  
   383  func (ctx *HookContext) handleReboot(err *error) {
   384  	logger.Infof("handling reboot")
   385  	rebootPriority := ctx.GetRebootPriority()
   386  	switch rebootPriority {
   387  	case jujuc.RebootSkip:
   388  		return
   389  	case jujuc.RebootAfterHook:
   390  		// Reboot should happen only after hook has finished.
   391  		if *err != nil {
   392  			return
   393  		}
   394  		*err = ErrReboot
   395  	case jujuc.RebootNow:
   396  		*err = ErrRequeueAndReboot
   397  	}
   398  	reqErr := ctx.unit.RequestReboot()
   399  	if reqErr != nil {
   400  		*err = reqErr
   401  	}
   402  }
   403  
   404  func (ctx *HookContext) FlushContext(process string, ctxErr error) (err error) {
   405  	writeChanges := ctxErr == nil
   406  
   407  	// In the case of Actions, handle any errors using finalizeAction.
   408  	if ctx.actionData != nil {
   409  		// If we had an error in err at this point, it's part of the
   410  		// normal behavior of an Action.  Errors which happen during
   411  		// the finalize should be handed back to the uniter.  Close
   412  		// over the existing err, clear it, and only return errors
   413  		// which occur during the finalize, e.g. API call errors.
   414  		defer func(ctxErr error) {
   415  			err = ctx.finalizeAction(ctxErr, err)
   416  		}(ctxErr)
   417  		ctxErr = nil
   418  	} else {
   419  		// TODO(gsamfira): Just for now, reboot will not be supported in actions.
   420  		defer ctx.handleReboot(&err)
   421  	}
   422  
   423  	for id, rctx := range ctx.relations {
   424  		if writeChanges {
   425  			if e := rctx.WriteSettings(); e != nil {
   426  				e = errors.Errorf(
   427  					"could not write settings from %q to relation %d: %v",
   428  					process, id, e,
   429  				)
   430  				logger.Errorf("%v", e)
   431  				if ctxErr == nil {
   432  					ctxErr = e
   433  				}
   434  			}
   435  		}
   436  	}
   437  
   438  	for rangeKey, rangeInfo := range ctx.pendingPorts {
   439  		if writeChanges {
   440  			var e error
   441  			var op string
   442  			if rangeInfo.ShouldOpen {
   443  				e = ctx.unit.OpenPorts(
   444  					rangeKey.Ports.Protocol,
   445  					rangeKey.Ports.FromPort,
   446  					rangeKey.Ports.ToPort,
   447  				)
   448  				op = "open"
   449  			} else {
   450  				e = ctx.unit.ClosePorts(
   451  					rangeKey.Ports.Protocol,
   452  					rangeKey.Ports.FromPort,
   453  					rangeKey.Ports.ToPort,
   454  				)
   455  				op = "close"
   456  			}
   457  			if e != nil {
   458  				e = errors.Annotatef(e, "cannot %s %v", op, rangeKey.Ports)
   459  				logger.Errorf("%v", e)
   460  				if ctxErr == nil {
   461  					ctxErr = e
   462  				}
   463  			}
   464  		}
   465  	}
   466  	if ctxErr != nil {
   467  		return ctxErr
   468  	}
   469  
   470  	// TODO (tasdomas) 2014 09 03: context finalization needs to modified to apply all
   471  	//                             changes in one api call to minimize the risk
   472  	//                             of partial failures.
   473  	if ctx.canAddMetrics && len(ctx.metrics) > 0 {
   474  		if writeChanges {
   475  			metrics := make([]params.Metric, len(ctx.metrics))
   476  			for i, metric := range ctx.metrics {
   477  				metrics[i] = params.Metric{Key: metric.Key, Value: metric.Value, Time: metric.Time}
   478  			}
   479  			if e := ctx.unit.AddMetrics(metrics); e != nil {
   480  				logger.Errorf("%v", e)
   481  				if ctxErr == nil {
   482  					ctxErr = e
   483  				}
   484  			}
   485  		}
   486  		ctx.metrics = nil
   487  	}
   488  
   489  	return ctxErr
   490  }
   491  
   492  // finalizeAction passes back the final status of an Action hook to state.
   493  // It wraps any errors which occurred in normal behavior of the Action run;
   494  // only errors passed in unhandledErr will be returned.
   495  func (ctx *HookContext) finalizeAction(err, unhandledErr error) error {
   496  	// TODO (binary132): synchronize with gsamfira's reboot logic
   497  	message := ctx.actionData.ResultsMessage
   498  	results := ctx.actionData.ResultsMap
   499  	tag := ctx.actionData.ActionTag
   500  	status := params.ActionCompleted
   501  	if ctx.actionData.ActionFailed {
   502  		status = params.ActionFailed
   503  	}
   504  
   505  	// If we had an action error, we'll simply encapsulate it in the response
   506  	// and discard the error state.  Actions should not error the uniter.
   507  	if err != nil {
   508  		message = err.Error()
   509  		if IsMissingHookError(err) {
   510  			message = fmt.Sprintf("action not implemented on unit %q", ctx.unitName)
   511  		}
   512  		status = params.ActionFailed
   513  	}
   514  
   515  	callErr := ctx.state.ActionFinish(tag, status, results, message)
   516  	if callErr != nil {
   517  		unhandledErr = errors.Wrap(unhandledErr, callErr)
   518  	}
   519  	return unhandledErr
   520  }
   521  
   522  // killCharmHook tries to kill the current running charm hook.
   523  func (ctx *HookContext) killCharmHook() error {
   524  	proc := ctx.GetProcess()
   525  	if proc == nil {
   526  		// nothing to kill
   527  		return ErrNoProcess
   528  	}
   529  	logger.Infof("trying to kill context process %d", proc.Pid)
   530  
   531  	tick := time.After(0)
   532  	timeout := time.After(30 * time.Second)
   533  	for {
   534  		// We repeatedly try to kill the process until we fail; this is
   535  		// because we don't control the *Process, and our clients expect
   536  		// to be able to Wait(); so we can't Wait. We could do better,
   537  		//   but not with a single implementation across all platforms.
   538  		// TODO(gsamfira): come up with a better cross-platform approach.
   539  		select {
   540  		case <-tick:
   541  			err := proc.Kill()
   542  			if err != nil {
   543  				logger.Infof("kill returned: %s", err)
   544  				logger.Infof("assuming already killed")
   545  				return nil
   546  			}
   547  		case <-timeout:
   548  			return errors.Errorf("failed to kill context process %d", proc.Pid)
   549  		}
   550  		logger.Infof("waiting for context process %d to die", proc.Pid)
   551  		tick = time.After(100 * time.Millisecond)
   552  	}
   553  }