github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/storage/resolver.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storage
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/juju/charm.v6/hooks"
     9  	"gopkg.in/juju/names.v2"
    10  
    11  	"github.com/juju/juju/apiserver/params"
    12  	"github.com/juju/juju/core/model"
    13  	"github.com/juju/juju/storage"
    14  	"github.com/juju/juju/worker/uniter/hook"
    15  	"github.com/juju/juju/worker/uniter/operation"
    16  	"github.com/juju/juju/worker/uniter/remotestate"
    17  	"github.com/juju/juju/worker/uniter/resolver"
    18  )
    19  
    20  // StorageResolverOperations instances know how to make operations
    21  // required by the resolver.
    22  type StorageResolverOperations interface {
    23  	NewUpdateStorage(tags []names.StorageTag) (operation.Operation, error)
    24  	NewRunHook(hookInfo hook.Info) (operation.Operation, error)
    25  }
    26  
    27  type storageResolver struct {
    28  	storage   *Attachments
    29  	dying     bool
    30  	life      map[names.StorageTag]params.Life
    31  	modelType model.ModelType
    32  }
    33  
    34  // NewResolver returns a new storage resolver.
    35  func NewResolver(storage *Attachments, modelType model.ModelType) resolver.Resolver {
    36  	return &storageResolver{
    37  		storage:   storage,
    38  		modelType: modelType,
    39  		life:      make(map[names.StorageTag]params.Life),
    40  	}
    41  }
    42  
    43  // NextOp is defined on the Resolver interface.
    44  func (s *storageResolver) NextOp(
    45  	localState resolver.LocalState,
    46  	remoteState remotestate.Snapshot,
    47  	opFactory operation.Factory,
    48  ) (operation.Operation, error) {
    49  
    50  	// Only IAAS models need to first run the install hook.
    51  	// For CAAS models, storage is specified in the pod config
    52  	// and mounted as the pod in started.
    53  	blockedWaitingForInstall := !localState.Installed && s.modelType == model.IAAS
    54  
    55  	if remoteState.Life == params.Dying {
    56  		// The unit is dying, so destroy all of its storage.
    57  		if !s.dying {
    58  			if err := s.storage.SetDying(); err != nil {
    59  				return nil, errors.Trace(err)
    60  			}
    61  			s.dying = true
    62  		}
    63  		for tag, snap := range remoteState.Storage {
    64  			snap.Life = params.Dying
    65  			remoteState.Storage[tag] = snap
    66  		}
    67  	}
    68  
    69  	if err := s.maybeShortCircuitRemoval(remoteState.Storage); err != nil {
    70  		return nil, errors.Trace(err)
    71  	}
    72  
    73  	var runStorageHooks bool
    74  	switch {
    75  	case localState.Kind == operation.Continue:
    76  		// There's nothing in progress.
    77  		runStorageHooks = true
    78  	case blockedWaitingForInstall && localState.Kind == operation.RunHook && localState.Step == operation.Queued:
    79  		// The install operation completed, and there's an install
    80  		// hook queued. Run storage-attached hooks first.
    81  		runStorageHooks = true
    82  	}
    83  	if !runStorageHooks {
    84  		return nil, resolver.ErrNoOperation
    85  	}
    86  
    87  	if !localState.Installed && s.storage.Pending() == 0 {
    88  		logger.Infof("initial storage attachments ready")
    89  	}
    90  
    91  	for tag, snap := range remoteState.Storage {
    92  		op, err := s.nextHookOp(tag, snap, opFactory)
    93  		if errors.Cause(err) == resolver.ErrNoOperation {
    94  			continue
    95  		}
    96  		return op, err
    97  	}
    98  	if s.storage.Pending() > 0 {
    99  		logger.Debugf("still pending %v", s.storage.pending.SortedValues())
   100  		if blockedWaitingForInstall {
   101  			// We only wait for pending storage before
   102  			// the install hook runs; we should not block
   103  			// other hooks from running while storage is
   104  			// being provisioned.
   105  			return nil, resolver.ErrWaiting
   106  		}
   107  	}
   108  	return nil, resolver.ErrNoOperation
   109  }
   110  
   111  // maybeShortCircuitRemoval removes any storage that is not alive,
   112  // and has not had a storage-attached hook committed.
   113  func (s *storageResolver) maybeShortCircuitRemoval(remote map[names.StorageTag]remotestate.StorageSnapshot) error {
   114  	for tag, snap := range remote {
   115  		local, ok := s.storage.storageAttachments[tag]
   116  		if (ok && local.attached) || snap.Life == params.Alive {
   117  			continue
   118  		}
   119  		if err := s.storage.removeStorageAttachment(tag); err != nil {
   120  			return errors.Trace(err)
   121  		}
   122  		delete(remote, tag)
   123  	}
   124  	return nil
   125  }
   126  
   127  func (s *storageResolver) nextHookOp(
   128  	tag names.StorageTag,
   129  	snap remotestate.StorageSnapshot,
   130  	opFactory operation.Factory,
   131  ) (operation.Operation, error) {
   132  
   133  	logger.Debugf("next hook op for %v: %+v", tag, snap)
   134  
   135  	if snap.Life == params.Dead {
   136  		// Storage must have been Dying to become Dead;
   137  		// no further action is required.
   138  		return nil, resolver.ErrNoOperation
   139  	}
   140  
   141  	hookInfo := hook.Info{StorageId: tag.Id()}
   142  	switch snap.Life {
   143  	case params.Alive:
   144  		storageAttachment, ok := s.storage.storageAttachments[tag]
   145  		if ok && storageAttachment.attached {
   146  			// Once the storage is attached, we only care about
   147  			// lifecycle state changes.
   148  			return nil, resolver.ErrNoOperation
   149  		}
   150  		// The storage-attached hook has not been committed, so add the
   151  		// storage to the pending set.
   152  		s.storage.pending.Add(tag)
   153  		if !snap.Attached {
   154  			// The storage attachment has not been provisioned yet,
   155  			// so just ignore it for now. We'll be notified again
   156  			// when it has been provisioned.
   157  			return nil, resolver.ErrNoOperation
   158  		}
   159  		// The storage is alive, but we haven't previously run the
   160  		// "storage-attached" hook. Do so now.
   161  		hookInfo.Kind = hooks.StorageAttached
   162  	case params.Dying:
   163  		storageAttachment, ok := s.storage.storageAttachments[tag]
   164  		if !ok || !storageAttachment.attached {
   165  			// Nothing to do: attachment is dying, but
   166  			// the storage-attached hook has not been
   167  			// issued.
   168  			return nil, resolver.ErrNoOperation
   169  		}
   170  		// The storage is dying, but we haven't previously run the
   171  		// "storage-detached" hook. Do so now.
   172  		hookInfo.Kind = hooks.StorageDetaching
   173  	}
   174  
   175  	// Update the local state to reflect what we're about to report
   176  	// to a hook.
   177  	stateFile, err := readStateFile(s.storage.storageStateDir, tag)
   178  	if err != nil {
   179  		return nil, errors.Trace(err)
   180  	}
   181  	s.storage.storageAttachments[tag] = storageAttachment{
   182  		stateFile, &contextStorage{
   183  			tag:      tag,
   184  			kind:     storage.StorageKind(snap.Kind),
   185  			location: snap.Location,
   186  		},
   187  	}
   188  
   189  	return opFactory.NewRunHook(hookInfo)
   190  }