github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "github.com/go-goose/goose/v5/swift" 13 jujuerrors "github.com/juju/errors" 14 "github.com/juju/utils/v3" 15 16 "github.com/juju/juju/environs/storage" 17 ) 18 19 // openstackstorage implements storage.Storage on an OpenStack container. 20 type openstackstorage struct { 21 sync.Mutex 22 madeContainer bool 23 containerName string 24 containerACL swift.ACL 25 swift *swift.Client 26 } 27 28 // makeContainer makes the environment's control container, the 29 // place where bootstrap information and deployed charms 30 // are stored. To avoid two round trips on every PUT operation, 31 // we do this only once for each environ. 32 func (s *openstackstorage) makeContainer(containerName string, containerACL swift.ACL) error { 33 s.Lock() 34 defer s.Unlock() 35 if s.madeContainer { 36 return nil 37 } 38 // try to make the container - CreateContainer will succeed if the container already exists. 39 err := s.swift.CreateContainer(containerName, containerACL) 40 if err == nil { 41 s.madeContainer = true 42 } 43 return err 44 } 45 46 func (s *openstackstorage) Put(file string, r io.Reader, length int64) error { 47 if err := s.makeContainer(s.containerName, s.containerACL); err != nil { 48 return fmt.Errorf("cannot make Swift control container: %v", err) 49 } 50 err := s.swift.PutReader(s.containerName, file, r, length) 51 if err != nil { 52 return fmt.Errorf("cannot write file %q to control container %q: %v", file, s.containerName, err) 53 } 54 return nil 55 } 56 57 func (s *openstackstorage) Get(file string) (io.ReadCloser, error) { 58 r, _, err := s.swift.GetReader(s.containerName, file) 59 if err, _ := maybeNotFound(err); err != nil { 60 return nil, err 61 } 62 return r, nil 63 } 64 65 func (s *openstackstorage) URL(name string) (string, error) { 66 // 10 years should be good enough. 67 // TODO(perrito666) 2016-05-02 lp:1558657 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 && IsNotFoundError(err) { 185 return jujuerrors.NewNotFound(err, ""), true 186 } 187 return err, false 188 }