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  }