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 }