github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/source.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  	"github.com/juju/names"
     9  	"gopkg.in/juju/charm.v5/hooks"
    10  	"launchpad.net/tomb"
    11  
    12  	apiwatcher "github.com/juju/juju/api/watcher"
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/state/watcher"
    15  	"github.com/juju/juju/storage"
    16  	"github.com/juju/juju/worker/uniter/hook"
    17  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    18  )
    19  
    20  // storageSource is a hook source that generates storage hooks for
    21  // a single storage attachment.
    22  type storageSource struct {
    23  	tomb tomb.Tomb
    24  
    25  	*storageHookQueue
    26  	st      StorageAccessor
    27  	watcher apiwatcher.NotifyWatcher
    28  	changes chan hook.SourceChange
    29  }
    30  
    31  // storageHookQueue implements a subset of hook.Source, separated from
    32  // storageSource for simpler testing.
    33  type storageHookQueue struct {
    34  	unitTag    names.UnitTag
    35  	storageTag names.StorageTag
    36  
    37  	// attached records whether or not the storage-attached
    38  	// hook has been executed.
    39  	attached bool
    40  
    41  	// hookInfo is the next hook.Info to return, if non-nil.
    42  	hookInfo *hook.Info
    43  
    44  	// context contains the details of the storage attachment.
    45  	context *contextStorage
    46  }
    47  
    48  // newStorageSource creates a hook source that watches for changes to,
    49  // and generates storage hooks for, a single storage attachment.
    50  func newStorageSource(
    51  	st StorageAccessor,
    52  	unitTag names.UnitTag,
    53  	storageTag names.StorageTag,
    54  	attached bool,
    55  ) (*storageSource, error) {
    56  	w, err := st.WatchStorageAttachment(storageTag, unitTag)
    57  	if err != nil {
    58  		return nil, errors.Annotate(err, "watching storage attachment")
    59  	}
    60  	s := &storageSource{
    61  		storageHookQueue: &storageHookQueue{
    62  			unitTag:    unitTag,
    63  			storageTag: storageTag,
    64  			attached:   attached,
    65  		},
    66  		st:      st,
    67  		watcher: w,
    68  		changes: make(chan hook.SourceChange),
    69  	}
    70  	go func() {
    71  		defer s.tomb.Done()
    72  		defer watcher.Stop(w, &s.tomb)
    73  		s.tomb.Kill(s.loop())
    74  	}()
    75  	return s, nil
    76  }
    77  
    78  func (s *storageSource) loop() error {
    79  	defer close(s.changes)
    80  
    81  	var inChanges <-chan struct{}
    82  	var outChanges chan<- hook.SourceChange
    83  	var outChange hook.SourceChange
    84  	ready := make(chan struct{}, 1)
    85  	ready <- struct{}{}
    86  	for {
    87  		select {
    88  		case <-s.tomb.Dying():
    89  			return tomb.ErrDying
    90  		case <-ready:
    91  			inChanges = s.watcher.Changes()
    92  		case _, ok := <-inChanges:
    93  			logger.Debugf("got storage attachment change")
    94  			if !ok {
    95  				return watcher.EnsureErr(s.watcher)
    96  			}
    97  			inChanges = nil
    98  			outChanges = s.changes
    99  			outChange = func() error {
   100  				defer func() {
   101  					ready <- struct{}{}
   102  				}()
   103  				logger.Debugf("processing storage source change")
   104  				return s.update()
   105  			}
   106  		case outChanges <- outChange:
   107  			logger.Debugf("sent storage source change")
   108  			outChanges = nil
   109  			outChange = nil
   110  		}
   111  	}
   112  }
   113  
   114  // Changes is part of the hook.Source interface.
   115  func (s *storageSource) Changes() <-chan hook.SourceChange {
   116  	return s.changes
   117  }
   118  
   119  // Stop is part of the hook.Source interface.
   120  func (s *storageSource) Stop() error {
   121  	s.tomb.Kill(nil)
   122  	return s.tomb.Wait()
   123  }
   124  
   125  // update is called when hook.SourceChanges are applied.
   126  func (s *storageSource) update() error {
   127  	attachment, err := s.st.StorageAttachment(s.storageTag, s.unitTag)
   128  	if params.IsCodeNotFound(err) {
   129  		// The storage attachment was removed from state, which
   130  		// implies that the storage has been detached already.
   131  		logger.Debugf("storage attachment %q not found", s.storageTag.Id())
   132  		return nil
   133  	} else if params.IsCodeNotProvisioned(err) {
   134  		logger.Debugf("storage attachment %q not provisioned yet", s.storageTag.Id())
   135  		return nil
   136  	} else if err != nil {
   137  		logger.Debugf("error refreshing storage details: %v", err)
   138  		return errors.Annotate(err, "refreshing storage details")
   139  	}
   140  	return s.storageHookQueue.Update(attachment)
   141  }
   142  
   143  // Empty is part of the hook.Source interface.
   144  func (s *storageHookQueue) Empty() bool {
   145  	return s.hookInfo == nil
   146  }
   147  
   148  // Next is part of the hook.Source interface.
   149  func (s *storageHookQueue) Next() hook.Info {
   150  	if s.Empty() {
   151  		panic("source is empty")
   152  	}
   153  	return *s.hookInfo
   154  }
   155  
   156  // Pop is part of the hook.Source interface.
   157  func (s *storageHookQueue) Pop() {
   158  	if s.Empty() {
   159  		panic("source is empty")
   160  	}
   161  	if s.hookInfo.Kind == hooks.StorageAttached {
   162  		s.attached = true
   163  	}
   164  	s.hookInfo = nil
   165  }
   166  
   167  // Update updates the hook queue with the freshly acquired information about
   168  // the storage attachment.
   169  func (s *storageHookQueue) Update(attachment params.StorageAttachment) error {
   170  	switch attachment.Life {
   171  	case params.Alive:
   172  		if s.attached {
   173  			// Storage attachments currently do not change
   174  			// (apart from lifecycle) after being provisioned.
   175  			// We don't process unprovisioned storage here,
   176  			// so there's nothing to do.
   177  			return nil
   178  		}
   179  	case params.Dying:
   180  		if !s.attached {
   181  			// Nothing to do: attachment is dying, but
   182  			// the storage-attached hook has not been
   183  			// consumed.
   184  			s.hookInfo = nil
   185  			return nil
   186  		}
   187  	case params.Dead:
   188  		// Storage must been Dying to become Dead;
   189  		// no further action is required.
   190  		return nil
   191  	}
   192  
   193  	// Set the storage context when the first hook is generated
   194  	// for this storager. Later, when we need to handle changing
   195  	// storage, we'll need to have a cache in the runner like
   196  	// we have for relations.
   197  	if s.context == nil {
   198  		s.context = &contextStorage{
   199  			tag:      s.storageTag,
   200  			kind:     storage.StorageKind(attachment.Kind),
   201  			location: attachment.Location,
   202  		}
   203  	}
   204  
   205  	if s.hookInfo == nil {
   206  		s.hookInfo = &hook.Info{
   207  			StorageId: s.storageTag.Id(),
   208  		}
   209  	}
   210  	if attachment.Life == params.Alive {
   211  		s.hookInfo.Kind = hooks.StorageAttached
   212  	} else {
   213  		s.hookInfo.Kind = hooks.StorageDetaching
   214  	}
   215  	logger.Debugf("queued hook: %v", s.hookInfo)
   216  	return nil
   217  }
   218  
   219  // Context returns the ContextStorage for the storage that this hook queue
   220  // corresponds to, and whether there is any context available yet. There
   221  // will be context beginning from when the first hook is queued.
   222  func (s *storageHookQueue) Context() (jujuc.ContextStorageAttachment, bool) {
   223  	if s.context != nil {
   224  		return s.context, true
   225  	}
   226  	return nil, false
   227  }