@@ -29,14 +29,15 @@ const KIND_MAP = new Map([
2929
3030const LRU = require ( 'lru-cache' ) ;
3131const LruOptions = {
32- maxSize : 100000 , // the max cache size
32+ maxSize : parseInt ( process . env . FETCHENVS_CACHE_SIZE ) || 100000 , // the max cache size
3333 sizeCalculation : ( r ) => { return ( JSON . stringify ( r ) . length ) ; } , // how to determine the size of a resource added to the cache
3434 ttl : 1000 * 60 * 3 , // max time to cache (LRU does not directly enforce, but maxSize will eventually push them out)
3535 updateAgeOnGet : false , // Don't update ttl when an item is retrieved from cache
3636 updateAgeOnHas : false , // Don't update ttl when an item is checked in cache
3737} ;
3838const globalResourceCache = new LRU ( LruOptions ) ;
3939const globalResourceCacheUsers = new Set ( ) ;
40+ const singleResourceQueryCache = { } ;
4041
4142module . exports = class FetchEnvs {
4243
@@ -58,39 +59,26 @@ module.exports = class FetchEnvs {
5859 ( ( ) => { log . debug ( '\'updateRazeeLogs()\' not passed to fetchEnvs. will not update razeeLogs on failure to fetch envs' ) ; } ) ;
5960
6061 const user = this . data ?. object ?. spec ?. clusterAuth ?. impersonateUser ;
61- if ( process . env . INSTANCE_FETCHENVS_CACHE_ONLY ) {
62- // Using `user` is not technically necessary for an instance-specific cache, but used for consistency
63- log . info ( 'FetchEnvs.constructor using instance-specific resource cache' ) ;
64- this . instanceCache = { } ;
65- this . resourceCache = {
66- has : ( key ) => {
67- const hit = Object . prototype . hasOwnProperty . call ( this . instanceCache , `${ user } /${ key } ` ) ;
68- log . info ( `FetchEnvs cache ${ hit ?'HIT' :'MISS' } : '${ user } /${ key } '` ) ;
69- return hit ;
70- } ,
71- set : ( key , value ) => { this . instanceCache [ `${ user } /${ key } ` ] = value ; } ,
72- get : ( key ) => { return this . instanceCache [ `${ user } /${ key } ` ] ; } ,
73- } ;
74- }
75- else {
76- log . info ( `FetchEnvs.constructor using global resource cache, ${ globalResourceCache . size } resources currently cached (may be TTL expired)` ) ;
77- this . resourceCache = {
78- has : ( key ) => {
79- const hit = globalResourceCache . has ( `${ user } /${ key } ` ) ;
80- log . info ( `FetchEnvs cache ${ hit ?'HIT' :'MISS' } : '${ user } /${ key } '` ) ;
81- return hit ;
82- } ,
83- set : ( key , value ) => {
84- // When setting a key, keep track of users to allow later deletion
85- globalResourceCacheUsers . add ( user ) ;
86- globalResourceCache . set ( `${ user } /${ key } ` , value ) ;
87- log . info ( `FetchEnvs cached '${ user } /${ key } '` ) ;
88- } ,
89- get : ( key ) => {
90- return globalResourceCache . get ( `${ user } /${ key } ` ) ;
91- } ,
92- } ;
93- }
62+ this . resourceCache = {
63+ has : ( key ) => {
64+ const hit = globalResourceCache . has ( `${ user } /${ key } ` ) ;
65+ if ( hit ) log . info ( `FetchEnvs cache HIT: '${ user } /${ key } '` ) ;
66+ return hit ;
67+ } ,
68+ set : ( key , value ) => {
69+ log . info ( `FetchEnvs cache MISS: '${ user } /${ key } '` ) ;
70+ // When setting a key, keep track of users to allow later deletion
71+ globalResourceCacheUsers . add ( user ) ;
72+ globalResourceCache . set ( `${ user } /${ key } ` , value ) ;
73+ log . info ( `FetchEnvs cached '${ user } /${ key } '` ) ;
74+ } ,
75+ get : ( key ) => {
76+ if ( globalResourceCache . has ( `${ user } /${ key } ` ) ) {
77+ log . info ( `FetchEnvs cache HIT: '${ user } /${ key } '` ) ;
78+ }
79+ return globalResourceCache . get ( `${ user } /${ key } ` ) ;
80+ } ,
81+ } ;
9482 }
9583
9684 // This function needs to be called any time a watch on a potentially cached item is triggered by creation/update/poll, e.g. in the ReferencedResourceManager
@@ -139,6 +127,45 @@ module.exports = class FetchEnvs {
139127 return this . #genericKeyRef( conf , 'configMapKeyRef' ) ;
140128 }
141129
130+ /*
131+ Single-resource queries are cacheable. If it's in the cache, use it.
132+ If not in the cache, start an api call to populate the cache if needed, wait for it to finish, then use it from the cache.
133+ */
134+ async #getSingleResource( resource ) {
135+ const { apiVersion, kind, namespace, name } = resource ;
136+ const cacheKey = [ apiVersion , kind , namespace , name ] . join ( '/' ) ;
137+
138+ // Single-resource queries are cacheable. If it's in the cache, use it.
139+ if ( this . resourceCache . has ( cacheKey ) ) {
140+ resource = this . resourceCache . get ( cacheKey ) ;
141+ }
142+ // Single-resource queries are cacheable. If not in the cache, start an api call to populate the cache if needed, wait for it to finish, then use it from the cache.
143+ else {
144+ if ( ! singleResourceQueryCache [ cacheKey ] ) {
145+ singleResourceQueryCache [ cacheKey ] = ( async ( ) => {
146+ try {
147+ const krm = await this . kubeClass . getKubeResourceMeta ( apiVersion , kind , 'update' ) ;
148+ if ( krm ) {
149+ resource = await krm . get ( name , namespace ) ;
150+ if ( resource ) {
151+ this . resourceCache . set ( cacheKey , resource ) ; // Cache this resource
152+ }
153+ }
154+ }
155+ finally {
156+ delete singleResourceQueryCache [ cacheKey ] ;
157+ }
158+ } ) ( ) ;
159+ }
160+
161+ await singleResourceQueryCache [ cacheKey ] ;
162+
163+ resource = this . resourceCache . get ( cacheKey ) ;
164+ }
165+
166+ return resource ;
167+ }
168+
142169 /*
143170 @param [I] conf An object like `{ configMapRef: { name: 'asdf', namespace: 'asdf' } }`.
144171 @param [I] valueFrom The name of the conf attribute containing resource details, e.g. `configMapRef`.
@@ -147,8 +174,6 @@ module.exports = class FetchEnvs {
147174 @return An object like { configMapRef: { name: 'asdf', namespace: 'asdf' }, data: { key1: val1, ... } }
148175 */
149176 async #genericMapRef( conf , valueFrom = 'genericMapRef' , decode = false ) {
150- let resource ;
151- let kubeError = ERR_NODATA ;
152177 const ref = conf [ valueFrom ] ;
153178 const optional = ! ! conf . optional ;
154179
@@ -159,23 +184,14 @@ module.exports = class FetchEnvs {
159184 name
160185 } = ref ;
161186
162- const cacheKey = [ apiVersion , kind , namespace , name ] . join ( '/' ) ;
163- if ( this . resourceCache . has ( cacheKey ) ) {
164- resource = this . resourceCache . get ( cacheKey ) ;
187+ let kubeError = ERR_NODATA ;
188+ let resource ;
189+ // Get single resource from cache or api call (with cache addition)
190+ try {
191+ resource = await this . #getSingleResource( { apiVersion, kind, namespace, name } ) ;
165192 }
166- else {
167- const krm = await this . kubeClass . getKubeResourceMeta ( apiVersion , kind , 'update' ) ;
168-
169- if ( krm ) {
170- try {
171- resource = await krm . get ( name , namespace ) ;
172- if ( resource ) {
173- this . resourceCache . set ( cacheKey , resource ) ; // Cache this resource
174- }
175- } catch ( error ) {
176- kubeError = error ;
177- }
178- }
193+ catch ( error ) {
194+ kubeError = error ;
179195 }
180196
181197 const data = resource ?. data ;
@@ -209,46 +225,55 @@ module.exports = class FetchEnvs {
209225 @return The discovered value
210226 */
211227 async #genericKeyRef( conf , valueFrom = 'genericKeyRef' , decode = false ) {
212- let response ;
213- let kubeError = ERR_NODATA ;
214228 const optional = ! ! conf . optional ;
215229 const defaultValue = conf . default ;
216230 const ref = conf . valueFrom [ valueFrom ] ;
217231 const strategy = conf . overrideStrategy ;
218232 const {
233+ apiVersion = 'v1' ,
234+ kind = KIND_MAP . get ( valueFrom ) ,
235+ namespace = this . namespace ,
219236 name,
220- key,
221237 matchLabels,
238+ key,
222239 type,
223- namespace = this . namespace ,
224- kind = KIND_MAP . get ( valueFrom ) ,
225- apiVersion = 'v1'
226240 } = ref ;
227241
228242 const matchLabelsQS = labelSelectors ( matchLabels ) ;
229243
230- const cacheKey = [ apiVersion , kind , namespace , name ] . join ( '/' ) ;
231- // Note: Using `matchLabels` will always result in a kube api call, label-based queries cannot use the resourceCache
232- if ( ! matchLabelsQS && this . resourceCache . has ( cacheKey ) ) {
233- response = this . resourceCache . get ( cacheKey ) ;
234- }
235- else {
236- const krm = await this . kubeClass . getKubeResourceMeta ( apiVersion , kind , 'update' ) ;
244+ let kubeError = ERR_NODATA ;
245+ let response ;
246+ if ( typeof matchLabelsQS === OBJECT ) {
247+ // Get multiple resources that match the specified labels
248+ // MatchLabels queries are not cached (though the resulting resources are cached)
249+ try {
250+ const krm = await this . kubeClass . getKubeResourceMeta ( apiVersion , kind , 'update' ) ;
237251
238- if ( krm ) {
239- try {
252+ if ( krm ) {
240253 response = await this . api ( {
241254 uri : krm . uri ( { namespace, name } ) ,
242255 json : true ,
243256 qs : matchLabelsQS
244257 } ) ;
245- // Note: cache here only if getting a single resource
246- if ( response ?. data && ! response ?. items ) {
247- this . resourceCache . set ( cacheKey , response ) ;
258+ // Cache multiple resources
259+ if ( response ?. items ) {
260+ response . items . forEach ( function ( item ) {
261+ const cacheKey = [ item . apiVersion , item . kind , item . metadata . namespace , item . metadata . name ] . join ( '/' ) ;
262+ this . resourceCache . set ( cacheKey , item ) ;
263+ } , this ) ;
248264 }
249- } catch ( error ) {
250- kubeError = error ;
251265 }
266+ } catch ( error ) {
267+ kubeError = error ;
268+ }
269+ }
270+ else {
271+ // Get single resource from cache or api call (with cache addition)
272+ try {
273+ response = await this . #getSingleResource( { apiVersion, kind, namespace, name } ) ;
274+ }
275+ catch ( error ) {
276+ kubeError = error ;
252277 }
253278 }
254279
@@ -257,13 +282,6 @@ module.exports = class FetchEnvs {
257282 // If matching by labels, there can be multiple matching resources.
258283 // Reduce to a single value via the specified strategy ('merge' combines objects, otherwise a single value is picked).
259284 if ( typeof matchLabelsQS === OBJECT ) {
260- // Cache here if there are multiple retrieved resources
261- if ( response ?. items ) {
262- response . items . forEach ( function ( item ) {
263- const cacheKey = [ item . apiVersion , item . kind , item . metadata . namespace , item . metadata . name ] . join ( '/' ) ;
264- this . resourceCache . set ( cacheKey , item ) ;
265- } , this ) ;
266- }
267285 const output = response ?. items . reduce (
268286 reduceItemList ( ref , strategy , decode ) ,
269287 Object . create ( null )
0 commit comments