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 }