github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/storage/state.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  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils"
    14  	"gopkg.in/juju/charm.v6/hooks"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/worker/uniter/hook"
    18  )
    19  
    20  // state describes the state of a storage attachment.
    21  type state struct {
    22  	// storage is the tag of the storage attachment.
    23  	storage names.StorageTag
    24  
    25  	// attached records the uniter's knowledge of the
    26  	// storage attachment state.
    27  	attached bool
    28  }
    29  
    30  // ValidateHook returns an error if the supplied hook.Info does not represent
    31  // a valid change to the storage state. Hooks must always be validated
    32  // against the current state before they are run, to ensure that the system
    33  // meets its guarantees about hook execution order.
    34  func (s *state) ValidateHook(hi hook.Info) (err error) {
    35  	defer errors.DeferredAnnotatef(&err, "inappropriate %q hook for storage %q", hi.Kind, s.storage.Id())
    36  	if hi.StorageId != s.storage.Id() {
    37  		return errors.Errorf("expected storage %q, got storage %q", s.storage.Id(), hi.StorageId)
    38  	}
    39  	switch hi.Kind {
    40  	case hooks.StorageAttached:
    41  		if s.attached {
    42  			return errors.New("storage already attached")
    43  		}
    44  	case hooks.StorageDetaching:
    45  		if !s.attached {
    46  			return errors.New("storage not attached")
    47  		}
    48  	}
    49  	return nil
    50  }
    51  
    52  // stateFile is a filesystem-backed representation of the state of a
    53  // storage attachment. Concurrent modifications to the underlying state
    54  // file will have undefined consequences.
    55  type stateFile struct {
    56  	// path identifies the directory holding persistent state.
    57  	path string
    58  
    59  	// state is the cached state of the directory, which is guaranteed
    60  	// to be synchronized with the true state so long as no concurrent
    61  	// changes are made to the directory.
    62  	state
    63  }
    64  
    65  // readStateFile loads a stateFile from the subdirectory of dirPath named
    66  // for the supplied storage tag. If the directory does not exist, no error
    67  // is returned.
    68  func readStateFile(dirPath string, tag names.StorageTag) (d *stateFile, err error) {
    69  	filename := strings.Replace(tag.Id(), "/", "-", -1)
    70  	d = &stateFile{
    71  		filepath.Join(dirPath, filename),
    72  		state{storage: tag},
    73  	}
    74  	defer errors.DeferredAnnotatef(&err, "cannot load storage %q state from %q", tag.Id(), d.path)
    75  	if _, err := os.Stat(d.path); os.IsNotExist(err) {
    76  		return d, nil
    77  	} else if err != nil {
    78  		return nil, err
    79  	}
    80  	var info diskInfo
    81  	if err := utils.ReadYaml(d.path, &info); err != nil {
    82  		return nil, errors.Errorf("invalid storage state file %q: %v", d.path, err)
    83  	}
    84  	if info.Attached == nil {
    85  		return nil, errors.Errorf("invalid storage state file %q: missing 'attached'", d.path)
    86  	}
    87  	d.state.attached = *info.Attached
    88  	return d, nil
    89  }
    90  
    91  // readAllStateFiles loads and returns every stateFile persisted inside
    92  // the supplied dirPath. If dirPath does not exist, no error is returned.
    93  func readAllStateFiles(dirPath string) (files map[names.StorageTag]*stateFile, err error) {
    94  	defer errors.DeferredAnnotatef(&err, "cannot load storage state from %q", dirPath)
    95  	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
    96  		return nil, nil
    97  	} else if err != nil {
    98  		return nil, err
    99  	}
   100  	fis, err := ioutil.ReadDir(dirPath)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	files = make(map[names.StorageTag]*stateFile)
   105  	for _, fi := range fis {
   106  		if fi.IsDir() {
   107  			continue
   108  		}
   109  		storageId := fi.Name()
   110  		if i := strings.LastIndex(storageId, "-"); i > 0 {
   111  			storageId = storageId[:i] + "/" + storageId[i+1:]
   112  			if !names.IsValidStorage(storageId) {
   113  				continue
   114  			}
   115  		} else {
   116  			// Lack of "-" means it's not a valid storage ID.
   117  			continue
   118  		}
   119  		tag := names.NewStorageTag(storageId)
   120  		f, err := readStateFile(dirPath, tag)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		files[tag] = f
   125  	}
   126  	return files, nil
   127  }
   128  
   129  // CommitHook atomically writes to disk the storage state change in hi.
   130  // It must be called after the respective hook was executed successfully.
   131  // CommitHook doesn't validate hi but guarantees that successive writes
   132  // of the same hi are idempotent.
   133  func (d *stateFile) CommitHook(hi hook.Info) (err error) {
   134  	defer errors.DeferredAnnotatef(&err, "failed to write %q hook info for %q on state directory", hi.Kind, hi.StorageId)
   135  	if hi.Kind == hooks.StorageDetaching {
   136  		return d.Remove()
   137  	}
   138  	attached := true
   139  	di := diskInfo{&attached}
   140  	if err := utils.WriteYaml(d.path, &di); err != nil {
   141  		return err
   142  	}
   143  	// If write was successful, update own state.
   144  	d.state.attached = true
   145  	return nil
   146  }
   147  
   148  // Remove removes the directory if it exists and is empty.
   149  func (d *stateFile) Remove() error {
   150  	if err := os.Remove(d.path); err != nil && !os.IsNotExist(err) {
   151  		return err
   152  	}
   153  	// If atomic delete succeeded, update own state.
   154  	d.state.attached = false
   155  	return nil
   156  }
   157  
   158  // diskInfo defines the storage attachment data serialization.
   159  type diskInfo struct {
   160  	Attached *bool `yaml:"attached,omitempty"`
   161  }