github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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.v6-unstable/hooks"
    17  
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/worker/uniter/hook"
    20  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.worker.uniter.storage")
    24  
    25  // StorageAccessor is an interface for accessing information about
    26  // storage attachments.
    27  type StorageAccessor interface {
    28  	// StorageAttachment returns details of the storage attachment
    29  	// with the specified unit and storage tags.
    30  	StorageAttachment(names.StorageTag, names.UnitTag) (params.StorageAttachment, error)
    31  
    32  	// StorageAttachmentLife returns the lifecycle state of the specified
    33  	// storage attachments.
    34  	StorageAttachmentLife([]params.StorageAttachmentId) ([]params.LifeResult, error)
    35  
    36  	// UnitStorageAttachments returns details of all of the storage
    37  	// attachments for the unit with the specified tag.
    38  	UnitStorageAttachments(names.UnitTag) ([]params.StorageAttachmentId, error)
    39  
    40  	// DestroyUnitStorageAttachments ensures that all storage
    41  	// attachments for the specified unit will be removed at
    42  	// some point in the future.
    43  	DestroyUnitStorageAttachments(names.UnitTag) error
    44  
    45  	// RemoveStorageAttachment removes that the storage attachment
    46  	// with the specified unit and storage tags. This method is only
    47  	// expected to succeed if the storage attachment is Dead.
    48  	RemoveStorageAttachment(names.StorageTag, names.UnitTag) error
    49  }
    50  
    51  type storageAttachment struct {
    52  	*stateFile
    53  	jujuc.ContextStorageAttachment
    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  	storageStateDir string
    64  
    65  	// pending is the set of tags for storage attachments
    66  	// for which no hooks have been run.
    67  	pending set.Tags
    68  
    69  	// current storage attachments
    70  	storageAttachments map[names.StorageTag]storageAttachment
    71  }
    72  
    73  // NewAttachments returns a new Attachments.
    74  func NewAttachments(
    75  	st StorageAccessor,
    76  	tag names.UnitTag,
    77  	storageStateDir string,
    78  	abort <-chan struct{},
    79  ) (*Attachments, error) {
    80  	a := &Attachments{
    81  		st:                 st,
    82  		unitTag:            tag,
    83  		abort:              abort,
    84  		storageAttachments: make(map[names.StorageTag]storageAttachment),
    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  // SetDying ensures that any unprovisioned storage attachments are removed
   145  // from state, and Pending is updated. After SetDying returns successfully,
   146  // and once Pending returns zero and Empty returns true, there will be no
   147  // remaining storage attachments.
   148  func (a *Attachments) SetDying() error {
   149  	if err := a.st.DestroyUnitStorageAttachments(a.unitTag); err != nil {
   150  		return errors.Trace(err)
   151  	}
   152  	if err := a.Refresh(); err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  	return nil
   156  }
   157  
   158  // Refresh fetches all of the unit's storage attachments and processes each
   159  // one as in UpdateStorage.
   160  func (a *Attachments) Refresh() error {
   161  	attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag)
   162  	if err != nil {
   163  		return errors.Annotate(err, "getting unit attachments")
   164  	}
   165  	storageTags := make([]names.StorageTag, len(attachmentIds))
   166  	for i, attachmentId := range attachmentIds {
   167  		storageTag, err := names.ParseStorageTag(attachmentId.StorageTag)
   168  		if err != nil {
   169  			return errors.Trace(err)
   170  		}
   171  		storageTags[i] = storageTag
   172  	}
   173  	// Remove non-existent storage from pending.
   174  	for pending := range a.pending {
   175  		var found bool
   176  		for _, active := range storageTags {
   177  			if pending == active {
   178  				found = true
   179  				break
   180  			}
   181  		}
   182  		if !found {
   183  			a.pending.Remove(pending)
   184  		}
   185  	}
   186  	return a.UpdateStorage(storageTags)
   187  }
   188  
   189  // UpdateStorage responds to changes in the lifecycle states of the
   190  // storage attachments corresponding to the supplied storage tags,
   191  // sending storage hooks on the channel returned by Hooks().
   192  func (a *Attachments) UpdateStorage(tags []names.StorageTag) error {
   193  	ids := make([]params.StorageAttachmentId, len(tags))
   194  	for i, storageTag := range tags {
   195  		ids[i] = params.StorageAttachmentId{
   196  			StorageTag: storageTag.String(),
   197  			UnitTag:    a.unitTag.String(),
   198  		}
   199  	}
   200  	results, err := a.st.StorageAttachmentLife(ids)
   201  	if err != nil {
   202  		return errors.Trace(err)
   203  	}
   204  	for i, result := range results {
   205  		if result.Error == nil {
   206  			continue
   207  		} else if params.IsCodeNotFound(result.Error) {
   208  			a.pending.Remove(tags[i])
   209  			continue
   210  		}
   211  		return errors.Annotatef(
   212  			result.Error, "getting life of storage %s attachment", tags[i].Id(),
   213  		)
   214  	}
   215  	for i, result := range results {
   216  		if result.Error != nil {
   217  			continue
   218  		}
   219  		if err := a.updateOneStorage(tags[i], result.Life); err != nil {
   220  			return errors.Trace(err)
   221  		}
   222  	}
   223  	return nil
   224  }
   225  
   226  func (a *Attachments) updateOneStorage(storageTag names.StorageTag, life params.Life) error {
   227  	// Fetch the attachment's remote state, so we know when we can
   228  	// stop the storager and possibly short-circuit the attachment's
   229  	// removal.
   230  	stateFile, err := readStateFile(a.storageStateDir, storageTag)
   231  	if err != nil {
   232  		return errors.Trace(err)
   233  	}
   234  	_, storageKnown := a.storageAttachments[storageTag]
   235  
   236  	switch life {
   237  	case params.Dying:
   238  		if stateFile.state.attached {
   239  			// Previously ran storage-attached, so we'll
   240  			// leave the external watcher to handle the lifecycle
   241  			// state change.
   242  			if !storageKnown {
   243  				panic("missing status for attached storage")
   244  			}
   245  			return nil
   246  		}
   247  		// Storage attachment hasn't previously been observed,
   248  		// so we can short-circuit the removal.
   249  		err := a.removeStorageAttachment(storageTag, storageKnown)
   250  		return errors.Trace(err)
   251  	}
   252  
   253  	if !storageKnown {
   254  		a.pending.Add(storageTag)
   255  		return a.add(storageTag, stateFile)
   256  	}
   257  	return nil
   258  }
   259  
   260  // add creates a new storager for the specified storage tag.
   261  func (a *Attachments) add(storageTag names.StorageTag, stateFile *stateFile) error {
   262  	a.storageAttachments[storageTag] = storageAttachment{
   263  		stateFile: stateFile,
   264  	}
   265  	logger.Debugf("adding storage %q", storageTag.Id())
   266  	return nil
   267  }
   268  
   269  // Pending reports the number of storage attachments whose hooks have yet
   270  // to be run and committed.
   271  func (a *Attachments) Pending() int {
   272  	return a.pending.Size()
   273  }
   274  
   275  // Empty reports whether or not there are any active storage attachments.
   276  func (a *Attachments) Empty() bool {
   277  	return len(a.storageAttachments) == 0
   278  }
   279  
   280  // Storage returns the ContextStorage with the supplied tag if it was
   281  // found, and whether it was found.
   282  func (a *Attachments) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, error) {
   283  	if attachment, ok := a.storageAttachments[tag]; ok {
   284  		return attachment, nil
   285  	}
   286  	return nil, errors.NotFoundf("storage")
   287  }
   288  
   289  // StorageTags returns the names.StorageTags for the active storage attachments.
   290  func (a *Attachments) StorageTags() ([]names.StorageTag, error) {
   291  	tags := set.NewTags()
   292  	for tag := range a.storageAttachments {
   293  		tags.Add(tag)
   294  	}
   295  	storageTags := make([]names.StorageTag, tags.Size())
   296  	for i, tag := range tags.SortedValues() {
   297  		storageTags[i] = tag.(names.StorageTag)
   298  	}
   299  	return storageTags, nil
   300  }
   301  
   302  // ValidateHook validates the hook against the current state.
   303  func (a *Attachments) ValidateHook(hi hook.Info) error {
   304  	storageState, err := a.storageStateForHook(hi)
   305  	if err != nil {
   306  		return errors.Trace(err)
   307  	}
   308  	return storageState.ValidateHook(hi)
   309  }
   310  
   311  // CommitHook persists the state change encoded in the supplied storage
   312  // hook, or returns an error if the hook is invalid given current state.
   313  func (a *Attachments) CommitHook(hi hook.Info) error {
   314  	storageState, err := a.storageStateForHook(hi)
   315  	if err != nil {
   316  		return errors.Trace(err)
   317  	}
   318  	if err := storageState.CommitHook(hi); err != nil {
   319  		return err
   320  	}
   321  	storageTag := names.NewStorageTag(hi.StorageId)
   322  	switch hi.Kind {
   323  	case hooks.StorageAttached:
   324  		a.pending.Remove(storageTag)
   325  	case hooks.StorageDetaching:
   326  		if err := a.removeStorageAttachment(storageTag, storageState != nil); err != nil {
   327  			return errors.Trace(err)
   328  		}
   329  	}
   330  	return nil
   331  }
   332  
   333  func (a *Attachments) removeStorageAttachment(tag names.StorageTag, storageKnown bool) error {
   334  	if err := a.st.RemoveStorageAttachment(tag, a.unitTag); err != nil {
   335  		return errors.Annotate(err, "removing storage attachment")
   336  	}
   337  	a.pending.Remove(tag)
   338  	if !storageKnown {
   339  		return nil
   340  	}
   341  	delete(a.storageAttachments, tag)
   342  	return nil
   343  }
   344  
   345  func (a *Attachments) storageStateForHook(hi hook.Info) (*stateFile, error) {
   346  	if !hi.Kind.IsStorage() {
   347  		return nil, errors.Errorf("not a storage hook: %#v", hi)
   348  	}
   349  	storageAttachment, ok := a.storageAttachments[names.NewStorageTag(hi.StorageId)]
   350  	if !ok {
   351  		return nil, errors.Errorf("unknown storage %q", hi.StorageId)
   352  	}
   353  	return storageAttachment.stateFile, nil
   354  }