go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/appengine/gaemiddleware/context.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 gaemiddleware 16 17 import ( 18 "context" 19 "net/http" 20 "sync" 21 22 "go.chromium.org/luci/gae/filter/dscache" 23 "go.chromium.org/luci/gae/filter/featureBreaker" 24 "go.chromium.org/luci/gae/filter/readonly" 25 "go.chromium.org/luci/gae/filter/txndefer" 26 "go.chromium.org/luci/gae/service/datastore" 27 28 "go.chromium.org/luci/common/data/caching/cacheContext" 29 "go.chromium.org/luci/common/errors" 30 "go.chromium.org/luci/common/logging" 31 32 "go.chromium.org/luci/server/caching" 33 "go.chromium.org/luci/server/router" 34 "go.chromium.org/luci/server/secrets" 35 "go.chromium.org/luci/server/settings" 36 "go.chromium.org/luci/server/warmup" 37 38 "go.chromium.org/luci/appengine/gaesecrets" 39 "go.chromium.org/luci/appengine/gaesettings" 40 ) 41 42 // errSimulatedMemcacheOutage is returned by all memcache calls if 43 // SimulateMemcacheOutage setting is enabled. 44 var errSimulatedMemcacheOutage = errors.New("simulated memcache outage") 45 46 // Environment is a middleware environment. Its parameters define how the 47 // middleware is applied, and which services are enlisted. 48 // 49 // This is low-level API. Use 'gaemiddeware/standard' instead. 50 type Environment struct { 51 // MemcacheAvailable is true if the environment has working memcache. 52 // 53 // If false, also implies disabled datastore caching layer. 54 MemcacheAvailable bool 55 56 // DSReadOnly, if true, causes a read-only datastore layer to be imposed, 57 // preventing datastore writes. 58 // 59 // For any given datastore instance, at most one caching layer may be used. 60 // All other instances must be ReadOnly to prevent errant writes from breaking 61 // the assumptions of that caching layer. For example, if a Flex VM is being 62 // used in conjunction with a non-read-only Classic AppEngine instance. 63 DSReadOnly bool 64 65 // DSReadOnlyPredicate returns true for keys that must not be mutated. 66 // 67 // Effective only when DSReadOnly is true. If nil, all keys are considered 68 // read-only. 69 DSReadOnlyPredicate readonly.Predicate 70 71 // Prepare will be called once after init() time, but before serving requests. 72 // 73 // The given context is very bare, use it only for logging and deadlines and 74 // stuff like that. It has no other services installed. 75 Prepare func(context.Context) 76 77 // WithInitialRequest is called at the very beginning of the handler. It 78 // contains a reference to the handler's HTTP request. 79 // 80 // This should install basic services into the Context, including: 81 // - Logging 82 // - luci/GAE services. 83 WithInitialRequest func(context.Context, *http.Request) context.Context 84 85 // WithConfig is called during service setup to install the "gaeconfig" layer. 86 // 87 // If nil, no config layer will be installed. 88 WithConfig func(context.Context) context.Context 89 90 // WithAuth is called during service setup to install the "gaeauth" layer. 91 // 92 // If nil, no auth layer will be installed. 93 WithAuth func(context.Context) context.Context 94 95 // ExtraMiddleware, if not nil, is additional middleware chain to append to 96 // the end of the Base middleware chain to perform per-request monitoring. 97 ExtraMiddleware router.MiddlewareChain 98 99 // ExtraHandlers, if not nil, is used to install additional handlers when 100 // InstallHandlers is called. 101 ExtraHandlers func(r *router.Router, base router.MiddlewareChain) 102 103 prepareOnce sync.Once 104 105 // processCacheData holds all global LRU caches. 106 processCacheData *caching.ProcessCacheData 107 // globalSettings holds global app settings lazily updated from the datastore. 108 globalSettings *settings.Settings 109 } 110 111 // ensurePrepared is called before handling requests to initialize global state. 112 func (e *Environment) ensurePrepared(ctx context.Context) { 113 e.prepareOnce.Do(func() { 114 e.processCacheData = caching.NewProcessCacheData() 115 e.globalSettings = settings.New(gaesettings.Storage{}) 116 if e.Prepare != nil { 117 e.Prepare(ctx) 118 } 119 }) 120 } 121 122 // InstallHandlers installs handlers for an Environment's framework routes. 123 // 124 // See InstallHandlersWithMiddleware for more information. 125 func (e *Environment) InstallHandlers(r *router.Router) { 126 e.InstallHandlersWithMiddleware(r, e.Base()) 127 } 128 129 // InstallHandlersWithMiddleware installs handlers for an Environment's 130 // framework routes. 131 // 132 // In addition to Environment-specific handlers, InstallHandlersWithMiddleware 133 // installs: 134 // - Warmup Handler (warmup) 135 func (e *Environment) InstallHandlersWithMiddleware(r *router.Router, base router.MiddlewareChain) { 136 warmup.InstallHandlersDeprecated(r, base) 137 138 if e.ExtraHandlers != nil { 139 e.ExtraHandlers(r, base) 140 } 141 } 142 143 // With adds various production GAE LUCI services to the context. 144 // 145 // Basically, it installs GAE-specific backends and caches for various 146 // subsystems to make them work in GAE environment. 147 // 148 // One example is a backend for Logging: go.chromium.org/luci/common/logging. 149 // Logs emitted through a WithProd() context go to GAE logs. 150 // 151 // 'Production' here means the services will use real GAE APIs (not mocks or 152 // stubs), so With should never be used from unit tests. 153 func (e *Environment) With(ctx context.Context, req *http.Request) context.Context { 154 // Set an initial logging level. We'll configure this to be more specific 155 // later once we can load settings. 156 ctx = logging.SetLevel(ctx, logging.Debug) 157 158 // Ensure one-time initialization happened. 159 e.ensurePrepared(ctx) 160 161 // Install process and request LRU caches. 162 ctx = caching.WithProcessCacheData(ctx, e.processCacheData) 163 ctx = caching.WithRequestCache(ctx) 164 165 ctx = e.WithInitialRequest(ctx, req) 166 167 // A previous layer must have installed a "luci/gae" datastore. 168 if datastore.Raw(ctx) == nil { 169 panic("no luci/gae datastore is installed") 170 } 171 172 // The global cache depends on luci/gae's memcache installed. 173 if e.MemcacheAvailable { 174 ctx = caching.WithGlobalCache(ctx, blobCacheProvider) 175 } 176 177 // These are needed to use fetchCachedSettings. 178 ctx = settings.Use(ctx, e.globalSettings) 179 180 // Fetch and apply configuration stored in the datastore. 181 cachedSettings := fetchCachedSettings(ctx) 182 ctx = logging.SetLevel(ctx, cachedSettings.LoggingLevel) 183 if e.MemcacheAvailable { 184 if bool(cachedSettings.SimulateMemcacheOutage) { 185 logging.Warningf(ctx, "Memcache outage simulation is enabled") 186 var fb featureBreaker.FeatureBreaker 187 ctx, fb = featureBreaker.FilterMC(ctx, errSimulatedMemcacheOutage) 188 fb.BreakFeatures(nil, 189 "GetMulti", "AddMulti", "SetMulti", "DeleteMulti", 190 "CompareAndSwapMulti", "Flush", "Stats") 191 } 192 if !bool(cachedSettings.DisableDSCache) { 193 ctx = dscache.FilterRDS(ctx, nil) 194 } 195 } 196 if e.DSReadOnly { 197 ctx = readonly.FilterRDS(ctx, e.DSReadOnlyPredicate) 198 } 199 ctx = txndefer.FilterRDS(ctx) 200 201 // The rest of the service may use applied configuration. 202 if e.WithConfig != nil { 203 ctx = e.WithConfig(ctx) 204 } 205 ctx = secrets.Use(ctx, gaesecrets.New(nil)) 206 if e.WithAuth != nil { 207 ctx = e.WithAuth(ctx) 208 } 209 210 // Wrap this in a cache context so that lookups for any of the aforementioned 211 // items are fast. 212 return cacheContext.Wrap(ctx) 213 } 214 215 // Base returns a middleware chain to use for all GAE environment 216 // requests. 217 // 218 // Base DOES NOT install "luci/gae" services. To install appropriate services, 219 // use methods from a sub-package. This is done so that a given AppEngine 220 // environment doesn't need to include the superset of packages across all 221 // supported environments. 222 // 223 // This middleware chain installs prod GAE services into the request context 224 // (via With), and wraps the request with a panic catcher and monitoring 225 // hooks. 226 func (e *Environment) Base() router.MiddlewareChain { 227 // addServices is a middleware that installs the set of standard production 228 // AppEngine services by calling With. 229 addServices := func(c *router.Context, next router.Handler) { 230 // Create a cancelable Context that cancels when this request finishes. 231 // 232 // We do this because Contexts will leak resources and/or goroutines if they 233 // have timers or can be canceled, but aren't. Predictably canceling the 234 // parent will ensure that any such resource leaks are cleaned up. 235 ctx, cancelFunc := context.WithCancel(c.Request.Context()) 236 defer cancelFunc() 237 238 // Apply production settings. 239 c.Request = c.Request.WithContext(e.With(ctx, c.Request)) 240 241 next(c) 242 } 243 244 mw := router.NewMiddlewareChain(addServices) 245 mw = mw.Extend(e.ExtraMiddleware...) 246 return mw 247 }