go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/filter/readonly/rds.go (about)

     1  // Copyright 2017 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package readonly implements a filter that enforces read-only accesses to
    16  // datastore.
    17  //
    18  // This is useful in hybrid environments where one cluster wants to read from
    19  // a cache-backed datastore, but cannot modify the cache, so reads are safe and
    20  // direct, but writes would create a state where the cached values are invalid.
    21  // This happens when mixing AppEngine datastore/memcache with Cloud Datastore
    22  // readers.
    23  package readonly
    24  
    25  import (
    26  	"context"
    27  
    28  	"go.chromium.org/luci/common/errors"
    29  
    30  	ds "go.chromium.org/luci/gae/service/datastore"
    31  )
    32  
    33  // ErrReadOnly is an error returned in response to mutating datastore
    34  // operations.
    35  var ErrReadOnly = errors.New("readonly: datastore is read-only")
    36  
    37  // readOnlyDatastore is a datastore.RawInterface implementation that returns
    38  // ErrReadOnly on mutating operations.
    39  type readOnlyDatastore struct {
    40  	ds.RawInterface
    41  	isRO Predicate
    42  }
    43  
    44  type perKeyCB func(idx int, err error)
    45  type implCB func(mutable []int, cb perKeyCB) error
    46  
    47  func (r *readOnlyDatastore) run(keys []*ds.Key, impl implCB, cb perKeyCB) error {
    48  	var mutable []int
    49  	if r.isRO != nil { // all keys are RO by default
    50  		for i, k := range keys {
    51  			if !r.isRO(k) {
    52  				mutable = append(mutable, i)
    53  			}
    54  		}
    55  	}
    56  
    57  	if len(mutable) != 0 {
    58  		if err := impl(mutable, cb); err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	cur := 0 // cursor inside 'mutable' array
    64  	for idx := 0; idx < len(keys); idx++ {
    65  		if cur != len(mutable) && idx == mutable[cur] {
    66  			cur++
    67  			continue // results for 'idx' was already delivered above
    68  		}
    69  		cb(idx, ErrReadOnly)
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  func (r *readOnlyDatastore) AllocateIDs(keys []*ds.Key, cb ds.NewKeyCB) error {
    76  	impl := func(mutable []int, cb perKeyCB) error {
    77  		mutableKeys := make([]*ds.Key, len(mutable))
    78  		for i, idx := range mutable {
    79  			mutableKeys[i] = keys[idx]
    80  		}
    81  		return r.RawInterface.AllocateIDs(mutableKeys, func(idx int, key *ds.Key, err error) {
    82  			cb(mutable[idx], err)
    83  		})
    84  	}
    85  	return r.run(keys, impl, func(idx int, err error) {
    86  		cb(idx, keys[idx], err)
    87  	})
    88  }
    89  
    90  func (r *readOnlyDatastore) DeleteMulti(keys []*ds.Key, cb ds.DeleteMultiCB) error {
    91  	impl := func(mutable []int, cb perKeyCB) error {
    92  		mutableKeys := make([]*ds.Key, len(mutable))
    93  		for i, idx := range mutable {
    94  			mutableKeys[i] = keys[idx]
    95  		}
    96  		return r.RawInterface.DeleteMulti(mutableKeys, func(idx int, err error) {
    97  			cb(mutable[idx], err)
    98  		})
    99  	}
   100  	return r.run(keys, impl, perKeyCB(cb))
   101  }
   102  
   103  func (r *readOnlyDatastore) PutMulti(keys []*ds.Key, vals []ds.PropertyMap, cb ds.NewKeyCB) error {
   104  	impl := func(mutable []int, cb perKeyCB) error {
   105  		mutableKeys := make([]*ds.Key, len(mutable))
   106  		mutableVals := make([]ds.PropertyMap, len(mutable))
   107  		for i, idx := range mutable {
   108  			mutableKeys[i] = keys[idx]
   109  			mutableVals[i] = vals[idx]
   110  		}
   111  		return r.RawInterface.PutMulti(mutableKeys, mutableVals, func(idx int, key *ds.Key, err error) {
   112  			cb(mutable[idx], err)
   113  		})
   114  	}
   115  	return r.run(keys, impl, func(idx int, err error) {
   116  		cb(idx, keys[idx], err)
   117  	})
   118  }
   119  
   120  // Predicate is a user-supplied function that examines a key and returns true if
   121  // it should be treated as read-only.
   122  type Predicate func(*ds.Key) (isReadOnly bool)
   123  
   124  // FilterRDS installs a read-only datastore filter in the context.
   125  //
   126  // This enforces that mutating datastore operations that touch keys for which
   127  // the predicate returns 'true' fail with ErrReadOnly.
   128  //
   129  // If the predicate is nil, all mutating operations are denied.
   130  func FilterRDS(c context.Context, p Predicate) context.Context {
   131  	return ds.AddRawFilters(c, func(ic context.Context, inner ds.RawInterface) ds.RawInterface {
   132  		return &readOnlyDatastore{inner, p}
   133  	})
   134  }