Skip to content

Commit 66ef52d

Browse files
committed
S3UTILS-195 verifyBucketSproxydKeys.js crashes on empty object without location
1 parent e79a2f6 commit 66ef52d

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { Logger } = require('werelogs');
2+
3+
const log = new Logger('s3utils:validateLocations');
4+
5+
/**
6+
* Validates that locations array is valid for objects with non-zero content-length
7+
* @param {string} objectUrl - The S3 object URL for logging
8+
* @param {Array} locations - The locations array from metadata
9+
* @param {Object} status - Status object to update counters
10+
* @param {Object} findDuplicateSproxydKeys - Object with skipVersion method
11+
* @returns {boolean} - true if valid, false if invalid
12+
*/
13+
function validateLocations(objectUrl, locations, status, findDuplicateSproxydKeys) {
14+
// Handle broken metadata where locations is invalid or empty for non-zero content-length objects
15+
if (!Array.isArray(locations) || locations.length === 0) {
16+
log.error('object with non-zero content-length has invalid or empty location data', {
17+
objectUrl,
18+
locations,
19+
locationType: typeof locations,
20+
});
21+
// eslint-disable-next-line no-param-reassign
22+
status.objectsScanned += 1;
23+
// eslint-disable-next-line no-param-reassign
24+
status.objectsWithBrokenMetadata += 1;
25+
findDuplicateSproxydKeys.skipVersion();
26+
return false;
27+
}
28+
return true;
29+
}
30+
31+
module.exports = validateLocations;

tests/unit/verifyBucketSproxydKeys.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const getObjectURL = require('../../VerifyBucketSproxydKeys/getObjectURL');
22
const getBucketdURL = require('../../VerifyBucketSproxydKeys/getBucketdURL');
33
const FindDuplicateSproxydKeys = require('../../VerifyBucketSproxydKeys/FindDuplicateSproxydKeys');
4+
const validateLocations = require('../../VerifyBucketSproxydKeys/validateLocations');
45

56
describe('verifyBucketSproxydKeys', () => {
67
test('getObjectURL', () => {
@@ -77,4 +78,93 @@ describe('verifyBucketSproxydKeys', () => {
7778
expect(finder.sproxydKeys).toEqual({ k7: 'obj9' });
7879
expect(finder.versionsWindow).toEqual({ 11: ['k7'], 12: ['k7'] });
7980
});
81+
82+
describe('validateLocations', () => {
83+
const key1 = {
84+
key: '8df148c188a7369ef6b632b08e9a2a867c065761',
85+
size: 2097,
86+
start: 0,
87+
dataStoreName: 'us-east-1',
88+
dataStoreType: 'scality',
89+
dataStoreETag: '1:35bf6d36c0c721deceda5d15fa642a18',
90+
};
91+
const key2 = {
92+
key: '8df148c188a7369ef6b632b08e9a2a867c065762',
93+
size: 2098,
94+
start: 0,
95+
dataStoreName: 'us-east-1',
96+
dataStoreType: 'scality',
97+
dataStoreETag: '1:35bf6d36c0c721deceda5d15fa642a19',
98+
};
99+
let status;
100+
let findDuplicateSproxydKeys;
101+
102+
beforeEach(() => {
103+
status = { objectsScanned: 0, objectsWithBrokenMetadata: 0 };
104+
findDuplicateSproxydKeys = { skipVersion: jest.fn() };
105+
});
106+
107+
test('should return true for valid locations array', () => {
108+
const result = validateLocations('s3://bucket/key', [key1], status, findDuplicateSproxydKeys);
109+
110+
expect(result).toEqual(true);
111+
expect(status.objectsScanned).toEqual(0);
112+
expect(status.objectsWithBrokenMetadata).toEqual(0);
113+
expect(findDuplicateSproxydKeys.skipVersion).not.toHaveBeenCalled();
114+
});
115+
116+
test('should return true for multiple valid locations', () => {
117+
const result = validateLocations('s3://bucket/valid-key', [key1, key2], status, findDuplicateSproxydKeys);
118+
119+
expect(result).toEqual(true);
120+
expect(status.objectsScanned).toEqual(0);
121+
expect(status.objectsWithBrokenMetadata).toEqual(0);
122+
expect(findDuplicateSproxydKeys.skipVersion).not.toHaveBeenCalled();
123+
});
124+
125+
test('should return false for undefined locations', () => {
126+
const result = validateLocations('s3://bucket/broken-key', undefined, status, findDuplicateSproxydKeys);
127+
128+
expect(result).toEqual(false);
129+
expect(status.objectsScanned).toEqual(1);
130+
expect(status.objectsWithBrokenMetadata).toEqual(1);
131+
expect(findDuplicateSproxydKeys.skipVersion).toHaveBeenCalledTimes(1);
132+
});
133+
134+
test('should return false for null locations', () => {
135+
const result = validateLocations('s3://bucket/null-key', null, status, findDuplicateSproxydKeys);
136+
137+
expect(result).toEqual(false);
138+
expect(status.objectsScanned).toEqual(1);
139+
expect(status.objectsWithBrokenMetadata).toEqual(1);
140+
expect(findDuplicateSproxydKeys.skipVersion).toHaveBeenCalledTimes(1);
141+
});
142+
143+
test('should return false for empty array locations', () => {
144+
const result = validateLocations('s3://bucket/empty-key', [], status, findDuplicateSproxydKeys);
145+
146+
expect(result).toEqual(false);
147+
expect(status.objectsScanned).toEqual(1);
148+
expect(status.objectsWithBrokenMetadata).toEqual(1);
149+
expect(findDuplicateSproxydKeys.skipVersion).toHaveBeenCalledTimes(1);
150+
});
151+
152+
test('should return false for non-array locations', () => {
153+
const result = validateLocations('s3://bucket/string-key', 'not-an-array', status, findDuplicateSproxydKeys);
154+
155+
expect(result).toEqual(false);
156+
expect(status.objectsScanned).toEqual(1);
157+
expect(status.objectsWithBrokenMetadata).toEqual(1);
158+
expect(findDuplicateSproxydKeys.skipVersion).toHaveBeenCalledTimes(1);
159+
});
160+
161+
test('should return false for object instead of array', () => {
162+
const result = validateLocations('s3://bucket/object-key', { key: 'sproxyd-key' }, status, findDuplicateSproxydKeys);
163+
164+
expect(result).toEqual(false);
165+
expect(status.objectsScanned).toEqual(1);
166+
expect(status.objectsWithBrokenMetadata).toEqual(1);
167+
expect(findDuplicateSproxydKeys.skipVersion).toHaveBeenCalledTimes(1);
168+
});
169+
});
80170
});

verifyBucketSproxydKeys.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const { Logger } = require('werelogs');
1414
const getObjectURL = require('./VerifyBucketSproxydKeys/getObjectURL');
1515
const getBucketdURL = require('./VerifyBucketSproxydKeys/getBucketdURL');
1616
const FindDuplicateSproxydKeys = require('./VerifyBucketSproxydKeys/FindDuplicateSproxydKeys');
17+
const validateLocations = require('./VerifyBucketSproxydKeys/validateLocations');
1718
const BlockDigestsStream = require('./CompareRaftMembers/BlockDigestsStream');
1819
const BlockDigestsStorage = require('./CompareRaftMembers/BlockDigestsStorage');
1920

@@ -141,6 +142,7 @@ const status = {
141142
objectsWithDupKeys: 0,
142143
objectsWithEmptyMetadata: 0,
143144
objectsWithDupVersionIds: 0,
145+
objectsWithBrokenMetadata: 0,
144146
objectsErrors: 0,
145147
bucketInProgress: null,
146148
KeyMarker: '',
@@ -196,6 +198,7 @@ function logProgress(message) {
196198
haveDupKeys: status.objectsWithDupKeys,
197199
haveEmptyMetadata: status.objectsWithEmptyMetadata,
198200
haveDupVersionIds: status.objectsWithDupVersionIds,
201+
haveBrokenMetadata: status.objectsWithBrokenMetadata,
199202
errors: status.objectErrors,
200203
url: getObjectURL(status.bucketInProgress, status.KeyMarker),
201204
});
@@ -299,6 +302,10 @@ function fetchObjectLocations(bucket, objectKey, cb) {
299302
}
300303

301304
function checkSproxydKeys(objectUrl, locations, cb) {
305+
if (!validateLocations(objectUrl, locations, status, findDuplicateSproxydKeys)) {
306+
return process.nextTick(cb);
307+
}
308+
302309
let keyError = false;
303310
let keyMissing = false;
304311
let dupKey = false;
@@ -649,6 +656,9 @@ function main() {
649656
if (status.objectsWithEmptyMetadata) {
650657
process.exit(104);
651658
}
659+
if (status.objectsWithBrokenMetadata) {
660+
process.exit(105);
661+
}
652662
if (status.objectsErrors) {
653663
process.exit(103);
654664
}

0 commit comments

Comments
 (0)