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