github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"gopkg.in/juju/charm.v6/hooks"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/api/uniter"
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/core/leadership"
    18  	"github.com/juju/juju/core/model"
    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 hooks.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 hooks.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  	modelName  string
    73  	modelType  model.ModelType
    74  	machineTag names.MachineTag
    75  	storage    StorageContextAccessor
    76  	clock      Clock
    77  	zone       string
    78  	principal  string
    79  
    80  	// Callback to get relation state snapshot.
    81  	getRelationInfos RelationsFunc
    82  	relationCaches   map[int]*RelationCache
    83  
    84  	// For generating "unique" context ids.
    85  	rand *rand.Rand
    86  }
    87  
    88  // FactoryConfig contains configuration values
    89  // for the context factory.
    90  type FactoryConfig struct {
    91  	State            *uniter.State
    92  	UnitTag          names.UnitTag
    93  	Tracker          leadership.Tracker
    94  	GetRelationInfos RelationsFunc
    95  	Storage          StorageContextAccessor
    96  	Paths            Paths
    97  	Clock            Clock
    98  }
    99  
   100  // NewContextFactory returns a ContextFactory capable of creating execution contexts backed
   101  // by the supplied unit's supplied API connection.
   102  func NewContextFactory(config FactoryConfig) (ContextFactory, error) {
   103  	unit, err := config.State.Unit(config.UnitTag)
   104  	if err != nil {
   105  		return nil, errors.Trace(err)
   106  	}
   107  	m, err := config.State.Model()
   108  	if err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  	var (
   112  		machineTag names.MachineTag
   113  		zone       string
   114  	)
   115  	if m.ModelType == model.IAAS {
   116  		machineTag, err = unit.AssignedMachine()
   117  		if err != nil {
   118  			return nil, errors.Trace(err)
   119  		}
   120  
   121  		zone, err = unit.AvailabilityZone()
   122  		if err != nil {
   123  			return nil, errors.Trace(err)
   124  		}
   125  	}
   126  	principal, ok, err := unit.PrincipalName()
   127  	if err != nil {
   128  		return nil, errors.Trace(err)
   129  	} else if !ok {
   130  		principal = ""
   131  	}
   132  
   133  	f := &contextFactory{
   134  		unit:             unit,
   135  		state:            config.State,
   136  		tracker:          config.Tracker,
   137  		paths:            config.Paths,
   138  		modelUUID:        m.UUID,
   139  		modelName:        m.Name,
   140  		machineTag:       machineTag,
   141  		getRelationInfos: config.GetRelationInfos,
   142  		relationCaches:   map[int]*RelationCache{},
   143  		storage:          config.Storage,
   144  		rand:             rand.New(rand.NewSource(time.Now().Unix())),
   145  		clock:            config.Clock,
   146  		zone:             zone,
   147  		principal:        principal,
   148  		modelType:        m.ModelType,
   149  	}
   150  	return f, nil
   151  }
   152  
   153  // newId returns a probably-unique identifier for a new context, containing the
   154  // supplied string.
   155  func (f *contextFactory) newId(name string) string {
   156  	return fmt.Sprintf("%s-%s-%d", f.unit.Name(), name, f.rand.Int63())
   157  }
   158  
   159  // coreContext creates a new context with all unspecialised fields filled in.
   160  func (f *contextFactory) coreContext() (*HookContext, error) {
   161  	leadershipContext := newLeadershipContext(
   162  		f.state.LeadershipSettings,
   163  		f.tracker,
   164  		f.unit.Name(),
   165  	)
   166  	ctx := &HookContext{
   167  		unit:               f.unit,
   168  		state:              f.state,
   169  		LeadershipContext:  leadershipContext,
   170  		uuid:               f.modelUUID,
   171  		modelName:          f.modelName,
   172  		unitName:           f.unit.Name(),
   173  		assignedMachineTag: f.machineTag,
   174  		relations:          f.getContextRelations(),
   175  		relationId:         -1,
   176  		pendingPorts:       make(map[PortRange]PortRangeInfo),
   177  		storage:            f.storage,
   178  		clock:              f.clock,
   179  		componentDir:       f.paths.ComponentDir,
   180  		componentFuncs:     registeredComponentFuncs,
   181  		availabilityzone:   f.zone,
   182  		principal:          f.principal,
   183  	}
   184  	if err := f.updateContext(ctx); err != nil {
   185  		return nil, err
   186  	}
   187  	return ctx, nil
   188  }
   189  
   190  // ActionContext is part of the ContextFactory interface.
   191  func (f *contextFactory) ActionContext(actionData *ActionData) (*HookContext, error) {
   192  	if actionData == nil {
   193  		return nil, errors.New("nil actionData specified")
   194  	}
   195  	ctx, err := f.coreContext()
   196  	if err != nil {
   197  		return nil, errors.Trace(err)
   198  	}
   199  	ctx.actionData = actionData
   200  	ctx.id = f.newId(actionData.Name)
   201  	return ctx, nil
   202  }
   203  
   204  // HookContext is part of the ContextFactory interface.
   205  func (f *contextFactory) HookContext(hookInfo hook.Info) (*HookContext, error) {
   206  	ctx, err := f.coreContext()
   207  	if err != nil {
   208  		return nil, errors.Trace(err)
   209  	}
   210  	hookName := string(hookInfo.Kind)
   211  	if hookInfo.Kind.IsRelation() {
   212  		ctx.relationId = hookInfo.RelationId
   213  		ctx.remoteUnitName = hookInfo.RemoteUnit
   214  		relation, found := ctx.relations[hookInfo.RelationId]
   215  		if !found {
   216  			return nil, errors.Errorf("unknown relation id: %v", hookInfo.RelationId)
   217  		}
   218  		if hookInfo.Kind == hooks.RelationDeparted {
   219  			relation.cache.RemoveMember(hookInfo.RemoteUnit)
   220  		} else if hookInfo.RemoteUnit != "" {
   221  			// Clear remote settings cache for changing remote unit.
   222  			relation.cache.InvalidateMember(hookInfo.RemoteUnit)
   223  		}
   224  		hookName = fmt.Sprintf("%s-%s", relation.Name(), hookInfo.Kind)
   225  	}
   226  	if hookInfo.Kind.IsStorage() {
   227  		ctx.storageTag = names.NewStorageTag(hookInfo.StorageId)
   228  		if _, err := ctx.storage.Storage(ctx.storageTag); err != nil {
   229  			return nil, errors.Annotatef(err, "could not retrieve storage for id: %v", hookInfo.StorageId)
   230  		}
   231  		storageName, err := names.StorageName(hookInfo.StorageId)
   232  		if err != nil {
   233  			return nil, errors.Trace(err)
   234  		}
   235  		hookName = fmt.Sprintf("%s-%s", storageName, hookName)
   236  	}
   237  	ctx.id = f.newId(hookName)
   238  	return ctx, nil
   239  }
   240  
   241  // CommandContext is part of the ContextFactory interface.
   242  func (f *contextFactory) CommandContext(commandInfo CommandInfo) (*HookContext, error) {
   243  	ctx, err := f.coreContext()
   244  	if err != nil {
   245  		return nil, errors.Trace(err)
   246  	}
   247  	relationId, remoteUnitName, err := inferRemoteUnit(ctx.relations, commandInfo)
   248  	if err != nil {
   249  		return nil, errors.Trace(err)
   250  	}
   251  	ctx.relationId = relationId
   252  	ctx.remoteUnitName = remoteUnitName
   253  	ctx.id = f.newId("run-commands")
   254  	return ctx, nil
   255  }
   256  
   257  // getContextRelations updates the factory's relation caches, and uses them
   258  // to construct ContextRelations for a fresh context.
   259  func (f *contextFactory) getContextRelations() map[int]*ContextRelation {
   260  	contextRelations := map[int]*ContextRelation{}
   261  	relationInfos := f.getRelationInfos()
   262  	relationCaches := map[int]*RelationCache{}
   263  	for id, info := range relationInfos {
   264  		relationUnit := info.RelationUnit
   265  		memberNames := info.MemberNames
   266  		cache, found := f.relationCaches[id]
   267  		if found {
   268  			cache.Prune(memberNames)
   269  		} else {
   270  			cache = NewRelationCache(relationUnit.ReadSettings, memberNames)
   271  		}
   272  		relationCaches[id] = cache
   273  		contextRelations[id] = NewContextRelation(relationUnit, cache)
   274  	}
   275  	f.relationCaches = relationCaches
   276  	return contextRelations
   277  }
   278  
   279  // updateContext fills in all unspecialized fields that require an API call to
   280  // discover.
   281  //
   282  // Approximately *every* line of code in this function represents a bug: ie, some
   283  // piece of information we expose to the charm but which we fail to report changes
   284  // to via hooks. Furthermore, the fact that we make multiple API calls at this
   285  // time, rather than grabbing everything we need in one go, is unforgivably yucky.
   286  func (f *contextFactory) updateContext(ctx *HookContext) (err error) {
   287  	defer errors.Trace(err)
   288  
   289  	ctx.apiAddrs, err = f.state.APIAddresses()
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	sla, err := f.state.SLALevel()
   295  	if err != nil {
   296  		return errors.Annotate(err, "could not retrieve the SLA level")
   297  	}
   298  	ctx.slaLevel = sla
   299  
   300  	// TODO(fwereade) 23-10-2014 bug 1384572
   301  	// Nothing here should ever be getting the environ config directly.
   302  	modelConfig, err := f.state.ModelConfig()
   303  	if err != nil {
   304  		return err
   305  	}
   306  	ctx.legacyProxySettings = modelConfig.LegacyProxySettings()
   307  	ctx.jujuProxySettings = modelConfig.JujuProxySettings()
   308  
   309  	statusCode, statusInfo, err := f.unit.MeterStatus()
   310  	if err != nil {
   311  		return errors.Annotate(err, "could not retrieve meter status for unit")
   312  	}
   313  	ctx.meterStatus = &meterStatus{
   314  		code: statusCode,
   315  		info: statusInfo,
   316  	}
   317  
   318  	if f.modelType == model.IAAS {
   319  		ctx.machinePorts, err = f.state.AllMachinePorts(f.machineTag)
   320  		if err != nil {
   321  			return errors.Trace(err)
   322  		}
   323  
   324  		// Calling these last, because there's a potential race: they're not guaranteed
   325  		// to be set in time to be needed for a hook. If they're not, we just leave them
   326  		// unset as we always have; this isn't great but it's about behaviour preservation.
   327  		ctx.publicAddress, err = f.unit.PublicAddress()
   328  		if err != nil && !params.IsCodeNoAddressSet(err) {
   329  			logger.Warningf("cannot get legacy public address for %v: %v", f.unit.Name(), err)
   330  		}
   331  		ctx.privateAddress, err = f.unit.PrivateAddress()
   332  		if err != nil && !params.IsCodeNoAddressSet(err) {
   333  			logger.Warningf("cannot get legacy private address for %v: %v", f.unit.Name(), err)
   334  		}
   335  	}
   336  	return nil
   337  }
   338  
   339  func inferRemoteUnit(rctxs map[int]*ContextRelation, info CommandInfo) (int, string, error) {
   340  	relationId := info.RelationId
   341  	hasRelation := relationId != -1
   342  	remoteUnit := info.RemoteUnitName
   343  	hasRemoteUnit := remoteUnit != ""
   344  
   345  	// Check baseline sanity of remote unit, if supplied.
   346  	if hasRemoteUnit {
   347  		if !names.IsValidUnit(remoteUnit) {
   348  			return -1, "", errors.Errorf(`invalid remote unit: %s`, remoteUnit)
   349  		} else if !hasRelation {
   350  			return -1, "", errors.Errorf("remote unit provided without a relation: %s", remoteUnit)
   351  		}
   352  	}
   353  
   354  	// Check sanity of relation, if supplied, otherwise easy early return.
   355  	if !hasRelation {
   356  		return relationId, remoteUnit, nil
   357  	}
   358  	rctx, found := rctxs[relationId]
   359  	if !found {
   360  		return -1, "", errors.Errorf("unknown relation id: %d", relationId)
   361  	}
   362  
   363  	// Past basic sanity checks; if forced, accept what we're given.
   364  	if info.ForceRemoteUnit {
   365  		return relationId, remoteUnit, nil
   366  	}
   367  
   368  	// Infer an appropriate remote unit if we can.
   369  	possibles := rctx.UnitNames()
   370  	if remoteUnit == "" {
   371  		switch len(possibles) {
   372  		case 0:
   373  			return -1, "", errors.Errorf("cannot infer remote unit in empty relation %d", relationId)
   374  		case 1:
   375  			return relationId, possibles[0], nil
   376  		}
   377  		return -1, "", errors.Errorf("ambiguous remote unit; possibilities are %+v", possibles)
   378  	}
   379  	for _, possible := range possibles {
   380  		if remoteUnit == possible {
   381  			return relationId, remoteUnit, nil
   382  		}
   383  	}
   384  	return -1, "", errors.Errorf("unknown remote unit %s; possibilities are %+v", remoteUnit, possibles)
   385  }