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