launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/provider/openstack/storage.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  	"time"
    11  
    12  	gooseerrors "launchpad.net/goose/errors"
    13  	"launchpad.net/goose/swift"
    14  
    15  	"launchpad.net/juju-core/environs/storage"
    16  	coreerrors "launchpad.net/juju-core/errors"
    17  	"launchpad.net/juju-core/utils"
    18  )
    19  
    20  // openstackstorage implements storage.Storage on an OpenStack container.
    21  type openstackstorage struct {
    22  	sync.Mutex
    23  	madeContainer bool
    24  	containerName string
    25  	containerACL  swift.ACL
    26  	swift         *swift.Client
    27  }
    28  
    29  // makeContainer makes the environment's control container, the
    30  // place where bootstrap information and deployed charms
    31  // are stored. To avoid two round trips on every PUT operation,
    32  // we do this only once for each environ.
    33  func (s *openstackstorage) makeContainer(containerName string, containerACL swift.ACL) error {
    34  	s.Lock()
    35  	defer s.Unlock()
    36  	if s.madeContainer {
    37  		return nil
    38  	}
    39  	// try to make the container - CreateContainer will succeed if the container already exists.
    40  	err := s.swift.CreateContainer(containerName, containerACL)
    41  	if err == nil {
    42  		s.madeContainer = true
    43  	}
    44  	return err
    45  }
    46  
    47  func (s *openstackstorage) Put(file string, r io.Reader, length int64) error {
    48  	if err := s.makeContainer(s.containerName, s.containerACL); err != nil {
    49  		return fmt.Errorf("cannot make Swift control container: %v", err)
    50  	}
    51  	err := s.swift.PutReader(s.containerName, file, r, length)
    52  	if err != nil {
    53  		return fmt.Errorf("cannot write file %q to control container %q: %v", file, s.containerName, err)
    54  	}
    55  	return nil
    56  }
    57  
    58  func (s *openstackstorage) Get(file string) (r io.ReadCloser, err error) {
    59  	r, err = s.swift.GetReader(s.containerName, file)
    60  	err, _ = maybeNotFound(err)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return r, nil
    65  }
    66  
    67  func (s *openstackstorage) URL(name string) (string, error) {
    68  	// 10 years should be good enough.
    69  	expires := time.Now().AddDate(10, 0, 0)
    70  	return s.swift.SignedURL(s.containerName, name, expires)
    71  }
    72  
    73  var storageAttempt = utils.AttemptStrategy{
    74  	// It seems Nova needs more time than EC2.
    75  	Total: 10 * time.Second,
    76  	Delay: 200 * time.Millisecond,
    77  }
    78  
    79  // ConsistencyStrategy is specified in the StorageReader interface.
    80  func (s *openstackstorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
    81  	return storageAttempt
    82  }
    83  
    84  // ShouldRetry is specified in the StorageReader interface.
    85  func (s *openstackstorage) ShouldRetry(err error) bool {
    86  	_, retry := maybeNotFound(err)
    87  	return retry
    88  }
    89  
    90  func (s *openstackstorage) Remove(file string) error {
    91  	err := s.swift.DeleteObject(s.containerName, file)
    92  	// If we can't delete the object because the bucket doesn't
    93  	// exist, then we don't care.
    94  	if err, ok := maybeNotFound(err); !ok {
    95  		return err
    96  	}
    97  	return nil
    98  }
    99  
   100  func (s *openstackstorage) List(prefix string) ([]string, error) {
   101  	contents, err := s.swift.List(s.containerName, prefix, "", "", 0)
   102  	if err != nil {
   103  		// If the container is not found, it's not an error
   104  		// because it's only created when the first
   105  		// file is put.
   106  		if err, ok := maybeNotFound(err); !ok {
   107  			return nil, err
   108  		}
   109  		return nil, nil
   110  	}
   111  	var names []string
   112  	for _, item := range contents {
   113  		names = append(names, item.Name)
   114  	}
   115  	return names, nil
   116  }
   117  
   118  // Spawn this many goroutines to issue requests for deleting items from the
   119  // server. If only Openstack had a delete many request.
   120  const maxConcurrentDeletes = 8
   121  
   122  // RemoveAll is specified in the StorageWriter interface.
   123  func (s *openstackstorage) RemoveAll() error {
   124  	names, err := storage.List(s, "")
   125  	if err != nil {
   126  		return err
   127  	}
   128  	// Remove all the objects in parallel so as to minimize round-trips.
   129  	// Start with a goroutine feeding all the names that need to be
   130  	// deleted.
   131  	toDelete := make(chan string)
   132  	go func() {
   133  		for _, name := range names {
   134  			toDelete <- name
   135  		}
   136  		close(toDelete)
   137  	}()
   138  	// Now spawn up to N routines to actually issue the requests.
   139  	maxRoutines := len(names)
   140  	if maxConcurrentDeletes < maxRoutines {
   141  		maxRoutines = maxConcurrentDeletes
   142  	}
   143  	var wg sync.WaitGroup
   144  	wg.Add(maxRoutines)
   145  	// Make a channel long enough to buffer all possible errors.
   146  	errc := make(chan error, len(names))
   147  	for i := 0; i < maxRoutines; i++ {
   148  		go func() {
   149  			for name := range toDelete {
   150  				if err := s.Remove(name); err != nil {
   151  					errc <- err
   152  				}
   153  			}
   154  			wg.Done()
   155  		}()
   156  	}
   157  	wg.Wait()
   158  	select {
   159  	case err := <-errc:
   160  		return fmt.Errorf("cannot delete all provider state: %v", err)
   161  	default:
   162  	}
   163  
   164  	s.Lock()
   165  	defer s.Unlock()
   166  	// Even DeleteContainer fails, it won't harm if we try again - the
   167  	// operation might have succeeded even if we get an error.
   168  	s.madeContainer = false
   169  	err = s.swift.DeleteContainer(s.containerName)
   170  	err, ok := maybeNotFound(err)
   171  	if ok {
   172  		return nil
   173  	}
   174  	return err
   175  }
   176  
   177  // maybeNotFound returns a errors.NotFoundError if the root cause of the specified error is due to a file or
   178  // container not being found.
   179  func maybeNotFound(err error) (error, bool) {
   180  	if err != nil && gooseerrors.IsNotFound(err) {
   181  		return coreerrors.NewNotFoundError(err, ""), true
   182  	}
   183  	return err, false
   184  }