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