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 }