github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/configstore/disk.go (about)

     1  // Copyright 2013-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package configstore
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/utils/featureflag"
    17  	"github.com/juju/utils/fslock"
    18  	"github.com/juju/utils/set"
    19  	goyaml "gopkg.in/yaml.v1"
    20  
    21  	"github.com/juju/juju/feature"
    22  	"github.com/juju/juju/juju/osenv"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.environs.configstore")
    26  
    27  type configSource string
    28  
    29  const (
    30  	lockName = "env.lock"
    31  
    32  	sourceCreated configSource = "created"
    33  	sourceJenv    configSource = "jenv"
    34  	sourceCache   configSource = "cache"
    35  	sourceMem     configSource = "mem"
    36  )
    37  
    38  // A second should be way more than enough to write or read any files.
    39  var lockTimeout = time.Second
    40  
    41  // Default returns disk-based environment config storage
    42  // rooted at JujuHome.
    43  var Default = func() (Storage, error) {
    44  	return NewDisk(osenv.JujuHome())
    45  }
    46  
    47  type diskStore struct {
    48  	dir string
    49  }
    50  
    51  // EnvironInfoData is the serialisation structure for the original JENV file.
    52  type EnvironInfoData struct {
    53  	User            string
    54  	Password        string
    55  	EnvironUUID     string                 `json:"environ-uuid,omitempty" yaml:"environ-uuid,omitempty"`
    56  	ServerUUID      string                 `json:"server-uuid,omitempty" yaml:"server-uuid,omitempty"`
    57  	StateServers    []string               `json:"state-servers" yaml:"state-servers"`
    58  	ServerHostnames []string               `json:"server-hostnames,omitempty" yaml:"server-hostnames,omitempty"`
    59  	CACert          string                 `json:"ca-cert" yaml:"ca-cert"`
    60  	Config          map[string]interface{} `json:"bootstrap-config,omitempty" yaml:"bootstrap-config,omitempty"`
    61  }
    62  
    63  type environInfo struct {
    64  	mu sync.Mutex
    65  
    66  	// environmentDir is the directory where the files are written.
    67  	environmentDir string
    68  
    69  	// path is the location of the file that we read to load the info.
    70  	path string
    71  
    72  	// source identifies how this instance was created
    73  	source configSource
    74  
    75  	name            string
    76  	user            string
    77  	credentials     string
    78  	environmentUUID string
    79  	serverUUID      string
    80  	apiEndpoints    []string
    81  	apiHostnames    []string
    82  	caCert          string
    83  	bootstrapConfig map[string]interface{}
    84  }
    85  
    86  // NewDisk returns a ConfigStorage implementation that stores configuration in
    87  // the given directory. The parent of the directory must already exist; the
    88  // directory itself is created if it doesn't already exist.
    89  func NewDisk(dir string) (Storage, error) {
    90  	if _, err := os.Stat(dir); err != nil {
    91  		return nil, err
    92  	}
    93  	if err := os.MkdirAll(dir, 0755); err != nil {
    94  		return nil, err
    95  	}
    96  	d := &diskStore{
    97  		dir: filepath.Join(dir, "environments"),
    98  	}
    99  	if err := d.mkEnvironmentsDir(); err != nil {
   100  		return nil, err
   101  	}
   102  	return d, nil
   103  }
   104  
   105  func (d *diskStore) mkEnvironmentsDir() error {
   106  	err := os.Mkdir(d.dir, 0700)
   107  	if os.IsExist(err) {
   108  		return nil
   109  	}
   110  	logger.Debugf("Made dir %v", d.dir)
   111  	return err
   112  }
   113  
   114  // CreateInfo implements Storage.CreateInfo.
   115  func (d *diskStore) CreateInfo(envName string) EnvironInfo {
   116  	return &environInfo{
   117  		environmentDir: d.dir,
   118  		source:         sourceCreated,
   119  		name:           envName,
   120  	}
   121  }
   122  
   123  // List implements Storage.List
   124  func (d *diskStore) List() ([]string, error) {
   125  	var envs []string
   126  
   127  	// Awkward -  list both jenv files and cache entries.
   128  	cache, err := readCacheFile(cacheFilename(d.dir))
   129  	if err != nil {
   130  		return nil, errors.Trace(err)
   131  	}
   132  	for name := range cache.Environment {
   133  		envs = append(envs, name)
   134  	}
   135  
   136  	files, err := filepath.Glob(d.dir + "/*" + jenvExtension)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	for _, file := range files {
   141  		fName := filepath.Base(file)
   142  		name := fName[:len(fName)-len(jenvExtension)]
   143  		envs = append(envs, name)
   144  	}
   145  	return envs, nil
   146  }
   147  
   148  // ListSystems implements Storage.ListSystems
   149  func (d *diskStore) ListSystems() ([]string, error) {
   150  	// List both jenv files and cache entries.  Record
   151  	// results in a set to avoid repeat entries.
   152  	servers := set.NewStrings()
   153  	cache, err := readCacheFile(cacheFilename(d.dir))
   154  	if err != nil {
   155  		return nil, errors.Trace(err)
   156  	}
   157  	for name := range cache.Server {
   158  		servers.Add(name)
   159  	}
   160  
   161  	files, err := filepath.Glob(d.dir + "/*" + jenvExtension)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	for _, file := range files {
   166  		fName := filepath.Base(file)
   167  		name := fName[:len(fName)-len(jenvExtension)]
   168  		env, err := d.ReadInfo(name)
   169  		if err != nil {
   170  			return nil, err
   171  		}
   172  
   173  		// If ServerUUID is not set, it is an old env and is a
   174  		// server by default.  Otherwise, if the server and env
   175  		// UUIDs match, it is a server.
   176  		api := env.APIEndpoint()
   177  		if api.ServerUUID == "" || api.ServerUUID == api.EnvironUUID {
   178  			servers.Add(name)
   179  		}
   180  	}
   181  	return servers.SortedValues(), nil
   182  }
   183  
   184  // ReadInfo implements Storage.ReadInfo.
   185  func (d *diskStore) ReadInfo(envName string) (EnvironInfo, error) {
   186  	// NOTE: any reading or writing from the directory should be done with a
   187  	// fslock to make sure we have a consistent read or write.  Also worth
   188  	// noting, we should use a very short timeout.
   189  	lock, err := acquireEnvironmentLock(d.dir, "reading")
   190  	if err != nil {
   191  		return nil, errors.Annotatef(err, "cannot read info")
   192  	}
   193  	defer lock.Unlock()
   194  
   195  	info, err := d.readCacheFile(envName)
   196  	if err != nil {
   197  		if errors.IsNotFound(err) {
   198  			info, err = d.readJENVFile(envName)
   199  		}
   200  	}
   201  	if err != nil {
   202  		return nil, errors.Trace(err)
   203  	}
   204  	info.environmentDir = d.dir
   205  	return info, nil
   206  }
   207  
   208  func cacheFilename(dir string) string {
   209  	return filepath.Join(dir, "cache.yaml")
   210  }
   211  
   212  func (d *diskStore) readCacheFile(envName string) (*environInfo, error) {
   213  	cache, err := readCacheFile(cacheFilename(d.dir))
   214  	if err != nil {
   215  		return nil, errors.Trace(err)
   216  	}
   217  	info, err := cache.readInfo(envName)
   218  	if err != nil {
   219  		return nil, errors.Trace(err)
   220  	}
   221  	return info, nil
   222  }
   223  
   224  // Initialized implements EnvironInfo.Initialized.
   225  func (info *environInfo) Initialized() bool {
   226  	info.mu.Lock()
   227  	defer info.mu.Unlock()
   228  	return info.initialized()
   229  }
   230  
   231  func (info *environInfo) initialized() bool {
   232  	return info.source != sourceCreated
   233  }
   234  
   235  // BootstrapConfig implements EnvironInfo.BootstrapConfig.
   236  func (info *environInfo) BootstrapConfig() map[string]interface{} {
   237  	info.mu.Lock()
   238  	defer info.mu.Unlock()
   239  	return info.bootstrapConfig
   240  }
   241  
   242  // APICredentials implements EnvironInfo.APICredentials.
   243  func (info *environInfo) APICredentials() APICredentials {
   244  	info.mu.Lock()
   245  	defer info.mu.Unlock()
   246  	return APICredentials{
   247  		User:     info.user,
   248  		Password: info.credentials,
   249  	}
   250  }
   251  
   252  // APIEndpoint implements EnvironInfo.APIEndpoint.
   253  func (info *environInfo) APIEndpoint() APIEndpoint {
   254  	info.mu.Lock()
   255  	defer info.mu.Unlock()
   256  	return APIEndpoint{
   257  		Addresses:   info.apiEndpoints,
   258  		Hostnames:   info.apiHostnames,
   259  		CACert:      info.caCert,
   260  		EnvironUUID: info.environmentUUID,
   261  		ServerUUID:  info.serverUUID,
   262  	}
   263  }
   264  
   265  // SetBootstrapConfig implements EnvironInfo.SetBootstrapConfig.
   266  func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) {
   267  	info.mu.Lock()
   268  	defer info.mu.Unlock()
   269  	if info.source != sourceCreated {
   270  		panic("bootstrap config set on environment info that has not just been created")
   271  	}
   272  	info.bootstrapConfig = attrs
   273  }
   274  
   275  // SetAPIEndpoint implements EnvironInfo.SetAPIEndpoint.
   276  func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) {
   277  	info.mu.Lock()
   278  	defer info.mu.Unlock()
   279  	info.apiEndpoints = endpoint.Addresses
   280  	info.apiHostnames = endpoint.Hostnames
   281  	info.caCert = endpoint.CACert
   282  	info.environmentUUID = endpoint.EnvironUUID
   283  	info.serverUUID = endpoint.ServerUUID
   284  }
   285  
   286  // SetAPICredentials implements EnvironInfo.SetAPICredentials.
   287  func (info *environInfo) SetAPICredentials(creds APICredentials) {
   288  	info.mu.Lock()
   289  	defer info.mu.Unlock()
   290  	info.user = creds.User
   291  	info.credentials = creds.Password
   292  }
   293  
   294  // Location returns the location of the environInfo in human readable format.
   295  func (info *environInfo) Location() string {
   296  	info.mu.Lock()
   297  	defer info.mu.Unlock()
   298  	return fmt.Sprintf("file %q", info.path)
   299  }
   300  
   301  // Write implements EnvironInfo.Write.
   302  func (info *environInfo) Write() error {
   303  	info.mu.Lock()
   304  	defer info.mu.Unlock()
   305  	lock, err := acquireEnvironmentLock(info.environmentDir, "writing")
   306  	if err != nil {
   307  		return errors.Annotatef(err, "cannot write info")
   308  	}
   309  	defer lock.Unlock()
   310  
   311  	// In order to write out the environment info to the cache
   312  	// file we need to make sure the server UUID is set. Sufficiently
   313  	// up to date servers will write the server UUID to the JENV
   314  	// file as connections are made to the API server. It is possible
   315  	// that for an old JENV file, the first update (on API connection)
   316  	// may write a JENV file, and the subsequent update will create the
   317  	// entry in the cache file.
   318  	// If the source was the cache file, then always write there to
   319  	// avoid stale data in the cache file.
   320  	if info.source == sourceCache ||
   321  		(featureflag.Enabled(feature.JES) && info.serverUUID != "") {
   322  		if err := info.ensureNoJENV(); info.source == sourceCreated && err != nil {
   323  			return errors.Trace(err)
   324  		}
   325  		logger.Debugf("writing cache file")
   326  		filename := cacheFilename(info.environmentDir)
   327  		cache, err := readCacheFile(filename)
   328  		if err != nil {
   329  			return errors.Trace(err)
   330  		}
   331  		if err := cache.updateInfo(info); err != nil {
   332  			return errors.Trace(err)
   333  		}
   334  		if err := writeCacheFile(filename, cache); err != nil {
   335  			return errors.Trace(err)
   336  		}
   337  		oldPath := info.path
   338  		info.path = filename
   339  		// If source was jenv file, delete the jenv.
   340  		if info.source == sourceJenv {
   341  			err := os.Remove(oldPath)
   342  			if err != nil {
   343  				return errors.Trace(err)
   344  			}
   345  		}
   346  		info.source = sourceCache
   347  	} else {
   348  		logger.Debugf("writing jenv file")
   349  		if err := info.writeJENVFile(); err != nil {
   350  			return errors.Trace(err)
   351  		}
   352  		info.source = sourceJenv
   353  	}
   354  	return nil
   355  }
   356  
   357  // Destroy implements EnvironInfo.Destroy.
   358  func (info *environInfo) Destroy() error {
   359  	info.mu.Lock()
   360  	defer info.mu.Unlock()
   361  	lock, err := acquireEnvironmentLock(info.environmentDir, "destroying")
   362  	if err != nil {
   363  		return errors.Annotatef(err, "cannot destroy environment info")
   364  	}
   365  	defer lock.Unlock()
   366  
   367  	if info.initialized() {
   368  		if info.source == sourceJenv {
   369  			err := os.Remove(info.path)
   370  			if os.IsNotExist(err) {
   371  				return errors.New("environment info has already been removed")
   372  			}
   373  			return err
   374  		}
   375  		if info.source == sourceCache {
   376  			filename := cacheFilename(info.environmentDir)
   377  			cache, err := readCacheFile(filename)
   378  			if err != nil {
   379  				return errors.Trace(err)
   380  			}
   381  			if err := cache.removeInfo(info); err != nil {
   382  				return errors.Trace(err)
   383  			}
   384  			if err := writeCacheFile(filename, cache); err != nil {
   385  				return errors.Trace(err)
   386  			}
   387  			return nil
   388  		}
   389  		return errors.Errorf("unknown source %q for environment info", info.source)
   390  	}
   391  	return nil
   392  }
   393  
   394  const jenvExtension = ".jenv"
   395  
   396  func jenvFilename(basedir, envName string) string {
   397  	return filepath.Join(basedir, envName+jenvExtension)
   398  }
   399  
   400  func (d *diskStore) readJENVFile(envName string) (*environInfo, error) {
   401  	path := jenvFilename(d.dir, envName)
   402  	data, err := ioutil.ReadFile(path)
   403  	if err != nil {
   404  		if os.IsNotExist(err) {
   405  			return nil, errors.NotFoundf("environment %q", envName)
   406  		}
   407  		return nil, err
   408  	}
   409  	var info environInfo
   410  	info.path = path
   411  	if len(data) == 0 {
   412  		return &info, nil
   413  	}
   414  	var values EnvironInfoData
   415  	if err := goyaml.Unmarshal(data, &values); err != nil {
   416  		return nil, errors.Annotatef(err, "error unmarshalling %q", path)
   417  	}
   418  	info.name = envName
   419  	info.user = values.User
   420  	info.credentials = values.Password
   421  	info.environmentUUID = values.EnvironUUID
   422  	info.serverUUID = values.ServerUUID
   423  	info.caCert = values.CACert
   424  	info.apiEndpoints = values.StateServers
   425  	info.apiHostnames = values.ServerHostnames
   426  	info.bootstrapConfig = values.Config
   427  
   428  	info.source = sourceJenv
   429  	return &info, nil
   430  }
   431  
   432  func (info *environInfo) ensureNoJENV() error {
   433  	path := jenvFilename(info.environmentDir, info.name)
   434  	_, err := os.Stat(path)
   435  	if os.IsNotExist(err) {
   436  		return nil
   437  	}
   438  	if err == nil {
   439  		return ErrEnvironInfoAlreadyExists
   440  	}
   441  	return err
   442  }
   443  
   444  // Kept primarily for testing purposes now.
   445  func (info *environInfo) writeJENVFile() error {
   446  
   447  	infoData := EnvironInfoData{
   448  		User:            info.user,
   449  		Password:        info.credentials,
   450  		EnvironUUID:     info.environmentUUID,
   451  		ServerUUID:      info.serverUUID,
   452  		StateServers:    info.apiEndpoints,
   453  		ServerHostnames: info.apiHostnames,
   454  		CACert:          info.caCert,
   455  		Config:          info.bootstrapConfig,
   456  	}
   457  
   458  	data, err := goyaml.Marshal(infoData)
   459  	if err != nil {
   460  		return errors.Annotate(err, "cannot marshal environment info")
   461  	}
   462  	// We now use a fslock to sync reads and writes across the environment,
   463  	// so we don't need to use a temporary file any more.
   464  
   465  	flags := os.O_WRONLY
   466  	if info.initialized() {
   467  		flags |= os.O_TRUNC
   468  	} else {
   469  		flags |= os.O_CREATE | os.O_EXCL
   470  	}
   471  	path := jenvFilename(info.environmentDir, info.name)
   472  	logger.Debugf("writing jenv file to %s", path)
   473  	file, err := os.OpenFile(path, flags, 0600)
   474  	if os.IsExist(err) {
   475  		return ErrEnvironInfoAlreadyExists
   476  	}
   477  
   478  	_, err = file.Write(data)
   479  	file.Close()
   480  	info.path = path
   481  	return errors.Annotate(err, "cannot write file")
   482  }
   483  
   484  func acquireEnvironmentLock(dir, operation string) (*fslock.Lock, error) {
   485  	lock, err := fslock.NewLock(dir, lockName)
   486  	if err != nil {
   487  		return nil, errors.Trace(err)
   488  	}
   489  	message := fmt.Sprintf("pid: %d, operation: %s", os.Getpid(), operation)
   490  	err = lock.LockWithTimeout(lockTimeout, message)
   491  	if err != nil {
   492  		logger.Warningf("configstore lock held, lock dir: %s", filepath.Join(dir, lockName))
   493  		logger.Warningf("  lock holder message: %s", lock.Message())
   494  		return nil, errors.Trace(err)
   495  	}
   496  	return lock, nil
   497  }