github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/runner/contextfactory.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runner
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"gopkg.in/juju/charm.v5"
    14  	"gopkg.in/juju/charm.v5/hooks"
    15  
    16  	"github.com/juju/juju/api/uniter"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/worker/leadership"
    19  	"github.com/juju/juju/worker/uniter/hook"
    20  	"github.com/juju/juju/worker/uniter/metrics"
    21  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    22  )
    23  
    24  // ContextFactory represents a long-lived object that can create execution contexts
    25  // relevant to a specific unit.
    26  type ContextFactory interface {
    27  	// CommandContext creates a new context for running a juju command.
    28  	CommandContext(commandInfo CommandInfo) (*HookContext, error)
    29  
    30  	// HookContext creates a new context for running a juju hook.
    31  	HookContext(hookInfo hook.Info) (*HookContext, error)
    32  
    33  	// ActionContext creates a new context for running a juju action.
    34  	ActionContext(actionData *ActionData) (*HookContext, error)
    35  }
    36  
    37  // StorageContextAccessor is an interface providing access to StorageContexts
    38  // for a jujuc.Context.
    39  type StorageContextAccessor interface {
    40  
    41  	// StorageTags returns the tags of storage instances attached to
    42  	// the unit.
    43  	StorageTags() []names.StorageTag
    44  
    45  	// Storage returns the jujuc.ContextStorageAttachment with the
    46  	// supplied tag if it was found, and whether it was found.
    47  	Storage(names.StorageTag) (jujuc.ContextStorageAttachment, bool)
    48  }
    49  
    50  // RelationsFunc is used to get snapshots of relation membership at context
    51  // creation time.
    52  type RelationsFunc func() map[int]*RelationInfo
    53  
    54  type contextFactory struct {
    55  	// API connection fields; unit should be deprecated, but isn't yet.
    56  	unit    *uniter.Unit
    57  	state   *uniter.State
    58  	tracker leadership.Tracker
    59  
    60  	// Fields that shouldn't change in a factory's lifetime.
    61  	paths      Paths
    62  	envUUID    string
    63  	envName    string
    64  	machineTag names.MachineTag
    65  	storage    StorageContextAccessor
    66  
    67  	// Callback to get relation state snapshot.
    68  	getRelationInfos RelationsFunc
    69  	relationCaches   map[int]*RelationCache
    70  
    71  	// For generating "unique" context ids.
    72  	rand *rand.Rand
    73  }
    74  
    75  // NewContextFactory returns a ContextFactory capable of creating execution contexts backed
    76  // by the supplied unit's supplied API connection.
    77  func NewContextFactory(
    78  	state *uniter.State,
    79  	unitTag names.UnitTag,
    80  	tracker leadership.Tracker,
    81  	getRelationInfos RelationsFunc,
    82  	storage StorageContextAccessor,
    83  	paths Paths,
    84  ) (
    85  	ContextFactory, error,
    86  ) {
    87  	unit, err := state.Unit(unitTag)
    88  	if err != nil {
    89  		return nil, errors.Trace(err)
    90  	}
    91  	machineTag, err := unit.AssignedMachine()
    92  	if err != nil {
    93  		return nil, errors.Trace(err)
    94  	}
    95  	environment, err := state.Environment()
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	f := &contextFactory{
   100  		unit:             unit,
   101  		state:            state,
   102  		tracker:          tracker,
   103  		paths:            paths,
   104  		envUUID:          environment.UUID(),
   105  		envName:          environment.Name(),
   106  		machineTag:       machineTag,
   107  		getRelationInfos: getRelationInfos,
   108  		relationCaches:   map[int]*RelationCache{},
   109  		storage:          storage,
   110  		rand:             rand.New(rand.NewSource(time.Now().Unix())),
   111  	}
   112  	return f, nil
   113  }
   114  
   115  // newId returns a probably-unique identifier for a new context, containing the
   116  // supplied string.
   117  func (f *contextFactory) newId(name string) string {
   118  	return fmt.Sprintf("%s-%s-%d", f.unit.Name(), name, f.rand.Int63())
   119  }
   120  
   121  // coreContext creates a new context with all unspecialised fields filled in.
   122  func (f *contextFactory) coreContext() (*HookContext, error) {
   123  	leadershipContext := newLeadershipContext(
   124  		f.state.LeadershipSettings,
   125  		f.tracker,
   126  	)
   127  	ctx := &HookContext{
   128  		unit:               f.unit,
   129  		state:              f.state,
   130  		LeadershipContext:  leadershipContext,
   131  		uuid:               f.envUUID,
   132  		envName:            f.envName,
   133  		unitName:           f.unit.Name(),
   134  		assignedMachineTag: f.machineTag,
   135  		relations:          f.getContextRelations(),
   136  		relationId:         -1,
   137  		metricsRecorder:    nil,
   138  		definedMetrics:     nil,
   139  		pendingPorts:       make(map[PortRange]PortRangeInfo),
   140  		storage:            f.storage,
   141  	}
   142  	if err := f.updateContext(ctx); err != nil {
   143  		return nil, err
   144  	}
   145  	return ctx, nil
   146  }
   147  
   148  // ActionContext is part of the ContextFactory interface.
   149  func (f *contextFactory) ActionContext(actionData *ActionData) (*HookContext, error) {
   150  	if actionData == nil {
   151  		return nil, errors.New("nil actionData specified")
   152  	}
   153  	ctx, err := f.coreContext()
   154  	if err != nil {
   155  		return nil, errors.Trace(err)
   156  	}
   157  	ctx.actionData = actionData
   158  	ctx.id = f.newId(actionData.Name)
   159  	return ctx, nil
   160  }
   161  
   162  // HookContext is part of the ContextFactory interface.
   163  func (f *contextFactory) HookContext(hookInfo hook.Info) (*HookContext, error) {
   164  	ctx, err := f.coreContext()
   165  	if err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  	hookName := string(hookInfo.Kind)
   169  	if hookInfo.Kind.IsRelation() {
   170  		ctx.relationId = hookInfo.RelationId
   171  		ctx.remoteUnitName = hookInfo.RemoteUnit
   172  		relation, found := ctx.relations[hookInfo.RelationId]
   173  		if !found {
   174  			return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId)
   175  		}
   176  		if hookInfo.Kind == hooks.RelationDeparted {
   177  			relation.cache.RemoveMember(hookInfo.RemoteUnit)
   178  		} else if hookInfo.RemoteUnit != "" {
   179  			// Clear remote settings cache for changing remote unit.
   180  			relation.cache.InvalidateMember(hookInfo.RemoteUnit)
   181  		}
   182  		hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind)
   183  	}
   184  	if hookInfo.Kind.IsStorage() {
   185  		ctx.storageTag = names.NewStorageTag(hookInfo.StorageId)
   186  		if _, found := ctx.storage.Storage(ctx.storageTag); !found {
   187  			return nil, errors.Errorf("unknown storage id: %v", hookInfo.StorageId)
   188  		}
   189  		storageName, err := names.StorageName(hookInfo.StorageId)
   190  		if err != nil {
   191  			return nil, errors.Trace(err)
   192  		}
   193  		hookName = fmt.Sprintf("%s-%s", storageName, hookName)
   194  	}
   195  	// Metrics are only sent from the collect-metrics hook.
   196  	if hookInfo.Kind == hooks.CollectMetrics {
   197  		ch, err := getCharm(f.paths.GetCharmDir())
   198  		if err != nil {
   199  			return nil, errors.Trace(err)
   200  		}
   201  		ctx.definedMetrics = ch.Metrics()
   202  
   203  		chURL, err := f.unit.CharmURL()
   204  		if err != nil {
   205  			return nil, errors.Trace(err)
   206  		}
   207  
   208  		charmMetrics := map[string]charm.Metric{}
   209  		if ch.Metrics() != nil {
   210  			charmMetrics = ch.Metrics().Metrics
   211  		}
   212  		ctx.metricsRecorder, err = metrics.NewJSONMetricRecorder(
   213  			f.paths.GetMetricsSpoolDir(),
   214  			charmMetrics,
   215  			chURL.String())
   216  		if err != nil {
   217  			return nil, errors.Trace(err)
   218  		}
   219  	}
   220  	ctx.id = f.newId(hookName)
   221  	return ctx, nil
   222  }
   223  
   224  // CommandContext is part of the ContextFactory interface.
   225  func (f *contextFactory) CommandContext(commandInfo CommandInfo) (*HookContext, error) {
   226  	ctx, err := f.coreContext()
   227  	if err != nil {
   228  		return nil, errors.Trace(err)
   229  	}
   230  	relationId, remoteUnitName, err := inferRemoteUnit(ctx.relations, commandInfo)
   231  	if err != nil {
   232  		return nil, errors.Trace(err)
   233  	}
   234  	ctx.relationId = relationId
   235  	ctx.remoteUnitName = remoteUnitName
   236  	ctx.id = f.newId("run-commands")
   237  	return ctx, nil
   238  }
   239  
   240  // getContextRelations updates the factory's relation caches, and uses them
   241  // to construct ContextRelations for a fresh context.
   242  func (f *contextFactory) getContextRelations() map[int]*ContextRelation {
   243  	contextRelations := map[int]*ContextRelation{}
   244  	relationInfos := f.getRelationInfos()
   245  	relationCaches := map[int]*RelationCache{}
   246  	for id, info := range relationInfos {
   247  		relationUnit := info.RelationUnit
   248  		memberNames := info.MemberNames
   249  		cache, found := f.relationCaches[id]
   250  		if found {
   251  			cache.Prune(memberNames)
   252  		} else {
   253  			cache = NewRelationCache(relationUnit.ReadSettings, memberNames)
   254  		}
   255  		relationCaches[id] = cache
   256  		contextRelations[id] = NewContextRelation(relationUnit, cache)
   257  	}
   258  	f.relationCaches = relationCaches
   259  	return contextRelations
   260  }
   261  
   262  // updateContext fills in all unspecialized fields that require an API call to
   263  // discover.
   264  //
   265  // Approximately *every* line of code in this function represents a bug: ie, some
   266  // piece of information we expose to the charm but which we fail to report changes
   267  // to via hooks. Furthermore, the fact that we make multiple API calls at this
   268  // time, rather than grabbing everything we need in one go, is unforgivably yucky.
   269  func (f *contextFactory) updateContext(ctx *HookContext) (err error) {
   270  	defer errors.Trace(err)
   271  
   272  	ctx.apiAddrs, err = f.state.APIAddresses()
   273  	if err != nil {
   274  		return err
   275  	}
   276  	ctx.machinePorts, err = f.state.AllMachinePorts(f.machineTag)
   277  	if err != nil {
   278  		return errors.Trace(err)
   279  	}
   280  
   281  	statusCode, statusInfo, err := f.unit.MeterStatus()
   282  	if err != nil {
   283  		return errors.Annotate(err, "could not retrieve meter status for unit")
   284  	}
   285  	ctx.meterStatus = &meterStatus{
   286  		code: statusCode,
   287  		info: statusInfo,
   288  	}
   289  
   290  	// TODO(fwereade) 23-10-2014 bug 1384572
   291  	// Nothing here should ever be getting the environ config directly.
   292  	environConfig, err := f.state.EnvironConfig()
   293  	if err != nil {
   294  		return err
   295  	}
   296  	ctx.proxySettings = environConfig.ProxySettings()
   297  
   298  	// Calling these last, because there's a potential race: they're not guaranteed
   299  	// to be set in time to be needed for a hook. If they're not, we just leave them
   300  	// unset as we always have; this isn't great but it's about behaviour preservation.
   301  	ctx.publicAddress, err = f.unit.PublicAddress()
   302  	if err != nil && !params.IsCodeNoAddressSet(err) {
   303  		return err
   304  	}
   305  	ctx.privateAddress, err = f.unit.PrivateAddress()
   306  	if err != nil && !params.IsCodeNoAddressSet(err) {
   307  		return err
   308  	}
   309  	return nil
   310  }
   311  
   312  func inferRemoteUnit(rctxs map[int]*ContextRelation, info CommandInfo) (int, string, error) {
   313  	relationId := info.RelationId
   314  	hasRelation := relationId != -1
   315  	remoteUnit := info.RemoteUnitName
   316  	hasRemoteUnit := remoteUnit != ""
   317  
   318  	// Check baseline sanity of remote unit, if supplied.
   319  	if hasRemoteUnit {
   320  		if !names.IsValidUnit(remoteUnit) {
   321  			return -1, "", errors.Errorf(`invalid remote unit: %s`, remoteUnit)
   322  		} else if !hasRelation {
   323  			return -1, "", errors.Errorf("remote unit provided without a relation: %s", remoteUnit)
   324  		}
   325  	}
   326  
   327  	// Check sanity of relation, if supplied, otherwise easy early return.
   328  	if !hasRelation {
   329  		return relationId, remoteUnit, nil
   330  	}
   331  	rctx, found := rctxs[relationId]
   332  	if !found {
   333  		return -1, "", errors.Errorf("unknown relation id: %d", relationId)
   334  	}
   335  
   336  	// Past basic sanity checks; if forced, accept what we're given.
   337  	if info.ForceRemoteUnit {
   338  		return relationId, remoteUnit, nil
   339  	}
   340  
   341  	// Infer an appropriate remote unit if we can.
   342  	possibles := rctx.UnitNames()
   343  	if remoteUnit == "" {
   344  		switch len(possibles) {
   345  		case 0:
   346  			return -1, "", errors.Errorf("cannot infer remote unit in empty relation %d", relationId)
   347  		case 1:
   348  			return relationId, possibles[0], nil
   349  		}
   350  		return -1, "", errors.Errorf("ambiguous remote unit; possibilities are %+v", possibles)
   351  	}
   352  	for _, possible := range possibles {
   353  		if remoteUnit == possible {
   354  			return relationId, remoteUnit, nil
   355  		}
   356  	}
   357  	return -1, "", errors.Errorf("unknown remote unit %s; possibilities are %+v", remoteUnit, possibles)
   358  }