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 }