github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/names"
    14  	"github.com/juju/utils"
    15  	"gopkg.in/juju/charm.v6-unstable/hooks"
    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 := strings.Replace(fi.Name(), "-", "/", -1)
   110  		if !names.IsValidStorage(storageId) {
   111  			continue
   112  		}
   113  		tag := names.NewStorageTag(storageId)
   114  		f, err := readStateFile(dirPath, tag)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		files[tag] = f
   119  	}
   120  	return files, nil
   121  }
   122  
   123  // CommitHook atomically writes to disk the storage state change in hi.
   124  // It must be called after the respective hook was executed successfully.
   125  // CommitHook doesn't validate hi but guarantees that successive writes
   126  // of the same hi are idempotent.
   127  func (d *stateFile) CommitHook(hi hook.Info) (err error) {
   128  	defer errors.DeferredAnnotatef(&err, "failed to write %q hook info for %q on state directory", hi.Kind, hi.StorageId)
   129  	if hi.Kind == hooks.StorageDetaching {
   130  		return d.Remove()
   131  	}
   132  	attached := true
   133  	di := diskInfo{&attached}
   134  	if err := utils.WriteYaml(d.path, &di); err != nil {
   135  		return err
   136  	}
   137  	// If write was successful, update own state.
   138  	d.state.attached = true
   139  	return nil
   140  }
   141  
   142  // Remove removes the directory if it exists and is empty.
   143  func (d *stateFile) Remove() error {
   144  	if err := os.Remove(d.path); err != nil && !os.IsNotExist(err) {
   145  		return err
   146  	}
   147  	// If atomic delete succeeded, update own state.
   148  	d.state.attached = false
   149  	return nil
   150  }
   151  
   152  // diskInfo defines the storage attachment data serialization.
   153  type diskInfo struct {
   154  	Attached *bool `yaml:"attached,omitempty"`
   155  }