github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/gcs/fake/fake.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fake 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "io" 25 "net/http" 26 "sync" 27 28 "cloud.google.com/go/storage" 29 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 30 "google.golang.org/api/googleapi" 31 "google.golang.org/api/iterator" 32 ) 33 34 // ConditionalClient is a fake conditional client that can limit actions to matching conditions. 35 type ConditionalClient struct { 36 UploadClient 37 read, write *storage.Conditions 38 Lock *sync.RWMutex 39 } 40 41 func (cc *ConditionalClient) check(ctx context.Context, from, to *gcs.Path) error { 42 if from != nil && cc.read != nil { 43 attrs, err := cc.UploadClient.Stat(ctx, *from) 44 switch { 45 case err != nil: 46 return err 47 case cc.read.GenerationMatch != 0 && cc.read.GenerationMatch != attrs.Generation: 48 return fmt.Errorf("bad genneration due to GenerationMatch: %w", &googleapi.Error{ 49 Code: http.StatusPreconditionFailed, 50 }) 51 case cc.read.GenerationNotMatch != 0 && cc.read.GenerationNotMatch == attrs.Generation: 52 return fmt.Errorf("bad generation due to GenerationNotMatch: %w", &googleapi.Error{ 53 Code: http.StatusPreconditionFailed, 54 }) 55 } 56 } 57 if to != nil && cc.write != nil { 58 attrs, err := cc.UploadClient.Stat(ctx, *to) 59 switch { 60 case err == storage.ErrObjectNotExist: 61 if cc.write.GenerationMatch != 0 { 62 return fmt.Errorf("bad generation: %w", &googleapi.Error{ 63 Code: http.StatusPreconditionFailed, 64 }) 65 } 66 case err != nil: 67 return err 68 case cc.write.GenerationMatch != 0 && cc.write.GenerationMatch != attrs.Generation: 69 return fmt.Errorf("bad generation due to GenerationMatch: %w", &googleapi.Error{ 70 Code: http.StatusPreconditionFailed, 71 }) 72 case cc.write.GenerationNotMatch != 0 && cc.write.GenerationNotMatch == attrs.Generation: 73 return fmt.Errorf("bad generation due to GenerationNotMatch: %w", &googleapi.Error{ 74 Code: http.StatusPreconditionFailed, 75 }) 76 } 77 } 78 return nil 79 } 80 81 // Copy copies the contents of 'from' into 'to'. 82 func (cc *ConditionalClient) Copy(ctx context.Context, from, to gcs.Path) (*storage.ObjectAttrs, error) { 83 if cc.Lock != nil { 84 cc.Lock.Lock() 85 defer cc.Lock.Unlock() 86 } 87 if err := cc.check(ctx, &from, &to); err != nil { 88 return nil, err 89 } 90 91 gen := cc.Uploader[to].Generation + 1 92 if _, err := cc.UploadClient.Copy(ctx, from, to); err != nil { 93 return nil, err 94 } 95 u := cc.Uploader[to] 96 u.Generation = gen 97 cc.Uploader[to] = u 98 return u.Attrs(to), nil 99 } 100 101 // Upload writes content to the given path. 102 func (cc *ConditionalClient) Upload(ctx context.Context, path gcs.Path, buf []byte, worldRead bool, cache string) (*storage.ObjectAttrs, error) { 103 if cc.Lock != nil { 104 cc.Lock.Lock() 105 defer cc.Lock.Unlock() 106 } 107 if err := cc.check(ctx, nil, &path); err != nil { 108 return nil, err 109 } 110 111 gen := cc.Uploader[path].Generation + 1 112 _, err := cc.UploadClient.Upload(ctx, path, buf, worldRead, cache) 113 if err != nil { 114 return nil, err 115 } 116 117 u := cc.Uploader[path] 118 u.Generation = gen 119 cc.Uploader[path] = u 120 return u.Attrs(path), nil 121 } 122 123 // If returns a fake conditional client. 124 func (cc *ConditionalClient) If(read, write *storage.Conditions) gcs.ConditionalClient { 125 return &ConditionalClient{ 126 UploadClient: cc.UploadClient, 127 read: read, 128 write: write, 129 Lock: cc.Lock, 130 } 131 } 132 133 // Open the path conditionally. 134 func (cc *ConditionalClient) Open(ctx context.Context, path gcs.Path) (io.ReadCloser, *storage.ReaderObjectAttrs, error) { 135 if cc.Lock != nil { 136 cc.Lock.RLock() 137 defer cc.Lock.RUnlock() 138 } 139 if err := cc.check(ctx, &path, nil); err != nil { 140 return nil, nil, err 141 } 142 return cc.UploadClient.Open(ctx, path) 143 } 144 145 // Objects in the path. 146 func (cc *ConditionalClient) Objects(ctx context.Context, path gcs.Path, _, offset string) gcs.Iterator { 147 if cc.Lock != nil { 148 cc.Lock.RLock() 149 defer cc.Lock.RUnlock() 150 } 151 return cc.UploadClient.Objects(ctx, path, "", offset) 152 } 153 154 // Stat about the path, such as size, generation, etc. 155 func (cc *ConditionalClient) Stat(ctx context.Context, path gcs.Path) (*storage.ObjectAttrs, error) { 156 if cc.Lock != nil { 157 cc.Lock.RLock() 158 defer cc.Lock.RUnlock() 159 } 160 if err := cc.check(ctx, &path, nil); err != nil { 161 return nil, err 162 } 163 return cc.UploadClient.Stat(ctx, path) 164 } 165 166 // UploadClient is a fake upload client 167 type UploadClient struct { 168 Client 169 Uploader 170 Stater 171 } 172 173 // If returns a fake upload client. 174 func (fuc UploadClient) If(read, write *storage.Conditions) gcs.ConditionalClient { 175 return fuc 176 } 177 178 // Stat contains object attributes for a given path. 179 type Stat struct { 180 Err error 181 Attrs storage.ObjectAttrs 182 } 183 184 // Stater stats given paths. 185 type Stater map[gcs.Path]Stat 186 187 // Stat returns object attributes for a given path. 188 func (fs Stater) Stat(ctx context.Context, path gcs.Path) (*storage.ObjectAttrs, error) { 189 if err := ctx.Err(); err != nil { 190 return nil, fmt.Errorf("injected interrupt: %w", err) 191 } 192 193 ret, ok := fs[path] 194 if !ok { 195 return nil, storage.ErrObjectNotExist 196 } 197 if ret.Err != nil { 198 return nil, fmt.Errorf("injected upload error: %w", ret.Err) 199 } 200 return &ret.Attrs, nil 201 } 202 203 // Uploader adds upload capabilities to a fake client. 204 type Uploader map[gcs.Path]Upload 205 206 // Copy an object to the specified path 207 func (fu Uploader) Copy(ctx context.Context, from, to gcs.Path) (*storage.ObjectAttrs, error) { 208 if err := ctx.Err(); err != nil { 209 return nil, fmt.Errorf("injected interrupt: %w", err) 210 } 211 u, present := fu[from] 212 if !present { 213 return nil, storage.ErrObjectNotExist 214 } 215 if err := u.Err; err != nil { 216 return nil, fmt.Errorf("injected from error: %w", err) 217 } 218 219 u.Generation++ 220 fu[to] = u 221 return u.Attrs(to), nil 222 } 223 224 // Upload writes content to the given path. 225 func (fu Uploader) Upload(ctx context.Context, path gcs.Path, buf []byte, worldRead bool, cacheControl string) (*storage.ObjectAttrs, error) { 226 if err := ctx.Err(); err != nil { 227 return nil, fmt.Errorf("injected interrupt: %w", err) 228 } 229 if err := fu[path].Err; err != nil { 230 return nil, fmt.Errorf("injected upload error: %w", err) 231 } 232 233 u := Upload{ 234 Buf: buf, 235 CacheControl: cacheControl, 236 WorldRead: worldRead, 237 } 238 fu[path] = u 239 return u.Attrs(path), nil 240 } 241 242 // Upload represents an upload. 243 type Upload struct { 244 Buf []byte 245 CacheControl string 246 WorldRead bool 247 Err error 248 Generation int64 249 } 250 251 // Attrs returns file attributes. 252 func (u Upload) Attrs(path gcs.Path) *storage.ObjectAttrs { 253 return &storage.ObjectAttrs{ 254 Bucket: path.Bucket(), 255 Name: path.Object(), 256 CacheControl: u.CacheControl, 257 Generation: u.Generation, 258 } 259 } 260 261 // Opener opens given paths. 262 type Opener struct { 263 Paths map[gcs.Path]Object 264 Lock *sync.RWMutex 265 } 266 267 // Open returns a handle for a given path. 268 func (fo Opener) Open(ctx context.Context, path gcs.Path) (io.ReadCloser, *storage.ReaderObjectAttrs, error) { 269 if fo.Lock != nil { 270 fo.Lock.Lock() 271 defer fo.Lock.Unlock() 272 } 273 o, ok := fo.Paths[path] 274 if !ok { 275 return nil, nil, storage.ErrObjectNotExist 276 } 277 if o.OpenErr != nil { 278 // Only error is OpenErr is specified. 279 if !o.OpenOnRetry { 280 return nil, nil, o.OpenErr 281 } 282 // If retry is also specified, only error the first time. 283 if !o.OpenHasErred { 284 o.OpenHasErred = true 285 fo.Paths[path] = o 286 return nil, nil, o.OpenErr 287 } // else o.OpenOnRetry + o.OpenHasErred, so continue. 288 } 289 return &Reader{ 290 Buf: bytes.NewBufferString(o.Data), 291 ReadErr: o.ReadErr, 292 CloseErr: o.CloseErr, 293 }, o.Attrs, nil 294 } 295 296 // Object holds data for an object. 297 type Object struct { 298 Data string 299 Attrs *storage.ReaderObjectAttrs 300 OpenOnRetry bool // If true and OpenErr != nil, only error the first time Open() is called. 301 OpenHasErred bool 302 OpenErr error 303 ReadErr error 304 CloseErr error 305 } 306 307 // A Reader reads a file. 308 type Reader struct { 309 Buf *bytes.Buffer 310 ReadErr error 311 CloseErr error 312 } 313 314 // Read reads a file's contents. 315 func (fr *Reader) Read(p []byte) (int, error) { 316 if fr.ReadErr != nil { 317 return 0, fr.ReadErr 318 } 319 return fr.Buf.Read(p) 320 } 321 322 // Close closes a file. 323 func (fr *Reader) Close() error { 324 if fr.CloseErr != nil { 325 return fr.CloseErr 326 } 327 fr.ReadErr = errors.New("already closed") 328 fr.CloseErr = fr.ReadErr 329 return nil 330 } 331 332 // A Lister returns objects under a prefix. 333 type Lister map[gcs.Path]Iterator 334 335 // Objects returns an iterator of objects under a given path. 336 func (fl Lister) Objects(ctx context.Context, path gcs.Path, _, offset string) gcs.Iterator { 337 f := fl[path] 338 f.ctx = ctx 339 return &f 340 } 341 342 // An Iterator returns the attributes of the listed objects or an iterator.Done error. 343 type Iterator struct { 344 Objects []storage.ObjectAttrs 345 Idx int 346 Err int // must be > 0 347 ctx context.Context 348 Offset string 349 ErrOpen error 350 } 351 352 // A Client can list files and open them for reading. 353 type Client struct { 354 Lister 355 Opener 356 } 357 358 // Next returns the next value. 359 func (fi *Iterator) Next() (*storage.ObjectAttrs, error) { 360 if fi.ctx.Err() != nil { 361 return nil, fi.ctx.Err() 362 } 363 if fi.ErrOpen != nil { 364 return nil, fi.ErrOpen 365 } 366 for fi.Idx < len(fi.Objects) { 367 if fi.Offset == "" { 368 break 369 } 370 name, prefix := fi.Objects[fi.Idx].Name, fi.Objects[fi.Idx].Prefix 371 if name != "" && name < fi.Offset { 372 continue 373 } 374 if prefix != "" && prefix < fi.Offset { 375 continue 376 } 377 fi.Idx++ 378 } 379 if fi.Idx >= len(fi.Objects) { 380 return nil, iterator.Done 381 } 382 if fi.Idx > 0 && fi.Idx == fi.Err { 383 return nil, errors.New("injected Iterator error") 384 } 385 386 o := fi.Objects[fi.Idx] 387 fi.Idx++ 388 return &o, nil 389 }