github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/attachments.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package storage contains the storage subsystem for the uniter, responding
     5  // to changes in storage attachments (lifecycle, volume/filesystem details)
     6  // by queuing hooks and managing the storage attachments' lifecycle.
     7  package storage
     8  
     9  import (
    10  	"os"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/names"
    15  	"github.com/juju/utils/set"
    16  	"gopkg.in/juju/charm.v5/hooks"
    17  
    18  	"github.com/juju/juju/api/watcher"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/worker/uniter/hook"
    21  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.worker.uniter.storage")
    25  
    26  // StorageAccessor is an interface for accessing information about
    27  // storage attachments.
    28  type StorageAccessor interface {
    29  	// WatchStorageAttachment starts a watcher for changes to the
    30  	// storage attachment with the specified unit and storage tags.
    31  	WatchStorageAttachment(names.StorageTag, names.UnitTag) (watcher.NotifyWatcher, error)
    32  
    33  	// StorageAttachment returns details of the storage attachment
    34  	// with the specified unit and storage tags.
    35  	StorageAttachment(names.StorageTag, names.UnitTag) (params.StorageAttachment, error)
    36  
    37  	// StorageAttachmentLife returns the lifecycle state of the specified
    38  	// storage attachments.
    39  	StorageAttachmentLife([]params.StorageAttachmentId) ([]params.LifeResult, error)
    40  
    41  	// UnitStorageAttachments returns details of all of the storage
    42  	// attachments for the unit with the specified tag.
    43  	UnitStorageAttachments(names.UnitTag) ([]params.StorageAttachmentId, error)
    44  
    45  	// DestroyUnitStorageAttachments ensures that all storage
    46  	// attachments for the specified unit will be removed at
    47  	// some point in the future.
    48  	DestroyUnitStorageAttachments(names.UnitTag) error
    49  
    50  	// RemoveStorageAttachment removes that the storage attachment
    51  	// with the specified unit and storage tags. This method is only
    52  	// expected to succeed if the storage attachment is Dead.
    53  	RemoveStorageAttachment(names.StorageTag, names.UnitTag) error
    54  }
    55  
    56  // Attachments generates storage hooks in response to changes to
    57  // storage attachments, and provides access to information about
    58  // storage attachments to hooks.
    59  type Attachments struct {
    60  	st              StorageAccessor
    61  	unitTag         names.UnitTag
    62  	abort           <-chan struct{}
    63  	hooks           chan hook.Info
    64  	storagers       map[names.StorageTag]*storager
    65  	storageStateDir string
    66  
    67  	// pending is the set of tags for storage attachments
    68  	// for which no hooks have been run.
    69  	pending set.Tags
    70  }
    71  
    72  // NewAttachments returns a new Attachments.
    73  func NewAttachments(
    74  	st StorageAccessor,
    75  	tag names.UnitTag,
    76  	storageStateDir string,
    77  	abort <-chan struct{},
    78  ) (*Attachments, error) {
    79  	a := &Attachments{
    80  		st:              st,
    81  		unitTag:         tag,
    82  		abort:           abort,
    83  		hooks:           make(chan hook.Info),
    84  		storagers:       make(map[names.StorageTag]*storager),
    85  		storageStateDir: storageStateDir,
    86  		pending:         make(set.Tags),
    87  	}
    88  	if err := a.init(); err != nil {
    89  		return nil, err
    90  	}
    91  	return a, nil
    92  }
    93  
    94  // init processes the storage state directory and creates storagers
    95  // for the state files found.
    96  func (a *Attachments) init() error {
    97  	if err := os.MkdirAll(a.storageStateDir, 0755); err != nil {
    98  		return errors.Annotate(err, "creating storage state dir")
    99  	}
   100  	// Query all remote, known storage attachments for the unit,
   101  	// so we can cull state files, and store current context.
   102  	attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag)
   103  	if err != nil {
   104  		return errors.Annotate(err, "getting unit attachments")
   105  	}
   106  	attachmentsByTag := make(map[names.StorageTag]struct{})
   107  	for _, attachmentId := range attachmentIds {
   108  		storageTag, err := names.ParseStorageTag(attachmentId.StorageTag)
   109  		if err != nil {
   110  			return errors.Trace(err)
   111  		}
   112  		attachmentsByTag[storageTag] = struct{}{}
   113  	}
   114  	stateFiles, err := readAllStateFiles(a.storageStateDir)
   115  	if err != nil {
   116  		return errors.Annotate(err, "reading storage state dirs")
   117  	}
   118  	for storageTag, stateFile := range stateFiles {
   119  		if _, ok := attachmentsByTag[storageTag]; !ok {
   120  			if err := stateFile.Remove(); err != nil {
   121  				return errors.Trace(err)
   122  			}
   123  			continue
   124  		}
   125  		// Since there's a state file, we must previously have handled
   126  		// at least "storage-attached", so there is no possibility of
   127  		// short-circuiting the storage's removal.
   128  		if err := a.add(storageTag, stateFile); err != nil {
   129  			return errors.Trace(err)
   130  		}
   131  	}
   132  	for storageTag := range attachmentsByTag {
   133  		if _, ok := stateFiles[storageTag]; !ok {
   134  			// There is no state file for the attachment, so no
   135  			// hooks have been committed for it.
   136  			a.pending.Add(storageTag)
   137  		}
   138  		// Non-locally recorded attachments will be further handled
   139  		// by UpdateStorage.
   140  	}
   141  	return nil
   142  }
   143  
   144  // Hooks returns the channel on which storage hook execution requests
   145  // are sent.
   146  func (a *Attachments) Hooks() <-chan hook.Info {
   147  	return a.hooks
   148  }
   149  
   150  // Stop stops all of the storagers.
   151  func (a *Attachments) Stop() error {
   152  	for _, s := range a.storagers {
   153  		if err := s.Stop(); err != nil {
   154  			return errors.Trace(err)
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  // SetDying ensures that any unprovisioned storage attachments are removed
   161  // from state, and Pending is updated. After SetDying returns successfully,
   162  // and once Pending returns zero and Empty returns true, there will be no
   163  // remaining storage attachments.
   164  func (a *Attachments) SetDying() error {
   165  	if err := a.st.DestroyUnitStorageAttachments(a.unitTag); err != nil {
   166  		return errors.Trace(err)
   167  	}
   168  	if err := a.Refresh(); err != nil {
   169  		return errors.Trace(err)
   170  	}
   171  	return nil
   172  }
   173  
   174  // Refresh fetches all of the unit's storage attachments and processes each
   175  // one as in UpdateStorage.
   176  func (a *Attachments) Refresh() error {
   177  	attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag)
   178  	if err != nil {
   179  		return errors.Annotate(err, "getting unit attachments")
   180  	}
   181  	storageTags := make([]names.StorageTag, len(attachmentIds))
   182  	for i, attachmentId := range attachmentIds {
   183  		storageTag, err := names.ParseStorageTag(attachmentId.StorageTag)
   184  		if err != nil {
   185  			return errors.Trace(err)
   186  		}
   187  		storageTags[i] = storageTag
   188  	}
   189  	// Remove non-existent storage from pending.
   190  	for pending := range a.pending {
   191  		var found bool
   192  		for _, active := range storageTags {
   193  			if pending == active {
   194  				found = true
   195  				break
   196  			}
   197  		}
   198  		if !found {
   199  			a.pending.Remove(pending)
   200  		}
   201  	}
   202  	return a.UpdateStorage(storageTags)
   203  }
   204  
   205  // UpdateStorage responds to changes in the lifecycle states of the
   206  // storage attachments corresponding to the supplied storage tags,
   207  // sending storage hooks on the channel returned by Hooks().
   208  func (a *Attachments) UpdateStorage(tags []names.StorageTag) error {
   209  	ids := make([]params.StorageAttachmentId, len(tags))
   210  	for i, storageTag := range tags {
   211  		ids[i] = params.StorageAttachmentId{
   212  			StorageTag: storageTag.String(),
   213  			UnitTag:    a.unitTag.String(),
   214  		}
   215  	}
   216  	results, err := a.st.StorageAttachmentLife(ids)
   217  	if err != nil {
   218  		return errors.Trace(err)
   219  	}
   220  	for i, result := range results {
   221  		if result.Error == nil {
   222  			continue
   223  		} else if params.IsCodeNotFound(result.Error) {
   224  			a.pending.Remove(tags[i])
   225  			continue
   226  		}
   227  		return errors.Annotatef(
   228  			result.Error, "getting life of storage %s attachment", tags[i].Id(),
   229  		)
   230  	}
   231  	for i, result := range results {
   232  		if result.Error != nil {
   233  			continue
   234  		}
   235  		if err := a.updateOneStorage(tags[i], result.Life); err != nil {
   236  			return errors.Trace(err)
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  func (a *Attachments) updateOneStorage(storageTag names.StorageTag, life params.Life) error {
   243  	// Fetch the attachment's remote state, so we know when we can
   244  	// stop the storager and possibly short-circuit the attachment's
   245  	// removal.
   246  	stateFile, err := readStateFile(a.storageStateDir, storageTag)
   247  	if err != nil {
   248  		return errors.Trace(err)
   249  	}
   250  	storager := a.storagers[storageTag]
   251  
   252  	switch life {
   253  	case params.Dying:
   254  		if stateFile.state.attached {
   255  			// Previously ran storage-attached, so we'll
   256  			// leave the storager to handle the lifecycle
   257  			// state change.
   258  			if storager == nil {
   259  				panic("missing storager for attached storage")
   260  			}
   261  			return nil
   262  		}
   263  		// Storage attachment hasn't previously been observed,
   264  		// so we can short-circuit the removal.
   265  		err := a.removeStorageAttachment(storageTag, storager)
   266  		return errors.Trace(err)
   267  	}
   268  
   269  	if storager == nil {
   270  		a.pending.Add(storageTag)
   271  		return a.add(storageTag, stateFile)
   272  	}
   273  	return nil
   274  }
   275  
   276  // add creates a new storager for the specified storage tag.
   277  func (a *Attachments) add(storageTag names.StorageTag, stateFile *stateFile) error {
   278  	s, err := newStorager(a.st, a.unitTag, storageTag, stateFile, a.hooks)
   279  	if err != nil {
   280  		return errors.Annotatef(err, "watching storage %q", storageTag.Id())
   281  	}
   282  	a.storagers[storageTag] = s
   283  	logger.Debugf("watching storage %q", storageTag.Id())
   284  	return nil
   285  }
   286  
   287  // Pending reports the number of storage attachments whose hooks have yet
   288  // to be run and committed.
   289  func (a *Attachments) Pending() int {
   290  	return a.pending.Size()
   291  }
   292  
   293  // Empty reports whether or not there are any active storage attachments.
   294  func (a *Attachments) Empty() bool {
   295  	return len(a.storagers) == 0
   296  }
   297  
   298  // Storage returns the ContextStorage with the supplied tag if it was
   299  // found, and whether it was found.
   300  func (a *Attachments) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, bool) {
   301  	if s, ok := a.storagers[tag]; ok {
   302  		return s.Context()
   303  	}
   304  	return nil, false
   305  }
   306  
   307  // StorageTags returns the names.StorageTags for the active storage attachments.
   308  func (a *Attachments) StorageTags() []names.StorageTag {
   309  	tags := set.NewTags()
   310  	for tag := range a.storagers {
   311  		tags.Add(tag)
   312  	}
   313  	storageTags := make([]names.StorageTag, tags.Size())
   314  	for i, tag := range tags.SortedValues() {
   315  		storageTags[i] = tag.(names.StorageTag)
   316  	}
   317  	return storageTags
   318  }
   319  
   320  // ValidateHook validates the hook against the current state.
   321  func (a *Attachments) ValidateHook(hi hook.Info) error {
   322  	storager, err := a.storagerForHook(hi)
   323  	if err != nil {
   324  		return errors.Trace(err)
   325  	}
   326  	return storager.state.ValidateHook(hi)
   327  }
   328  
   329  // CommitHook persists the state change encoded in the supplied storage
   330  // hook, or returns an error if the hook is invalid given current state.
   331  func (a *Attachments) CommitHook(hi hook.Info) error {
   332  	storager, err := a.storagerForHook(hi)
   333  	if err != nil {
   334  		return errors.Trace(err)
   335  	}
   336  	if err := storager.CommitHook(hi); err != nil {
   337  		return err
   338  	}
   339  	storageTag := names.NewStorageTag(hi.StorageId)
   340  	switch hi.Kind {
   341  	case hooks.StorageAttached:
   342  		a.pending.Remove(storageTag)
   343  	case hooks.StorageDetaching:
   344  		if err := a.removeStorageAttachment(storageTag, storager); err != nil {
   345  			return errors.Trace(err)
   346  		}
   347  	}
   348  	return nil
   349  }
   350  
   351  func (a *Attachments) removeStorageAttachment(tag names.StorageTag, s *storager) error {
   352  	if err := a.st.RemoveStorageAttachment(tag, a.unitTag); err != nil {
   353  		return errors.Annotate(err, "removing storage attachment")
   354  	}
   355  	a.pending.Remove(tag)
   356  	if s == nil {
   357  		return nil
   358  	}
   359  	if err := s.Stop(); err != nil {
   360  		return errors.Trace(err)
   361  	}
   362  	delete(a.storagers, tag)
   363  	return nil
   364  }
   365  
   366  func (a *Attachments) storagerForHook(hi hook.Info) (*storager, error) {
   367  	if !hi.Kind.IsStorage() {
   368  		return nil, errors.Errorf("not a storage hook: %#v", hi)
   369  	}
   370  	storager, ok := a.storagers[names.NewStorageTag(hi.StorageId)]
   371  	if !ok {
   372  		return nil, errors.Errorf("unknown storage %q", hi.StorageId)
   373  	}
   374  	return storager, nil
   375  }