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

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