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