github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/utils/set"
    15  	"gopkg.in/juju/charm.v6-unstable/hooks"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/storage"
    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  	// StorageAttachment returns details of the storage attachment
    30  	// with the specified unit and storage tags.
    31  	StorageAttachment(names.StorageTag, names.UnitTag) (params.StorageAttachment, error)
    32  
    33  	// UnitStorageAttachments returns details of all of the storage
    34  	// attachments for the unit with the specified tag.
    35  	UnitStorageAttachments(names.UnitTag) ([]params.StorageAttachmentId, error)
    36  
    37  	// DestroyUnitStorageAttachments ensures that all storage
    38  	// attachments for the specified unit will be removed at
    39  	// some point in the future.
    40  	DestroyUnitStorageAttachments(names.UnitTag) error
    41  
    42  	// RemoveStorageAttachment removes that the storage attachment
    43  	// with the specified unit and storage tags. This method is only
    44  	// expected to succeed if the storage attachment is Dying.
    45  	RemoveStorageAttachment(names.StorageTag, names.UnitTag) error
    46  }
    47  
    48  type storageAttachment struct {
    49  	*stateFile
    50  	jujuc.ContextStorageAttachment
    51  }
    52  
    53  // Attachments generates storage hooks in response to changes to
    54  // storage attachments, and provides access to information about
    55  // storage attachments to hooks.
    56  type Attachments struct {
    57  	st              StorageAccessor
    58  	unitTag         names.UnitTag
    59  	abort           <-chan struct{}
    60  	storageStateDir string
    61  
    62  	// pending is the set of tags for storage attachments
    63  	// for which no hooks have been run.
    64  	pending set.Tags
    65  
    66  	// current storage attachments
    67  	storageAttachments map[names.StorageTag]storageAttachment
    68  }
    69  
    70  // NewAttachments returns a new Attachments.
    71  func NewAttachments(
    72  	st StorageAccessor,
    73  	tag names.UnitTag,
    74  	storageStateDir string,
    75  	abort <-chan struct{},
    76  ) (*Attachments, error) {
    77  	a := &Attachments{
    78  		st:                 st,
    79  		unitTag:            tag,
    80  		abort:              abort,
    81  		storageAttachments: make(map[names.StorageTag]storageAttachment),
    82  		storageStateDir:    storageStateDir,
    83  		pending:            make(set.Tags),
    84  	}
    85  	if err := a.init(); err != nil {
    86  		return nil, err
    87  	}
    88  	return a, nil
    89  }
    90  
    91  // init processes the storage state directory and creates storagers
    92  // for the state files found.
    93  func (a *Attachments) init() error {
    94  	if err := os.MkdirAll(a.storageStateDir, 0755); err != nil {
    95  		return errors.Annotate(err, "creating storage state dir")
    96  	}
    97  	// Query all remote, known storage attachments for the unit,
    98  	// so we can cull state files, and store current context.
    99  	attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag)
   100  	if err != nil {
   101  		return errors.Annotate(err, "getting unit attachments")
   102  	}
   103  	attachmentsByTag := make(map[names.StorageTag]struct{})
   104  	for _, attachmentId := range attachmentIds {
   105  		storageTag, err := names.ParseStorageTag(attachmentId.StorageTag)
   106  		if err != nil {
   107  			return errors.Trace(err)
   108  		}
   109  		attachmentsByTag[storageTag] = struct{}{}
   110  	}
   111  	stateFiles, err := readAllStateFiles(a.storageStateDir)
   112  	if err != nil {
   113  		return errors.Annotate(err, "reading storage state dirs")
   114  	}
   115  	for storageTag, stateFile := range stateFiles {
   116  		if _, ok := attachmentsByTag[storageTag]; !ok {
   117  			// We have previously removed the storage from state,
   118  			// but did not remove the state file. Remove the file.
   119  			if err := stateFile.Remove(); err != nil {
   120  				return errors.Trace(err)
   121  			}
   122  			continue
   123  		}
   124  		// Since there's a state file, we must previously have handled
   125  		// at least "storage-attached", so there is no possibility of
   126  		// short-circuiting the storage's removal.
   127  		attachment, err := a.st.StorageAttachment(storageTag, a.unitTag)
   128  		if err != nil {
   129  			return errors.Annotatef(
   130  				err, "querying storage attachment %q",
   131  				storageTag.Id(),
   132  			)
   133  		}
   134  		a.storageAttachments[storageTag] = storageAttachment{
   135  			stateFile,
   136  			&contextStorage{
   137  				tag:      storageTag,
   138  				kind:     storage.StorageKind(attachment.Kind),
   139  				location: attachment.Location,
   140  			},
   141  		}
   142  	}
   143  	for storageTag := range attachmentsByTag {
   144  		if _, ok := stateFiles[storageTag]; !ok {
   145  			// There is no state file for the attachment, so no
   146  			// hooks have been committed for it.
   147  			a.pending.Add(storageTag)
   148  		}
   149  		// Non-locally recorded attachments will be further handled
   150  		// by the resolver.
   151  	}
   152  	return nil
   153  }
   154  
   155  // SetDying ensures that any unprovisioned storage attachments are removed
   156  // from state.
   157  func (a *Attachments) SetDying() error {
   158  	if err := a.st.DestroyUnitStorageAttachments(a.unitTag); err != nil {
   159  		return errors.Trace(err)
   160  	}
   161  	return nil
   162  }
   163  
   164  // Pending reports the number of storage attachments whose hooks have yet
   165  // to be run and committed.
   166  func (a *Attachments) Pending() int {
   167  	return a.pending.Size()
   168  }
   169  
   170  // Empty reports whether or not there are any active storage attachments.
   171  func (a *Attachments) Empty() bool {
   172  	return len(a.storageAttachments) == 0
   173  }
   174  
   175  // Storage returns the ContextStorage with the supplied tag if it was
   176  // found, and whether it was found.
   177  func (a *Attachments) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, error) {
   178  	if attachment, ok := a.storageAttachments[tag]; ok {
   179  		return attachment, nil
   180  	}
   181  	return nil, errors.NotFoundf("storage")
   182  }
   183  
   184  // StorageTags returns the names.StorageTags for the active storage attachments.
   185  func (a *Attachments) StorageTags() ([]names.StorageTag, error) {
   186  	tags := set.NewTags()
   187  	for tag := range a.storageAttachments {
   188  		tags.Add(tag)
   189  	}
   190  	storageTags := make([]names.StorageTag, tags.Size())
   191  	for i, tag := range tags.SortedValues() {
   192  		storageTags[i] = tag.(names.StorageTag)
   193  	}
   194  	return storageTags, nil
   195  }
   196  
   197  // ValidateHook validates the hook against the current state.
   198  func (a *Attachments) ValidateHook(hi hook.Info) error {
   199  	storageState, err := a.storageStateForHook(hi)
   200  	if err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	return storageState.ValidateHook(hi)
   204  }
   205  
   206  // CommitHook persists the state change encoded in the supplied storage
   207  // hook, or returns an error if the hook is invalid given current state.
   208  func (a *Attachments) CommitHook(hi hook.Info) error {
   209  	storageState, err := a.storageStateForHook(hi)
   210  	if err != nil {
   211  		return errors.Trace(err)
   212  	}
   213  	if err := storageState.CommitHook(hi); err != nil {
   214  		return err
   215  	}
   216  	storageTag := names.NewStorageTag(hi.StorageId)
   217  	switch hi.Kind {
   218  	case hooks.StorageAttached:
   219  		a.pending.Remove(storageTag)
   220  	case hooks.StorageDetaching:
   221  		if err := a.removeStorageAttachment(storageTag); err != nil {
   222  			return errors.Trace(err)
   223  		}
   224  	}
   225  	return nil
   226  }
   227  
   228  func (a *Attachments) removeStorageAttachment(tag names.StorageTag) error {
   229  	if err := a.st.RemoveStorageAttachment(tag, a.unitTag); err != nil {
   230  		return errors.Annotate(err, "removing storage attachment")
   231  	}
   232  	a.pending.Remove(tag)
   233  	delete(a.storageAttachments, tag)
   234  	return nil
   235  }
   236  
   237  func (a *Attachments) storageStateForHook(hi hook.Info) (*stateFile, error) {
   238  	if !hi.Kind.IsStorage() {
   239  		return nil, errors.Errorf("not a storage hook: %#v", hi)
   240  	}
   241  	storageAttachment, ok := a.storageAttachments[names.NewStorageTag(hi.StorageId)]
   242  	if !ok {
   243  		return nil, errors.Errorf("unknown storage %q", hi.StorageId)
   244  	}
   245  	return storageAttachment.stateFile, nil
   246  }