github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/joyent/storage.go (about)

     1  // Copyright 2013 Joyent Inc.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package joyent
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"path"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/joyent/gocommon/client"
    17  	je "github.com/joyent/gocommon/errors"
    18  	"github.com/joyent/gomanta/manta"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/utils"
    21  
    22  	"github.com/juju/juju/environs/storage"
    23  )
    24  
    25  type JoyentStorage struct {
    26  	sync.Mutex
    27  	ecfg          *environConfig
    28  	madeContainer bool
    29  	containerName string
    30  	manta         *manta.Client
    31  }
    32  
    33  type byteCloser struct {
    34  	io.Reader
    35  }
    36  
    37  func (byteCloser) Close() error {
    38  	return nil
    39  }
    40  
    41  var _ storage.Storage = (*JoyentStorage)(nil)
    42  
    43  func newStorage(cfg *environConfig, name string) (storage.Storage, error) {
    44  	creds, err := credentials(cfg)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	client := client.NewClient(cfg.mantaUrl(), "", creds, &logger)
    49  
    50  	if name == "" {
    51  		name = cfg.controlDir()
    52  	}
    53  
    54  	return &JoyentStorage{
    55  		ecfg:          cfg,
    56  		containerName: name,
    57  		manta:         manta.New(client)}, nil
    58  }
    59  
    60  func (s *JoyentStorage) GetContainerName() string {
    61  	return s.containerName
    62  }
    63  
    64  func (s *JoyentStorage) GetMantaUrl() string {
    65  	return s.ecfg.mantaUrl()
    66  }
    67  
    68  func (s *JoyentStorage) GetMantaUser() string {
    69  	return s.ecfg.mantaUser()
    70  }
    71  
    72  // createContainer makes the environment's control container, the
    73  // place where bootstrap information and deployed charms
    74  // are stored. To avoid two round trips on every PUT operation,
    75  // we do this only once for each environ.
    76  func (s *JoyentStorage) createContainer() error {
    77  	s.Lock()
    78  	defer s.Unlock()
    79  	if s.madeContainer {
    80  		return nil
    81  	}
    82  	// try to make the container
    83  	err := s.manta.PutDirectory(s.containerName)
    84  	if err == nil {
    85  		s.madeContainer = true
    86  	}
    87  	return err
    88  }
    89  
    90  // DeleteContainer deletes the named container from the storage account.
    91  func (s *JoyentStorage) DeleteContainer(containerName string) error {
    92  	err := s.manta.DeleteDirectory(containerName)
    93  	if err == nil && strings.EqualFold(s.containerName, containerName) {
    94  		s.madeContainer = false
    95  	}
    96  	if je.IsResourceNotFound(err) {
    97  		return errors.NewNotFound(err, fmt.Sprintf("cannot delete %s, not found", containerName))
    98  	}
    99  	return err
   100  }
   101  
   102  func (s *JoyentStorage) List(prefix string) ([]string, error) {
   103  	content, err := list(s, s.containerName)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	var names []string
   109  	for _, item := range content {
   110  		name := strings.TrimPrefix(item, s.containerName+"/")
   111  		if prefix != "" {
   112  			if strings.HasPrefix(name, prefix) {
   113  				names = append(names, name)
   114  			}
   115  		} else {
   116  			names = append(names, name)
   117  		}
   118  	}
   119  	return names, nil
   120  }
   121  
   122  func list(s *JoyentStorage, path string) ([]string, error) {
   123  	// TODO - we don't want to create the container here, but instead handle
   124  	// any 404 and return as if no files exist.
   125  	if err := s.createContainer(); err != nil {
   126  		return nil, fmt.Errorf("cannot make Manta control container: %v", err)
   127  	}
   128  	// use empty opts, i.e. default values
   129  	// -- might be added in the provider config?
   130  	contents, err := s.manta.ListDirectory(path, manta.ListDirectoryOpts{})
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	var names []string
   135  	for _, item := range contents {
   136  		if strings.EqualFold(item.Type, "directory") {
   137  			items, err := list(s, path+"/"+item.Name)
   138  			if err != nil {
   139  				return nil, err
   140  			}
   141  			names = append(names, items...)
   142  		} else {
   143  			names = append(names, path+"/"+item.Name)
   144  		}
   145  	}
   146  	return names, nil
   147  }
   148  
   149  //return something that a random wget can retrieve the object at, without any credentials
   150  func (s *JoyentStorage) URL(name string) (string, error) {
   151  	path := fmt.Sprintf("/%s/stor/%s/%s", s.ecfg.mantaUser(), s.containerName, name)
   152  	return s.manta.SignURL(path, time.Now().AddDate(10, 0, 0))
   153  }
   154  
   155  func (s *JoyentStorage) Get(name string) (io.ReadCloser, error) {
   156  	b, err := s.manta.GetObject(s.containerName, name)
   157  	if err != nil {
   158  		return nil, errors.NewNotFound(err, fmt.Sprintf("cannot find %s", name))
   159  	}
   160  	r := byteCloser{bytes.NewReader(b)}
   161  	return r, nil
   162  }
   163  
   164  func (s *JoyentStorage) Put(name string, r io.Reader, length int64) error {
   165  	if err := s.createContainer(); err != nil {
   166  		return fmt.Errorf("cannot make Manta control container: %v", err)
   167  	}
   168  	if strings.Contains(name, "/") {
   169  		var parents []string
   170  		dirs := strings.Split(name, "/")
   171  		for i := range dirs {
   172  			if i < (len(dirs) - 1) {
   173  				parents = append(parents, strings.Join(dirs[:(i+1)], "/"))
   174  			}
   175  		}
   176  		for _, dir := range parents {
   177  			err := s.manta.PutDirectory(path.Join(s.containerName, dir))
   178  			if err != nil {
   179  				return fmt.Errorf("cannot create parent directory %q in control container %q: %v", dir, s.containerName, err)
   180  			}
   181  		}
   182  	}
   183  	object, err := ioutil.ReadAll(r)
   184  	if err != nil {
   185  		return fmt.Errorf("failed to read object %q: %v", name, err)
   186  	}
   187  	err = s.manta.PutObject(s.containerName, name, object)
   188  	if err != nil {
   189  		return fmt.Errorf("cannot write file %q to control container %q: %v", name, s.containerName, err)
   190  	}
   191  	return nil
   192  }
   193  
   194  func (s *JoyentStorage) Remove(name string) error {
   195  	err := s.manta.DeleteObject(s.containerName, name)
   196  	if err != nil {
   197  		if je.IsResourceNotFound(err) {
   198  			// gojoyent returns an error if file doesn't exist
   199  			// just log a warning
   200  			logger.Warningf("cannot delete %s from %s, already deleted", name, s.containerName)
   201  		} else {
   202  			return err
   203  		}
   204  	}
   205  
   206  	if strings.Contains(name, "/") {
   207  		var parents []string
   208  		dirs := strings.Split(name, "/")
   209  		for i := (len(dirs) - 1); i >= 0; i-- {
   210  			if i < (len(dirs) - 1) {
   211  				parents = append(parents, strings.Join(dirs[:(i+1)], "/"))
   212  			}
   213  		}
   214  
   215  		for _, dir := range parents {
   216  			err := s.manta.DeleteDirectory(path.Join(s.containerName, dir))
   217  			if err != nil {
   218  				if je.IsBadRequest(err) {
   219  					// check if delete request returned a bad request error, i.e. directory is not empty
   220  					// just log a warning
   221  					logger.Warningf("cannot delete %s, not empty", dir)
   222  				} else if je.IsResourceNotFound(err) {
   223  					// check if delete request returned a resource not found error, i.e. directory was already deleted
   224  					// just log a warning
   225  					logger.Warningf("cannot delete %s, already deleted", dir)
   226  				} else {
   227  					return fmt.Errorf("cannot delete parent directory %q in control container %q: %v", dir, s.containerName, err)
   228  				}
   229  			}
   230  		}
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  func (s *JoyentStorage) RemoveAll() error {
   237  	names, err := storage.List(s, "")
   238  	if err != nil {
   239  		return err
   240  	}
   241  	// Remove all the objects in parallel so that we incur less round-trips.
   242  	// If we're in danger of having hundreds of objects,
   243  	// we'll want to change this to limit the number
   244  	// of concurrent operations.
   245  	var wg sync.WaitGroup
   246  	wg.Add(len(names))
   247  	errc := make(chan error, len(names))
   248  	for _, name := range names {
   249  		name := name
   250  		go func() {
   251  			defer wg.Done()
   252  			if err := s.Remove(name); err != nil {
   253  				errc <- err
   254  			}
   255  		}()
   256  	}
   257  	wg.Wait()
   258  	select {
   259  	case err := <-errc:
   260  		return fmt.Errorf("cannot delete all provider state: %v", err)
   261  	default:
   262  	}
   263  
   264  	s.Lock()
   265  	defer s.Unlock()
   266  	// Even DeleteContainer fails, it won't harm if we try again - the
   267  	// operation might have succeeded even if we get an error.
   268  	s.madeContainer = false
   269  	if err = s.manta.DeleteDirectory(s.containerName); err != nil {
   270  		return err
   271  	}
   272  	return nil
   273  }
   274  
   275  func (s *JoyentStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
   276  	return utils.AttemptStrategy{}
   277  }
   278  
   279  func (s *JoyentStorage) ShouldRetry(err error) bool {
   280  	return false
   281  }