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