github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 }