github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/uniter/runner/factory.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  	"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/runner/jujuc"
    21  )
    22  
    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  // Factory represents a long-lived object that can create execution contexts
    33  // relevant to a specific unit. In its current state, it is somewhat bizarre
    34  // and inconsistent; its main value is as an evolutionary step towards a better
    35  // division of responsibilities across worker/uniter and its subpackages.
    36  type Factory interface {
    37  
    38  	// NewCommandRunner returns an execution context suitable for running
    39  	// an arbitrary script.
    40  	NewCommandRunner(commandInfo CommandInfo) (Runner, error)
    41  
    42  	// NewHookRunner returns an execution context suitable for running the
    43  	// supplied hook definition (which must be valid).
    44  	NewHookRunner(hookInfo hook.Info) (Runner, error)
    45  
    46  	// NewActionRunner returns an execution context suitable for running the
    47  	// action identified by the supplied id.
    48  	NewActionRunner(actionId string) (Runner, error)
    49  }
    50  
    51  // StorageContextAccessor is an interface providing access to StorageContexts
    52  // for a jujuc.Context.
    53  type StorageContextAccessor interface {
    54  
    55  	// Storage returns the jujuc.ContextStorageAttachment with the
    56  	// supplied tag if it was found, and whether it was found.
    57  	Storage(names.StorageTag) (jujuc.ContextStorageAttachment, bool)
    58  }
    59  
    60  // RelationsFunc is used to get snapshots of relation membership at context
    61  // creation time.
    62  type RelationsFunc func() map[int]*RelationInfo
    63  
    64  // NewFactory returns a Factory capable of creating execution contexts backed
    65  // by the supplied unit's supplied API connection.
    66  func NewFactory(
    67  	state *uniter.State,
    68  	unitTag names.UnitTag,
    69  	tracker leadership.Tracker,
    70  	getRelationInfos RelationsFunc,
    71  	storage StorageContextAccessor,
    72  	paths Paths,
    73  ) (
    74  	Factory, error,
    75  ) {
    76  	unit, err := state.Unit(unitTag)
    77  	if err != nil {
    78  		return nil, errors.Trace(err)
    79  	}
    80  	service, err := state.Service(unit.ServiceTag())
    81  	if err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	ownerTag, err := service.OwnerTag()
    85  	if err != nil {
    86  		return nil, errors.Trace(err)
    87  	}
    88  	machineTag, err := unit.AssignedMachine()
    89  	if err != nil {
    90  		return nil, errors.Trace(err)
    91  	}
    92  	environment, err := state.Environment()
    93  	if err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  	return &factory{
    97  		unit:             unit,
    98  		state:            state,
    99  		tracker:          tracker,
   100  		paths:            paths,
   101  		envUUID:          environment.UUID(),
   102  		envName:          environment.Name(),
   103  		machineTag:       machineTag,
   104  		ownerTag:         ownerTag,
   105  		getRelationInfos: getRelationInfos,
   106  		relationCaches:   map[int]*RelationCache{},
   107  		storage:          storage,
   108  		rand:             rand.New(rand.NewSource(time.Now().Unix())),
   109  	}, nil
   110  }
   111  
   112  type factory struct {
   113  	// API connection fields; unit should be deprecated, but isn't yet.
   114  	unit    *uniter.Unit
   115  	state   *uniter.State
   116  	tracker leadership.Tracker
   117  
   118  	// Fields that shouldn't change in a factory's lifetime.
   119  	paths      Paths
   120  	envUUID    string
   121  	envName    string
   122  	machineTag names.MachineTag
   123  	ownerTag   names.UserTag
   124  	storage    StorageContextAccessor
   125  
   126  	// Callback to get relation state snapshot.
   127  	getRelationInfos RelationsFunc
   128  	relationCaches   map[int]*RelationCache
   129  
   130  	// For generating "unique" context ids.
   131  	rand *rand.Rand
   132  }
   133  
   134  // NewCommandRunner exists to satisfy the Factory interface.
   135  func (f *factory) NewCommandRunner(commandInfo CommandInfo) (Runner, error) {
   136  	ctx, err := f.coreContext()
   137  	if err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  	relationId, remoteUnitName, err := inferRemoteUnit(ctx.relations, commandInfo)
   141  	if err != nil {
   142  		return nil, errors.Trace(err)
   143  	}
   144  	ctx.relationId = relationId
   145  	ctx.remoteUnitName = remoteUnitName
   146  	ctx.id = f.newId("run-commands")
   147  	runner := NewRunner(ctx, f.paths)
   148  	return runner, nil
   149  }
   150  
   151  // NewHookRunner exists to satisfy the Factory interface.
   152  func (f *factory) NewHookRunner(hookInfo hook.Info) (Runner, error) {
   153  	if err := hookInfo.Validate(); err != nil {
   154  		return nil, errors.Trace(err)
   155  	}
   156  
   157  	ctx, err := f.coreContext()
   158  	if err != nil {
   159  		return nil, errors.Trace(err)
   160  	}
   161  
   162  	hookName := string(hookInfo.Kind)
   163  	if hookInfo.Kind.IsRelation() {
   164  		ctx.relationId = hookInfo.RelationId
   165  		ctx.remoteUnitName = hookInfo.RemoteUnit
   166  		relation, found := ctx.relations[hookInfo.RelationId]
   167  		if !found {
   168  			return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId)
   169  		}
   170  		if hookInfo.Kind == hooks.RelationDeparted {
   171  			relation.cache.RemoveMember(hookInfo.RemoteUnit)
   172  		} else if hookInfo.RemoteUnit != "" {
   173  			// Clear remote settings cache for changing remote unit.
   174  			relation.cache.InvalidateMember(hookInfo.RemoteUnit)
   175  		}
   176  		hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind)
   177  	}
   178  	if hookInfo.Kind.IsStorage() {
   179  		ctx.storageTag = names.NewStorageTag(hookInfo.StorageId)
   180  		if _, found := ctx.storage.Storage(ctx.storageTag); !found {
   181  			return nil, errors.Errorf("unknown storage id: %v", hookInfo.StorageId)
   182  		}
   183  		storageName, err := names.StorageName(hookInfo.StorageId)
   184  		if err != nil {
   185  			return nil, errors.Trace(err)
   186  		}
   187  		hookName = fmt.Sprintf("%s-%s", storageName, hookName)
   188  	}
   189  	// Metrics are only sent from the collect-metrics hook.
   190  	if hookInfo.Kind == hooks.CollectMetrics {
   191  		ch, err := f.getCharm()
   192  		if err != nil {
   193  			return nil, errors.Trace(err)
   194  		}
   195  		ctx.definedMetrics = ch.Metrics()
   196  
   197  		chURL, err := f.unit.CharmURL()
   198  		if err != nil {
   199  			return nil, errors.Trace(err)
   200  		}
   201  
   202  		ctx.metricsRecorder, err = NewJSONMetricsRecorder(f.paths.GetMetricsSpoolDir(), chURL.String())
   203  		if err != nil {
   204  			return nil, errors.Trace(err)
   205  		}
   206  		ctx.metricsReader, err = NewJSONMetricsReader(f.paths.GetMetricsSpoolDir())
   207  		if err != nil {
   208  			return nil, errors.Trace(err)
   209  		}
   210  	}
   211  	ctx.id = f.newId(hookName)
   212  	runner := NewRunner(ctx, f.paths)
   213  	return runner, nil
   214  }
   215  
   216  // NewActionRunner exists to satisfy the Factory interface.
   217  func (f *factory) NewActionRunner(actionId string) (Runner, error) {
   218  	ch, err := f.getCharm()
   219  	if err != nil {
   220  		return nil, errors.Trace(err)
   221  	}
   222  
   223  	ok := names.IsValidAction(actionId)
   224  	if !ok {
   225  		return nil, &badActionError{actionId, "not valid actionId"}
   226  	}
   227  	tag := names.NewActionTag(actionId)
   228  	action, err := f.state.Action(tag)
   229  	if params.IsCodeNotFoundOrCodeUnauthorized(err) {
   230  		return nil, ErrActionNotAvailable
   231  	} else if params.IsCodeActionNotAvailable(err) {
   232  		return nil, ErrActionNotAvailable
   233  	} else if err != nil {
   234  		return nil, errors.Trace(err)
   235  	}
   236  
   237  	err = f.state.ActionBegin(tag)
   238  	if err != nil {
   239  		return nil, errors.Trace(err)
   240  	}
   241  
   242  	name := action.Name()
   243  	spec, ok := ch.Actions().ActionSpecs[name]
   244  	if !ok {
   245  		return nil, &badActionError{name, "not defined"}
   246  	}
   247  	params := action.Params()
   248  	if err := spec.ValidateParams(params); err != nil {
   249  		return nil, &badActionError{name, err.Error()}
   250  	}
   251  
   252  	ctx, err := f.coreContext()
   253  	if err != nil {
   254  		return nil, errors.Trace(err)
   255  	}
   256  	ctx.actionData = newActionData(name, &tag, params)
   257  	ctx.id = f.newId(name)
   258  	runner := NewRunner(ctx, f.paths)
   259  	return runner, nil
   260  }
   261  
   262  // newId returns a probably-unique identifier for a new context, containing the
   263  // supplied string.
   264  func (f *factory) newId(name string) string {
   265  	return fmt.Sprintf("%s-%s-%d", f.unit.Name(), name, f.rand.Int63())
   266  }
   267  
   268  // coreContext creates a new context with all unspecialised fields filled in.
   269  func (f *factory) coreContext() (*HookContext, error) {
   270  	leadershipContext := newLeadershipContext(
   271  		f.state.LeadershipSettings,
   272  		f.tracker,
   273  	)
   274  	ctx := &HookContext{
   275  		unit:               f.unit,
   276  		state:              f.state,
   277  		LeadershipContext:  leadershipContext,
   278  		uuid:               f.envUUID,
   279  		envName:            f.envName,
   280  		unitName:           f.unit.Name(),
   281  		assignedMachineTag: f.machineTag,
   282  		serviceOwner:       f.ownerTag,
   283  		relations:          f.getContextRelations(),
   284  		relationId:         -1,
   285  		metricsRecorder:    nil,
   286  		definedMetrics:     nil,
   287  		metricsSender:      f.unit,
   288  		pendingPorts:       make(map[PortRange]PortRangeInfo),
   289  		storage:            f.storage,
   290  	}
   291  	if err := f.updateContext(ctx); err != nil {
   292  		return nil, err
   293  	}
   294  	return ctx, nil
   295  }
   296  
   297  func (f *factory) getCharm() (charm.Charm, error) {
   298  	ch, err := charm.ReadCharm(f.paths.GetCharmDir())
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	return ch, nil
   303  }
   304  
   305  // getContextRelations updates the factory's relation caches, and uses them
   306  // to construct ContextRelations for a fresh context.
   307  func (f *factory) getContextRelations() map[int]*ContextRelation {
   308  	contextRelations := map[int]*ContextRelation{}
   309  	relationInfos := f.getRelationInfos()
   310  	relationCaches := map[int]*RelationCache{}
   311  	for id, info := range relationInfos {
   312  		relationUnit := info.RelationUnit
   313  		memberNames := info.MemberNames
   314  		cache, found := f.relationCaches[id]
   315  		if found {
   316  			cache.Prune(memberNames)
   317  		} else {
   318  			cache = NewRelationCache(relationUnit.ReadSettings, memberNames)
   319  		}
   320  		relationCaches[id] = cache
   321  		contextRelations[id] = NewContextRelation(relationUnit, cache)
   322  	}
   323  	f.relationCaches = relationCaches
   324  	return contextRelations
   325  }
   326  
   327  // updateContext fills in all unspecialized fields that require an API call to
   328  // discover.
   329  //
   330  // Approximately *every* line of code in this function represents a bug: ie, some
   331  // piece of information we expose to the charm but which we fail to report changes
   332  // to via hooks. Furthermore, the fact that we make multiple API calls at this
   333  // time, rather than grabbing everything we need in one go, is unforgivably yucky.
   334  func (f *factory) updateContext(ctx *HookContext) (err error) {
   335  	defer errors.Trace(err)
   336  
   337  	ctx.apiAddrs, err = f.state.APIAddresses()
   338  	if err != nil {
   339  		return err
   340  	}
   341  	ctx.machinePorts, err = f.state.AllMachinePorts(f.machineTag)
   342  	if err != nil {
   343  		return errors.Trace(err)
   344  	}
   345  
   346  	statusCode, statusInfo, err := f.unit.MeterStatus()
   347  	if err != nil {
   348  		return errors.Annotate(err, "could not retrieve meter status for unit")
   349  	}
   350  	ctx.meterStatus = &meterStatus{
   351  		code: statusCode,
   352  		info: statusInfo,
   353  	}
   354  
   355  	// TODO(fwereade) 23-10-2014 bug 1384572
   356  	// Nothing here should ever be getting the environ config directly.
   357  	environConfig, err := f.state.EnvironConfig()
   358  	if err != nil {
   359  		return err
   360  	}
   361  	ctx.proxySettings = environConfig.ProxySettings()
   362  
   363  	// Calling these last, because there's a potential race: they're not guaranteed
   364  	// to be set in time to be needed for a hook. If they're not, we just leave them
   365  	// unset as we always have; this isn't great but it's about behaviour preservation.
   366  	ctx.publicAddress, err = f.unit.PublicAddress()
   367  	if err != nil && !params.IsCodeNoAddressSet(err) {
   368  		return err
   369  	}
   370  	ctx.privateAddress, err = f.unit.PrivateAddress()
   371  	if err != nil && !params.IsCodeNoAddressSet(err) {
   372  		return err
   373  	}
   374  	return nil
   375  }
   376  
   377  func inferRemoteUnit(rctxs map[int]*ContextRelation, info CommandInfo) (int, string, error) {
   378  	relationId := info.RelationId
   379  	hasRelation := relationId != -1
   380  	remoteUnit := info.RemoteUnitName
   381  	hasRemoteUnit := remoteUnit != ""
   382  
   383  	// Check baseline sanity of remote unit, if supplied.
   384  	if hasRemoteUnit {
   385  		if !names.IsValidUnit(remoteUnit) {
   386  			return -1, "", errors.Errorf(`invalid remote unit: %s`, remoteUnit)
   387  		} else if !hasRelation {
   388  			return -1, "", errors.Errorf("remote unit provided without a relation: %s", remoteUnit)
   389  		}
   390  	}
   391  
   392  	// Check sanity of relation, if supplied, otherwise easy early return.
   393  	if !hasRelation {
   394  		return relationId, remoteUnit, nil
   395  	}
   396  	rctx, found := rctxs[relationId]
   397  	if !found {
   398  		return -1, "", errors.Errorf("unknown relation id: %d", relationId)
   399  	}
   400  
   401  	// Past basic sanity checks; if forced, accept what we're given.
   402  	if info.ForceRemoteUnit {
   403  		return relationId, remoteUnit, nil
   404  	}
   405  
   406  	// Infer an appropriate remote unit if we can.
   407  	possibles := rctx.UnitNames()
   408  	if remoteUnit == "" {
   409  		switch len(possibles) {
   410  		case 0:
   411  			return -1, "", errors.Errorf("cannot infer remote unit in empty relation %d", relationId)
   412  		case 1:
   413  			return relationId, possibles[0], nil
   414  		}
   415  		return -1, "", errors.Errorf("ambiguous remote unit; possibilities are %+v", possibles)
   416  	}
   417  	for _, possible := range possibles {
   418  		if remoteUnit == possible {
   419  			return relationId, remoteUnit, nil
   420  		}
   421  	}
   422  	return -1, "", errors.Errorf("unknown remote unit %s; possibilities are %+v", remoteUnit, possibles)
   423  }