github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/provider/dummy/storage.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package dummy
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"sort"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/utils"
    18  
    19  	"github.com/juju/juju/environs/storage"
    20  )
    21  
    22  // IsSameStorage returns whether the storage instances are the same.
    23  // Both storages must have been created through the dummy provider.
    24  func IsSameStorage(s1, s2 storage.Storage) bool {
    25  	localS1, localS2 := s1.(*dummyStorage), s2.(*dummyStorage)
    26  	return localS1.env.name == localS2.env.name
    27  }
    28  
    29  func (e *environ) Storage() storage.Storage {
    30  	return &dummyStorage{env: e}
    31  }
    32  
    33  // storageServer holds the storage for an environState.
    34  type storageServer struct {
    35  	path     string // path prefix in http space.
    36  	state    *environState
    37  	files    map[string][]byte
    38  	poisoned map[string]error
    39  }
    40  
    41  func newStorageServer(state *environState, path string) *storageServer {
    42  	return &storageServer{
    43  		state:    state,
    44  		files:    make(map[string][]byte),
    45  		path:     path,
    46  		poisoned: make(map[string]error),
    47  	}
    48  }
    49  
    50  // Poison causes all fetches of the given path to
    51  // return the given error.
    52  func Poison(ss storage.Storage, path string, poisonErr error) {
    53  	s := ss.(*dummyStorage)
    54  	srv, err := s.server()
    55  	if err != nil {
    56  		panic("cannot poison destroyed storage")
    57  	}
    58  	srv.state.mu.Lock()
    59  	srv.poisoned[path] = poisonErr
    60  	srv.state.mu.Unlock()
    61  }
    62  
    63  func (s *storageServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    64  	if req.Method != "GET" {
    65  		http.Error(w, "only GET is supported", http.StatusMethodNotAllowed)
    66  		return
    67  	}
    68  	data, err := s.dataWithDelay(req.URL.Path)
    69  	if err != nil {
    70  		http.Error(w, "404 "+err.Error(), http.StatusNotFound)
    71  		return
    72  	}
    73  	w.Header().Set("Content-Type", "application/octet-stream")
    74  	// If the write fails, the rest of the tests should pick up the problem.
    75  	// It's more likely because the client has legitimately dropped the
    76  	// connection.
    77  	w.Write(data)
    78  }
    79  
    80  // dataWithDelay returns the data for the given path,
    81  // waiting for the configured amount of time before
    82  // accessing it.
    83  func (s *storageServer) dataWithDelay(path string) (data []byte, err error) {
    84  	s.state.mu.Lock()
    85  	delay := s.state.storageDelay
    86  	s.state.mu.Unlock()
    87  	time.Sleep(delay)
    88  	s.state.mu.Lock()
    89  	defer s.state.mu.Unlock()
    90  	if err := s.poisoned[path]; err != nil {
    91  		return nil, err
    92  	}
    93  	data, ok := s.files[path]
    94  	if !ok {
    95  		return nil, errors.NotFoundf("file %q not found", path)
    96  	}
    97  	return data, nil
    98  }
    99  
   100  func (s *storageServer) Put(name string, r io.Reader, length int64) error {
   101  	// Allow Put to be poisoned as well.
   102  	if err := s.poisoned[name]; err != nil {
   103  		return err
   104  	}
   105  
   106  	// We only log Put requests on private storage.
   107  	if strings.HasSuffix(s.path, "/private") {
   108  		s.state.ops <- OpPutFile{s.state.name, name}
   109  	}
   110  	var buf bytes.Buffer
   111  	_, err := io.Copy(&buf, r)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	s.state.mu.Lock()
   116  	s.files[name] = buf.Bytes()
   117  	s.state.mu.Unlock()
   118  	return nil
   119  }
   120  
   121  func (s *storageServer) Get(name string) (io.ReadCloser, error) {
   122  	data, err := s.dataWithDelay(name)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return ioutil.NopCloser(bytes.NewBuffer(data)), nil
   127  }
   128  
   129  func (s *storageServer) URL(name string) (string, error) {
   130  	// Mimic the MAAS behaviour so we are testing with the
   131  	// lowest common denominator.
   132  	if name != "" {
   133  		if _, ok := s.files[name]; !ok {
   134  			found := false
   135  			for file, _ := range s.files {
   136  				found = strings.HasPrefix(file, name+"/")
   137  				if found {
   138  					break
   139  				}
   140  			}
   141  			if !found {
   142  				return "", errors.NotFoundf(name)
   143  			}
   144  		}
   145  	}
   146  	return fmt.Sprintf("http://%v%s/%s", s.state.httpListener.Addr(), s.path, name), nil
   147  }
   148  
   149  func (s *storageServer) Remove(name string) error {
   150  	s.state.mu.Lock()
   151  	delete(s.files, name)
   152  	s.state.mu.Unlock()
   153  	return nil
   154  }
   155  
   156  func (s *storageServer) DefaultConsistencyStrategy() utils.AttemptStrategy {
   157  	return utils.AttemptStrategy{}
   158  }
   159  
   160  // ShouldRetry is specified in the StorageReader interface.
   161  func (s *storageServer) ShouldRetry(err error) bool {
   162  	return false
   163  }
   164  
   165  func (s *storageServer) RemoveAll() error {
   166  	s.state.mu.Lock()
   167  	s.files = make(map[string][]byte)
   168  	s.state.mu.Unlock()
   169  	return nil
   170  }
   171  
   172  func (s *storageServer) List(prefix string) ([]string, error) {
   173  	s.state.mu.Lock()
   174  	defer s.state.mu.Unlock()
   175  	var names []string
   176  	for name := range s.files {
   177  		if strings.HasPrefix(name, prefix) {
   178  			names = append(names, name)
   179  		}
   180  	}
   181  	sort.Strings(names)
   182  	return names, nil
   183  }
   184  
   185  // dummyStorage implements the client side of the Storage interface.
   186  type dummyStorage struct {
   187  	env *environ
   188  }
   189  
   190  // server returns the server side of the given storage.
   191  func (s *dummyStorage) server() (*storageServer, error) {
   192  	st, err := s.env.state()
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	return st.storage, nil
   197  }
   198  
   199  func (s *dummyStorage) Get(name string) (io.ReadCloser, error) {
   200  	srv, err := s.server()
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	return srv.Get(name)
   205  }
   206  
   207  func (s *dummyStorage) URL(name string) (string, error) {
   208  	srv, err := s.server()
   209  	if err != nil {
   210  		return "", err
   211  	}
   212  	return srv.URL(name)
   213  }
   214  
   215  func (s *dummyStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
   216  	return utils.AttemptStrategy{}
   217  }
   218  
   219  // ShouldRetry is specified in the StorageReader interface.
   220  func (s *dummyStorage) ShouldRetry(err error) bool {
   221  	return false
   222  }
   223  
   224  func (s *dummyStorage) Put(name string, r io.Reader, length int64) error {
   225  	srv, err := s.server()
   226  	if err != nil {
   227  		return err
   228  	}
   229  	return srv.Put(name, r, length)
   230  }
   231  
   232  func (s *dummyStorage) Remove(name string) error {
   233  	srv, err := s.server()
   234  	if err != nil {
   235  		return err
   236  	}
   237  	return srv.Remove(name)
   238  }
   239  
   240  func (s *dummyStorage) RemoveAll() error {
   241  	srv, err := s.server()
   242  	if err != nil {
   243  		return err
   244  	}
   245  	return srv.RemoveAll()
   246  }
   247  
   248  func (s *dummyStorage) List(prefix string) ([]string, error) {
   249  	srv, err := s.server()
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  	return srv.List(prefix)
   254  }