Skip to content

Commit 62c1ce8

Browse files
committed
Merge branch 'feature/CLDSRV-812/list-objects-v2-optional-permissions' into feature/CLDSRV-813/optional-attributes-response
2 parents 3ceb257 + 9872996 commit 62c1ce8

30 files changed

+1239
-226
lines changed

Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ ENV no_proxy=localhost,127.0.0.1
3838
EXPOSE 8000
3939
EXPOSE 8002
4040

41-
RUN apt-get update && \
42-
apt-get install -y --no-install-recommends \
41+
RUN apt-get update \
42+
&& apt-get install -y --no-install-recommends \
4343
jq \
4444
tini \
45+
python3-redis \
4546
&& rm -rf /var/lib/apt/lists/*
4647

4748
WORKDIR /usr/src/app

lib/Config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,6 +1877,14 @@ class Config extends EventEmitter {
18771877
this.rateLimiting = parseRateLimitConfig(config.rateLimiting, this.clusters = this.clusters || 1);
18781878
}
18791879

1880+
1881+
if (config.capabilities) {
1882+
if (config.capabilities.locationTypes) {
1883+
config.capabilities.locationTypes = new Set(config.capabilities.locationTypes);
1884+
}
1885+
this.capabilities = config.capabilities;
1886+
}
1887+
18801888
return config;
18811889
}
18821890

lib/api/api.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,15 @@ const api = {
363363
});
364364
}; // End of processRequest helper function
365365

366-
// Cache-only rate limit check (fast path, no metadata fetch)
367-
// If config is cached, apply rate limiting now
368-
// If not cached, metadata validation functions will handle it
369-
if (request.bucketName && config.rateLimiting?.enabled && !rateLimitApiActions.includes(apiMethod)) {
366+
const applyRateLimit = request.bucketName
367+
&& config.rateLimiting?.enabled
368+
&& !rateLimitApiActions.includes(apiMethod) // Don't limit any rate limit admin actions
369+
&& !request.isInternalServiceRequest; // Don't limit any calls from internal services
370+
371+
if (applyRateLimit) {
372+
// Cache-only rate limit check (fast path, no metadata fetch)
373+
// If config is cached, apply rate limiting now
374+
// If not cached, metadata validation functions will handle it
370375
const cachedConfig = getRateLimitFromCache(request.bucketName);
371376

372377
if (cachedConfig !== undefined) {

lib/api/apiUtils/object/versioning.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ function _storeNullVersionMD(bucketName, objKey, nullVersionId, objMD, log, cb)
9898
isNull2: true,
9999
});
100100
}
101+
nullVersionMD.originOp = 's3:StoreNullVersion';
101102
metadata.putObjectMD(bucketName, objKey, nullVersionMD, { versionId }, log, err => {
102103
if (err) {
103104
log.debug('error from metadata storing null version as new version',

lib/api/apiUtils/rateLimit/cache.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,22 @@ function expireCachedConfigs(now) {
3939
return toRemove.length;
4040
}
4141

42+
/**
43+
* Invalidate cached config for a specific bucket
44+
*
45+
* @param {string} bucketName - Bucket name
46+
* @returns {boolean} True if entry was found and removed
47+
*/
48+
function invalidateCachedConfig(bucketName) {
49+
const cacheKey = `bucket:${bucketName}`;
50+
return configCache.delete(cacheKey);
51+
}
52+
4253
module.exports = {
4354
setCachedConfig,
4455
getCachedConfig,
4556
expireCachedConfigs,
57+
invalidateCachedConfig,
4658

4759
// Do not access directly
4860
// Used only for tests

lib/api/apiUtils/rateLimit/tokenBucket.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,16 @@ function getTokenBucket(bucketName, limitConfig, log) {
247247
bufferSize: bucket.bufferSize,
248248
refillThreshold: bucket.refillThreshold,
249249
});
250+
} else if (bucket.limitConfig.limit !== limitConfig.limit) {
251+
// Update limit config when it changes dynamically
252+
const oldLimit = bucket.limitConfig.limit;
253+
bucket.limitConfig = limitConfig;
254+
255+
log.info('Updated token bucket limit config', {
256+
bucketName,
257+
oldLimit,
258+
newLimit: limitConfig.limit,
259+
});
250260
}
251261

252262
return bucket;
@@ -286,9 +296,20 @@ function cleanupTokenBuckets(maxIdleMs = 60000) {
286296
return toRemove.length;
287297
}
288298

299+
/**
300+
* Remove a specific token bucket (used when rate limit config is deleted)
301+
*
302+
* @param {string} bucketName - Bucket name
303+
* @returns {boolean} True if bucket was found and removed
304+
*/
305+
function removeTokenBucket(bucketName) {
306+
return tokenBuckets.delete(bucketName);
307+
}
308+
289309
module.exports = {
290310
WorkerTokenBucket,
291311
getTokenBucket,
292312
getAllTokenBuckets,
293313
cleanupTokenBuckets,
314+
removeTokenBucket,
294315
};

lib/api/bucketDeleteRateLimit.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const metadata = require('../metadata/wrapper');
44
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
55
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
66
const { isRateLimitServiceUser } = require('./apiUtils/authorization/serviceUser');
7+
const { invalidateCachedConfig } = require('./apiUtils/rateLimit/cache');
8+
const { removeTokenBucket } = require('./apiUtils/rateLimit/tokenBucket');
79

810
/**
911
* bucketDeleteRateLimit - Delete the bucket rate limit configuration
@@ -49,6 +51,10 @@ function bucketDeleteRateLimit(authInfo, request, log, callback) {
4951
if (err) {
5052
return callback(err, corsHeaders);
5153
}
54+
// Invalidate cache and remove token bucket
55+
invalidateCachedConfig(bucketName);
56+
removeTokenBucket(bucketName);
57+
log.debug('invalidated rate limit cache and token bucket for bucket', { bucketName });
5258
// TODO: implement Utapi metric support
5359
return callback(null, corsHeaders);
5460
});

lib/api/bucketPutRateLimit.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { config } = require('../Config');
66
const metadata = require('../metadata/wrapper');
77
const { standardMetadataValidateBucket } = require('../metadata/metadataUtils');
88
const { isRateLimitServiceUser } = require('./apiUtils/authorization/serviceUser');
9+
const { invalidateCachedConfig } = require('./apiUtils/rateLimit/cache');
910

1011
function parseRequestBody(requestBody, callback) {
1112
try {
@@ -92,6 +93,9 @@ function bucketPutRateLimit(authInfo, request, log, callback) {
9293
{ error: err, method: 'bucketPutRateLimit' });
9394
return callback(err, corsHeaders);
9495
}
96+
// Invalidate cache so new limit takes effect immediately
97+
invalidateCachedConfig(bucketName);
98+
log.debug('invalidated rate limit cache for bucket', { bucketName });
9599
// TODO: implement Utapi metric support
96100
return callback(null, corsHeaders);
97101
});

lib/api/objectGet.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ function objectGet(authInfo, request, returnTagCount, log, callback) {
139139

140140
const objLength = (objMD.location === null ?
141141
0 : parseInt(objMD['content-length'], 10));
142+
// Store full object size for server access logs
143+
if (request.serverAccessLog) {
144+
// eslint-disable-next-line no-param-reassign
145+
request.serverAccessLog.objectSize = objLength;
146+
}
142147
let byteRange;
143148
const streamingParams = {};
144149
if (request.headers.range) {

lib/metadata/metadataUtils.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ function checkRateLimitIfNeeded(bucket, bucketName, request, log, callback) {
226226
// Skip if already checked or not enabled
227227
if (request.rateLimitAlreadyChecked
228228
|| !config.rateLimiting?.enabled
229-
|| rateLimitApiActions.includes(request.apiMethod)) {
229+
|| rateLimitApiActions.includes(request.apiMethod)
230+
|| request.isInternalServiceRequest) {
230231
return process.nextTick(callback, null);
231232
}
232233

0 commit comments

Comments
 (0)