github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	jujuerrors "github.com/juju/errors"
    13  	"github.com/juju/utils"
    14  	gooseerrors "launchpad.net/goose/errors"
    15  	"launchpad.net/goose/swift"
    16  
    17  	"github.com/juju/juju/environs/storage"
    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) (io.ReadCloser, error) {
    59  	r, _, err := s.swift.GetReader(s.containerName, file)
    60  	if err, _ := maybeNotFound(err); err != nil {
    61  		return nil, err
    62  	}
    63  	return r, nil
    64  }
    65  
    66  func (s *openstackstorage) URL(name string) (string, error) {
    67  	// 10 years should be good enough.
    68  	expires := time.Now().AddDate(10, 0, 0)
    69  	return s.swift.SignedURL(s.containerName, name, expires)
    70  }
    71  
    72  var storageAttempt = utils.AttemptStrategy{
    73  	// It seems Nova needs more time than EC2.
    74  	Total: 10 * time.Second,
    75  	Delay: 200 * time.Millisecond,
    76  }
    77  
    78  // DefaultConsistencyStrategy is specified in the StorageReader interface.
    79  func (s *openstackstorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
    80  	return storageAttempt
    81  }
    82  
    83  // ShouldRetry is specified in the StorageReader interface.
    84  func (s *openstackstorage) ShouldRetry(err error) bool {
    85  	_, retry := maybeNotFound(err)
    86  	return retry
    87  }
    88  
    89  func (s *openstackstorage) Remove(file string) error {
    90  	err := s.swift.DeleteObject(s.containerName, file)
    91  	// If we can't delete the object because the bucket doesn't
    92  	// exist, then we don't care.
    93  	if err, ok := maybeNotFound(err); !ok {
    94  		return err
    95  	}
    96  	return nil
    97  }
    98  
    99  func (s *openstackstorage) List(prefix string) ([]string, error) {
   100  	contents, err := s.swift.List(s.containerName, prefix, "", "", 0)
   101  	if err != nil {
   102  		// If the container is not found, it's not an error
   103  		// because it's only created when the first
   104  		// file is put.
   105  		if err, ok := maybeNotFound(err); !ok {
   106  			return nil, err
   107  		}
   108  		return nil, nil
   109  	}
   110  	var names []string
   111  	for _, item := range contents {
   112  		names = append(names, item.Name)
   113  	}
   114  	return names, nil
   115  }
   116  
   117  // Spawn this many goroutines to issue requests for deleting items from the
   118  // server. If only Openstack had a delete many request.
   119  const maxConcurrentDeletes = 8
   120  
   121  // RemoveAll is specified in the StorageWriter interface.
   122  func (s *openstackstorage) RemoveAll() error {
   123  	names, err := storage.List(s, "")
   124  	if err != nil {
   125  		return err
   126  	}
   127  	// Remove all the objects in parallel so as to minimize round-trips.
   128  	// Start with a goroutine feeding all the names that need to be
   129  	// deleted.
   130  	toDelete := make(chan string)
   131  	go func() {
   132  		for _, name := range names {
   133  			toDelete <- name
   134  		}
   135  		close(toDelete)
   136  	}()
   137  	// Now spawn up to N routines to actually issue the requests.
   138  	maxRoutines := len(names)
   139  	if maxConcurrentDeletes < maxRoutines {
   140  		maxRoutines = maxConcurrentDeletes
   141  	}
   142  	var wg sync.WaitGroup
   143  	wg.Add(maxRoutines)
   144  	// Make a channel long enough to buffer all possible errors.
   145  	errc := make(chan error, len(names))
   146  	for i := 0; i < maxRoutines; i++ {
   147  		go func() {
   148  			for name := range toDelete {
   149  				if err := s.Remove(name); err != nil {
   150  					errc <- err
   151  				}
   152  			}
   153  			wg.Done()
   154  		}()
   155  	}
   156  	wg.Wait()
   157  	select {
   158  	case err := <-errc:
   159  		return fmt.Errorf("cannot delete all provider state: %v", err)
   160  	default:
   161  	}
   162  
   163  	s.Lock()
   164  	defer s.Unlock()
   165  	// Even DeleteContainer fails, it won't harm if we try again - the
   166  	// operation might have succeeded even if we get an error.
   167  	s.madeContainer = false
   168  	err = s.swift.DeleteContainer(s.containerName)
   169  	err, ok := maybeNotFound(err)
   170  	if ok {
   171  		return nil
   172  	}
   173  	return err
   174  }
   175  
   176  // maybeNotFound returns a errors.NotFoundError if the root cause of the specified error is due to a file or
   177  // container not being found.
   178  func maybeNotFound(err error) (error, bool) {
   179  	if err != nil && gooseerrors.IsNotFound(err) {
   180  		return jujuerrors.NewNotFound(err, ""), true
   181  	}
   182  	return err, false
   183  }