github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "gopkg.in/goose.v1/errors"
    15  	"gopkg.in/goose.v1/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  	// Prevent a blank containerName combo being used, which
    91  	// can result in erroneously deleting the Swift account itself.
    92  	if s.containerName == "" {
    93  		return jujuerrors.Errorf("cannot remove %q: swift container name is empty", file)
    94  	}
    95  	err := s.swift.DeleteObject(s.containerName, file)
    96  	// If we can't delete the object because the bucket doesn't
    97  	// exist, then we don't care.
    98  	if err, ok := maybeNotFound(err); !ok {
    99  		return err
   100  	}
   101  	return nil
   102  }
   103  
   104  func (s *openstackstorage) List(prefix string) ([]string, error) {
   105  	contents, err := s.swift.List(s.containerName, prefix, "", "", 0)
   106  	if err != nil {
   107  		// If the container is not found, it's not an error
   108  		// because it's only created when the first
   109  		// file is put.
   110  		if err, ok := maybeNotFound(err); !ok {
   111  			return nil, err
   112  		}
   113  		return nil, nil
   114  	}
   115  	var names []string
   116  	for _, item := range contents {
   117  		names = append(names, item.Name)
   118  	}
   119  	return names, nil
   120  }
   121  
   122  // Spawn this many goroutines to issue requests for deleting items from the
   123  // server. If only Openstack had a delete many request.
   124  const maxConcurrentDeletes = 8
   125  
   126  // RemoveAll is specified in the StorageWriter interface.
   127  func (s *openstackstorage) RemoveAll() error {
   128  	names, err := storage.List(s, "")
   129  	if err != nil {
   130  		return err
   131  	}
   132  	// Remove all the objects in parallel so as to minimize round-trips.
   133  	// Start with a goroutine feeding all the names that need to be
   134  	// deleted.
   135  	toDelete := make(chan string)
   136  	go func() {
   137  		for _, name := range names {
   138  			toDelete <- name
   139  		}
   140  		close(toDelete)
   141  	}()
   142  	// Now spawn up to N routines to actually issue the requests.
   143  	maxRoutines := len(names)
   144  	if maxConcurrentDeletes < maxRoutines {
   145  		maxRoutines = maxConcurrentDeletes
   146  	}
   147  	var wg sync.WaitGroup
   148  	wg.Add(maxRoutines)
   149  	// Make a channel long enough to buffer all possible errors.
   150  	errc := make(chan error, len(names))
   151  	for i := 0; i < maxRoutines; i++ {
   152  		go func() {
   153  			for name := range toDelete {
   154  				if err := s.Remove(name); err != nil {
   155  					errc <- err
   156  				}
   157  			}
   158  			wg.Done()
   159  		}()
   160  	}
   161  	wg.Wait()
   162  	select {
   163  	case err := <-errc:
   164  		return fmt.Errorf("cannot delete all provider state: %v", err)
   165  	default:
   166  	}
   167  
   168  	s.Lock()
   169  	defer s.Unlock()
   170  	// Even DeleteContainer fails, it won't harm if we try again - the
   171  	// operation might have succeeded even if we get an error.
   172  	s.madeContainer = false
   173  	err = s.swift.DeleteContainer(s.containerName)
   174  	err, ok := maybeNotFound(err)
   175  	if ok {
   176  		return nil
   177  	}
   178  	return err
   179  }
   180  
   181  // maybeNotFound returns a errors.NotFoundError if the root cause of the specified error is due to a file or
   182  // container not being found.
   183  func maybeNotFound(err error) (error, bool) {
   184  	if err != nil && gooseerrors.IsNotFound(err) {
   185  		return jujuerrors.NewNotFound(err, ""), true
   186  	}
   187  	return err, false
   188  }