Skip to content

Commit 15f163f

Browse files
Serve capabilities from config if present
The capabilities returned by getCapabilities API will not be directly set in config by ZKOP, so that they are simply _served_ by Cloudserver. Old fields are keps for backwards compatibility, but the goal is really that ZKOP is free to put _any_ content there, as needed by pensieve-api: this will in particular help to minimize the changes required when adding a new kind of location. Issue: CLDSRV-599
1 parent bbf01f2 commit 15f163f

File tree

3 files changed

+328
-5
lines changed

3 files changed

+328
-5
lines changed

lib/Config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,6 +1810,14 @@ class Config extends EventEmitter {
18101810
if (config.enableVeeamRoute !== undefined && config.enableVeeamRoute !== null) {
18111811
this.enableVeeamRoute = config.enableVeeamRoute;
18121812
}
1813+
1814+
if (config.capabilities) {
1815+
if (config.capabilities.locationTypes) {
1816+
config.capabilities.locationTypes = new Set(config.capabilities.locationTypes);
1817+
}
1818+
this.capabilities = config.capabilities;
1819+
}
1820+
18131821
return config;
18141822
}
18151823

lib/utilities/reportHandler.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,50 @@ function hasWSOptionalDependencies() {
3434
}
3535
}
3636

37-
function getCapabilities() {
38-
const localVolumeCap = process.env.LOCAL_VOLUME_CAPABILITY || 'true';
39-
return {
37+
function getCapabilities(cfg = config) {
38+
const caps = cfg.capabilities || {
39+
// Default capabilities, for backward compatibility. Should not be modified,
40+
// changes are expected to be done through config.json file.
41+
locationTypeAzure: true,
42+
locationTypeGCP: true,
4043
locationTypeDigitalOcean: true,
4144
locationTypeS3Custom: true,
4245
locationTypeSproxyd: true,
4346
locationTypeNFS: true,
4447
locationTypeCephRadosGW: true,
4548
locationTypeHyperdriveV2: true,
46-
locationTypeLocal: localVolumeCap === '1' || localVolumeCap.toLowerCase() === 'true',
49+
locationTypeLocal: true,
4750
preferredReadLocation: true,
4851
managedLifecycle: true,
4952
managedLifecycleTransition: true,
50-
secureChannelOptimizedPath: hasWSOptionalDependencies(),
53+
secureChannelOptimizedPath: true,
5154
s3cIngestLocation: true,
5255
nfsIngestLocation: false,
5356
cephIngestLocation: false,
5457
awsIngestLocation: false,
5558
};
59+
60+
// Consistency & safety checks for capabilities that depend on other config values
61+
const localVolumeCap = process.env.LOCAL_VOLUME_CAPABILITY || 'true';
62+
caps.locationTypeLocal &&= (localVolumeCap === '1' || localVolumeCap.toLowerCase() === 'true');
63+
caps.secureChannelOptimizedPath &&= hasWSOptionalDependencies();
64+
caps.managedLifecycle &&= cfg.supportedLifecycleRules.includes('Expiration');
65+
caps.managedLifecycleTransition &&= cfg.supportedLifecycleRules.includes('Transition');
66+
caps.lifecycleRules &&= cfg.supportedLifecycleRules;
67+
68+
// Map locationTypes entries to the respective "legacy" capability flags
69+
if (caps.locationTypes) {
70+
caps.locationTypeAzure &&= caps.locationTypes.includes('location-azure-v1');
71+
caps.locationTypeGCP &&= caps.locationTypes.includes('location-gcp-v1');
72+
caps.locationTypeDigitalOcean &&= caps.locationTypes.includes('location-do-spaces-v1');
73+
caps.locationTypeSproxyd &&= caps.locationTypes.includes('location-scality-sproxyd-v1');
74+
caps.locationTypeNFS &&= caps.locationTypes.includes('location-nfs-mount-v1');
75+
caps.locationTypeCephRadosGW &&= caps.locationTypes.includes('location-ceph-radosgw-s3-v1');
76+
caps.locationTypeHyperdriveV2 &&= caps.locationTypes.includes('location-scality-hdclient-v2');
77+
caps.locationTypeLocal &&= caps.locationTypes.includes('location-file-v1');
78+
}
79+
80+
return caps;
5681
}
5782

5883
function cleanup(obj) {

tests/unit/utils/reportHandler.js

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
const assert = require('assert');
2+
const sinon = require('sinon');
3+
const { getCapabilities } = require('../../../lib/utilities/reportHandler');
4+
5+
describe('reportHandler.getCapabilities', () => {
6+
const sandbox = sinon.createSandbox();
7+
const originalEnv = process.env.LOCAL_VOLUME_CAPABILITY;
8+
9+
afterEach(() => {
10+
sandbox.restore();
11+
if (originalEnv === undefined) {
12+
delete process.env.LOCAL_VOLUME_CAPABILITY;
13+
} else {
14+
process.env.LOCAL_VOLUME_CAPABILITY = originalEnv;
15+
}
16+
});
17+
18+
describe('getCapabilities', () => {
19+
it('should return default capabilities when config.capabilities is not set', () => {
20+
const cfg = {
21+
supportedLifecycleRules: ['Expiration', 'Transition', 'NoncurrentVersionExpiration'],
22+
};
23+
const caps = getCapabilities(cfg);
24+
25+
assert.deepStrictEqual(caps, {
26+
locationTypeAzure: true,
27+
locationTypeGCP: true,
28+
locationTypeDigitalOcean: true,
29+
locationTypeS3Custom: true,
30+
locationTypeSproxyd: true,
31+
locationTypeNFS: true,
32+
locationTypeCephRadosGW: true,
33+
locationTypeHyperdriveV2: true,
34+
locationTypeLocal: true,
35+
preferredReadLocation: true,
36+
managedLifecycle: true,
37+
managedLifecycleTransition: true,
38+
secureChannelOptimizedPath: true,
39+
s3cIngestLocation: true,
40+
nfsIngestLocation: false,
41+
cephIngestLocation: false,
42+
awsIngestLocation: false,
43+
});
44+
});
45+
46+
it('should use capabilities from config when specified', () => {
47+
const cfg = {
48+
capabilities: {
49+
locationTypeAzure: false,
50+
locationTypeGCP: false,
51+
locationTypeDigitalOcean: true,
52+
locationTypeS3Custom: false,
53+
customCapability: 'test-value',
54+
},
55+
supportedLifecycleRules: ['Expiration'],
56+
};
57+
const caps = getCapabilities(cfg);
58+
59+
assert.deepStrictEqual(caps, {
60+
locationTypeAzure: false,
61+
locationTypeGCP: false,
62+
locationTypeDigitalOcean: true,
63+
locationTypeS3Custom: false,
64+
customCapability: 'test-value',
65+
});
66+
});
67+
68+
it('should apply LOCAL_VOLUME_CAPABILITY env when set to false', () => {
69+
process.env.LOCAL_VOLUME_CAPABILITY = 'false';
70+
const cfg = {
71+
capabilities: {
72+
locationTypeLocal: true,
73+
},
74+
supportedLifecycleRules: ['Expiration'],
75+
};
76+
const caps = getCapabilities(cfg);
77+
78+
// locationTypeLocal should be forced to false due to env variable
79+
assert.strictEqual(caps.locationTypeLocal, false);
80+
});
81+
82+
it('should apply LOCAL_VOLUME_CAPABILITY env when set to "0"', () => {
83+
process.env.LOCAL_VOLUME_CAPABILITY = '0';
84+
const cfg = {
85+
capabilities: {
86+
locationTypeLocal: true,
87+
},
88+
supportedLifecycleRules: ['Expiration'],
89+
};
90+
const caps = getCapabilities(cfg);
91+
92+
// locationTypeLocal should be forced to false due to env variable
93+
assert.strictEqual(caps.locationTypeLocal, false);
94+
});
95+
96+
it('should apply LOCAL_VOLUME_CAPABILITY env when set to true', () => {
97+
process.env.LOCAL_VOLUME_CAPABILITY = '1';
98+
const cfg = {
99+
capabilities: {
100+
locationTypeLocal: true,
101+
},
102+
supportedLifecycleRules: ['Expiration'],
103+
};
104+
const caps = getCapabilities(cfg);
105+
106+
// locationTypeLocal should remain true
107+
assert.strictEqual(caps.locationTypeLocal, true);
108+
});
109+
110+
it('should not apply LOCAL_VOLUME_CAPABILITY env if locationTypeLocal disabled', () => {
111+
process.env.LOCAL_VOLUME_CAPABILITY = true;
112+
const cfg = {
113+
capabilities: {
114+
locationTypeLocal: false,
115+
},
116+
supportedLifecycleRules: ['Expiration'],
117+
};
118+
const caps = getCapabilities(cfg);
119+
120+
// locationTypeLocal should remain true
121+
assert.strictEqual(caps.locationTypeLocal, false);
122+
});
123+
124+
it('should disable managedLifecycle when Expiration is not in supportedLifecycleRules', () => {
125+
const cfg = {
126+
capabilities: {
127+
managedLifecycle: true,
128+
},
129+
supportedLifecycleRules: ['Transition', 'NoncurrentVersionExpiration'],
130+
};
131+
const caps = getCapabilities(cfg);
132+
133+
// managedLifecycle should be false
134+
assert.strictEqual(caps.managedLifecycle, false);
135+
});
136+
137+
it('should keep managedLifecycle when Expiration is in supportedLifecycleRules', () => {
138+
const cfg = {
139+
capabilities: {
140+
managedLifecycle: true,
141+
},
142+
supportedLifecycleRules: ['Expiration', 'Transition'],
143+
};
144+
const caps = getCapabilities(cfg);
145+
146+
// managedLifecycle should remain true
147+
assert.strictEqual(caps.managedLifecycle, true);
148+
});
149+
150+
it('should not enable managedLifecycle if managedLifecycle is disabled', () => {
151+
const cfg = {
152+
capabilities: {
153+
managedLifecycle: false,
154+
},
155+
supportedLifecycleRules: ['Expiration', 'Transition'],
156+
};
157+
const caps = getCapabilities(cfg);
158+
159+
// managedLifecycle should remain false
160+
assert.strictEqual(caps.managedLifecycle, false);
161+
});
162+
163+
it('should disable managedLifecycleTransition when Transition is not in supportedLifecycleRules', () => {
164+
const cfg = {
165+
capabilities: {
166+
managedLifecycleTransition: true,
167+
},
168+
supportedLifecycleRules: ['Expiration', 'NoncurrentVersionExpiration'],
169+
};
170+
const caps = getCapabilities(cfg);
171+
172+
// managedLifecycleTransition should be false
173+
assert.strictEqual(caps.managedLifecycleTransition, false);
174+
});
175+
176+
it('should keep managedLifecycleTransition when Transition is in supportedLifecycleRules', () => {
177+
const cfg = {
178+
capabilities: {
179+
managedLifecycleTransition: true,
180+
},
181+
supportedLifecycleRules: ['Expiration', 'Transition'],
182+
};
183+
const caps = getCapabilities(cfg);
184+
185+
// managedLifecycleTransition should remain true
186+
assert.strictEqual(caps.managedLifecycleTransition, true);
187+
});
188+
189+
it('should not enable managedLifecycleTransition if managedLifecycleTransition is disabled', () => {
190+
const cfg = {
191+
capabilities: {
192+
managedLifecycleTransition: true,
193+
},
194+
supportedLifecycleRules: ['Expiration', 'Transition'],
195+
};
196+
const caps = getCapabilities(cfg);
197+
198+
// managedLifecycleTransition should remain true
199+
assert.strictEqual(caps.managedLifecycleTransition, true);
200+
});
201+
202+
it('should override lifecycleRules from supportedLifecycleRules', () => {
203+
const supportedRules = ['Expiration', 'Transition', 'NoncurrentVersionExpiration'];
204+
const cfg = {
205+
capabilities: {
206+
lifecycleRules: ['Expiration'],
207+
},
208+
supportedLifecycleRules: supportedRules,
209+
};
210+
const caps = getCapabilities(cfg);
211+
212+
// lifecycleRules should be set to supportedLifecycleRules
213+
assert.deepStrictEqual(caps.lifecycleRules, supportedRules);
214+
});
215+
216+
it('should update locationTypes capabilities based on locationTypes map', () => {
217+
const cfg = {
218+
capabilities: {
219+
locationTypeAzure: true,
220+
locationTypeGCP: true,
221+
locationTypeDigitalOcean: true,
222+
locationTypeS3Custom: true,
223+
locationTypeSproxyd: true,
224+
locationTypeNFS: true,
225+
locationTypeCephRadosGW: true,
226+
locationTypeHyperdriveV2: true,
227+
locationTypeLocal: true,
228+
locationTypes: [
229+
'location-gcp-v1',
230+
'location-scality-sproxyd-v1',
231+
'location-ceph-radosgw-s3-v1',
232+
'location-file-v1',
233+
'location-scality-artesca-s3-v1',
234+
],
235+
},
236+
supportedLifecycleRules: ['Expiration'],
237+
};
238+
const caps = getCapabilities(cfg);
239+
240+
// Verify locationTypes override the legacy flags
241+
assert.strictEqual(caps.locationTypeAzure, false);
242+
assert.strictEqual(caps.locationTypeGCP, true);
243+
assert.strictEqual(caps.locationTypeDigitalOcean, false);
244+
assert.strictEqual(caps.locationTypeSproxyd, true);
245+
assert.strictEqual(caps.locationTypeNFS, false);
246+
assert.strictEqual(caps.locationTypeCephRadosGW, true);
247+
assert.strictEqual(caps.locationTypeHyperdriveV2, false);
248+
assert.strictEqual(caps.locationTypeLocal, true);
249+
});
250+
251+
it('should handle multiple consistency checks together', () => {
252+
process.env.LOCAL_VOLUME_CAPABILITY = '0';
253+
const cfg = {
254+
capabilities: {
255+
locationTypeLocal: true,
256+
secureChannelOptimizedPath: true,
257+
managedLifecycle: true,
258+
managedLifecycleTransition: true,
259+
locationTypeAzure: true,
260+
locationTypeGCP: true,
261+
locationTypes: ['location-azure-v1'],
262+
},
263+
supportedLifecycleRules: ['Expiration'], // Missing Transition
264+
};
265+
const caps = getCapabilities(cfg);
266+
267+
// All consistency checks should be applied
268+
assert.strictEqual(caps.locationTypeLocal, false); // env override
269+
assert.strictEqual(caps.managedLifecycle, true); // Expiration present
270+
assert.strictEqual(caps.managedLifecycleTransition, false); // Transition missing
271+
assert.strictEqual(caps.locationTypeAzure, true); // locationTypes override
272+
assert.strictEqual(caps.locationTypeGCP, false); // locationTypes override
273+
});
274+
275+
it('should not modify capabilities when locationTypes is not defined', () => {
276+
const cfg = {
277+
capabilities: {
278+
locationTypeAzure: true,
279+
locationTypeGCP: false,
280+
},
281+
supportedLifecycleRules: ['Expiration'],
282+
};
283+
const caps = getCapabilities(cfg);
284+
285+
// Original values should be preserved
286+
assert.strictEqual(caps.locationTypeAzure, true);
287+
assert.strictEqual(caps.locationTypeGCP, false);
288+
});
289+
});
290+
});

0 commit comments

Comments
 (0)