Skip to content

Commit 7b6addd

Browse files
committed
fixup! Manage CRR endpoints without explicit port
1 parent e9586e5 commit 7b6addd

File tree

4 files changed

+295
-150
lines changed

4 files changed

+295
-150
lines changed

bin/replication.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ function _createSetupReplication(command, options, log) {
4040
const destinationEndpoint =
4141
config.getBootstrapList()
4242
.find(dest => Array.isArray(dest.servers));
43-
const destSiteConfig = destinationEndpoint ? config.getReplicationSiteDestConfig(destinationEndpoint.site) : null;
4443
return new SetupReplication({
4544
source: {
4645
bucket: sourceBucket,
@@ -52,8 +51,9 @@ function _createSetupReplication(command, options, log) {
5251
target: {
5352
bucket: targetBucket,
5453
credentials: targetCredentials,
55-
hosts: targetIsExternal ? undefined : new RoundRobin(destSiteConfig.replicationEndpoint.servers),
56-
transport: destSiteConfig?.transport || destination.transport,
54+
hosts: targetIsExternal ?
55+
undefined : new RoundRobin(destinationEndpoint.servers),
56+
transport: destination.transport,
5757
isExternal: targetIsExternal,
5858
siteName,
5959
},

lib/Config.js

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,19 @@ class Config extends EventEmitter {
9090
throw new Error('dataMoverTopic is required when lifecycle transitions is supported');
9191
}
9292

93-
if (parsedConfig.extensions && parsedConfig.extensions.replication
94-
&& parsedConfig.extensions.replication.destination
95-
&& parsedConfig.extensions.replication.destination.bootstrapList) {
96-
this.bootstrapList = parsedConfig.extensions.replication
97-
.destination.bootstrapList;
98-
} else {
99-
this.bootstrapList = [];
100-
}
93+
const destination = parsedConfig.extensions?.replication?.destination;
94+
this.bootstrapList = destination?.bootstrapList?.map(endpoint => {
95+
if (!Array.isArray(endpoint.servers)) {
96+
return endpoint;
97+
}
98+
99+
const transport = destination.sites?.[endpoint.site]?.transport || destination.transport || 'http';
100+
const defaultPort = transport === 'https' ? 443 : 80;
101+
const servers = endpoint.servers.map(server => server.includes(':') ? server : `${server}:${defaultPort}`);
102+
103+
return { ...endpoint, servers };
104+
}) ?? [];
105+
101106
const shouldRestrictToSite = process.env.BOOTSTRAP_SITE_NAME;
102107
if (shouldRestrictToSite) {
103108
this.bootstrapList = this.bootstrapList.filter(item => item.site === shouldRestrictToSite);
@@ -309,42 +314,13 @@ class Config extends EventEmitter {
309314
* @returns {object} site specific destination configuration
310315
*/
311316
getReplicationSiteDestConfig(site) {
312-
const { destination } = this.extensions.replication;
313-
const siteConfig = destination.sites?.[site] || {};
314-
315-
const transport = siteConfig.transport || destination.transport || 'http';
316-
const auth = siteConfig.auth || destination.auth;
317-
const endpoint = this.bootstrapList.find(e => e.site === site);
318-
317+
const destConfig = this.extensions.replication.destination;
319318
return {
320-
transport,
321-
auth,
322-
replicationEndpoint: this._normalizeEndpointServers(endpoint, transport),
319+
transport: destConfig.sites?.[site]?.transport || destConfig.transport,
320+
auth: destConfig.sites?.[site]?.auth || destConfig.auth,
321+
replicationEndpoint: this.bootstrapList.find(endpoint => endpoint.site === site),
323322
};
324323
}
325-
326-
/**
327-
* Normalize replication endpoint servers by adding the default port
328-
* based on the transport protocol when no explicit port is specified.
329-
* @param {object|undefined} endpoint - replication endpoint object
330-
* @param {string} transport - transport protocol ('http' or 'https')
331-
* @returns {object|undefined} endpoint with normalized servers
332-
*/
333-
_normalizeEndpointServers(endpoint, transport) {
334-
if (!endpoint || !Array.isArray(endpoint.servers)) {
335-
return endpoint;
336-
}
337-
338-
const defaultPort = transport === 'https' ? 443 : 80;
339-
const normalizedServers = endpoint.servers.map(server => {
340-
if (server.includes(':')) {
341-
return server;
342-
}
343-
return `${server}:${defaultPort}`;
344-
});
345-
346-
return { ...endpoint, servers: normalizedServers };
347-
}
348324
}
349325

350326
module.exports = new Config();

tests/unit/conf/Config.js

Lines changed: 71 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -91,134 +91,99 @@ describe('Site name', () => {
9191
});
9292
});
9393

94+
9495
describe('Config', () => {
9596
describe('getReplicationSiteDestConfig', () => {
9697
let ogConfigFileEnv;
98+
9799
before(() => {
98100
ogConfigFileEnv = process.env.BACKBEAT_CONFIG_FILE;
99101
});
100-
afterEach(() => {
101-
sinon.restore();
102-
});
102+
103+
afterEach(() => sinon.restore());
104+
103105
after(() => {
104106
if (ogConfigFileEnv) {
105107
process.env.BACKBEAT_CONFIG_FILE = ogConfigFileEnv;
106108
}
107109
});
108-
it('should return replication site destination config', () => {
109-
process.env.BACKBEAT_CONFIG_FILE = `${__dirname}/configs/replicationMultiDestConfig.json`;
110-
const conf = new Config();
111-
const destConfig = conf.getReplicationSiteDestConfig('aws3');
112-
assert.deepStrictEqual(destConfig, {
113-
transport: 'https',
114-
auth: {
115-
type: 'service',
116-
account: 'service-replication-3',
117-
},
118-
replicationEndpoint: {
119-
site: 'aws3',
120-
type: 'aws_s3',
121-
},
122-
});
123-
});
124110

125-
it('should normalize server entries with default port 443 for https transport', () => {
126-
const conf = new Config();
127-
conf.bootstrapList = [{ site: 'site1', servers: ['s3.example.com']}];
128-
conf.extensions.replication.destination = {
129-
transport: 'https',
130-
sites: {
131-
site1: { transport: 'https' },
132-
},
133-
bootstrapList: [
134-
{ site: 'site1', servers: ['s3.example.com'] },
135-
],
136-
};
111+
describe('bootstrapList server normalization', () => {
112+
let conf;
137113

138-
const destConfig = conf.getReplicationSiteDestConfig('site1');
139-
assert.deepStrictEqual(destConfig.replicationEndpoint.servers, ['s3.example.com:443']);
140-
});
114+
before(() => {
115+
process.env.BACKBEAT_CONFIG_FILE = `${__dirname}/configs/replicationServersConfig.json`;
116+
conf = new Config();
117+
});
141118

142-
it('should normalize server entries with default port 80 for http transport', () => {
143-
const conf = new Config();
144-
conf.bootstrapList = [{ site: 'site1', servers: ['s3.example.com']}];
145-
conf.extensions.replication.destination = {
146-
transport: 'http',
147-
sites: {
148-
site1: { transport: 'http' },
149-
},
150-
bootstrapList: [
151-
{ site: 'site1', servers: ['s3.example.com'] },
152-
],
153-
};
119+
it('should normalize server entries with default port 443 for https transport', () => {
120+
const entry = conf.bootstrapList.find(e => e.site === 'https-site');
121+
assert.deepStrictEqual(entry.servers, ['s3.example.com:443']);
122+
});
154123

155-
const destConfig = conf.getReplicationSiteDestConfig('site1');
156-
assert.deepStrictEqual(destConfig.replicationEndpoint.servers, ['s3.example.com:80']);
157-
});
124+
it('should normalize server entries with default port 80 for http transport', () => {
125+
const entry = conf.bootstrapList.find(e => e.site === 'http-site');
126+
assert.deepStrictEqual(entry.servers, ['s3.example.com:80']);
127+
});
158128

159-
it('should preserve explicit port in server entries', () => {
160-
const conf = new Config();
161-
conf.bootstrapList = [{ site: 'site1', servers: ['s3.example.com:8443']}];
162-
conf.extensions.replication.destination = {
163-
transport: 'https',
164-
sites: {
165-
site1: { transport: 'https' },
166-
},
167-
bootstrapList: [
168-
{ site: 'site1', servers: ['s3.example.com:8443'] },
169-
],
170-
};
129+
it('should preserve explicit port in server entries', () => {
130+
const entry = conf.bootstrapList.find(e => e.site === 'explicit-port-site');
131+
assert.deepStrictEqual(entry.servers, ['s3.example.com:8443']);
132+
});
171133

172-
const destConfig = conf.getReplicationSiteDestConfig('site1');
173-
assert.deepStrictEqual(destConfig.replicationEndpoint.servers, ['s3.example.com:8443']);
134+
it('should not modify endpoint without servers array', () => {
135+
const entry = conf.bootstrapList.find(e => e.site === 'aws-site');
136+
assert.strictEqual(entry.servers, undefined);
137+
assert.strictEqual(entry.type, 'aws_s3');
138+
});
174139
});
175140

176-
it('should not modify endpoint without servers array', () => {
177-
const conf = new Config();
178-
conf.bootstrapList = [{ site: 'aws1', type: 'aws_s3' }];
179-
conf.extensions.replication.destination = {
180-
transport: 'http',
181-
sites: {
182-
aws1: { transport: 'http' },
183-
},
184-
bootstrapList: [
185-
{ site: 'aws1', type: 'aws_s3' },
186-
],
187-
};
188141

189-
const destConfig = conf.getReplicationSiteDestConfig('aws1');
190-
assert.deepStrictEqual(destConfig.replicationEndpoint, {
191-
site: 'aws1',
192-
type: 'aws_s3',
142+
describe('getReplicationSiteDestConfig', () => {
143+
it('should return replication site destination config', () => {
144+
process.env.BACKBEAT_CONFIG_FILE = `${__dirname}/configs/replicationMultiDestConfig.json`;
145+
const conf = new Config();
146+
const destConfig = conf.getReplicationSiteDestConfig('aws3');
147+
assert.deepStrictEqual(destConfig, {
148+
transport: 'https',
149+
auth: {
150+
type: 'service',
151+
account: 'service-replication-3',
152+
},
153+
replicationEndpoint: {
154+
site: 'aws3',
155+
type: 'aws_s3',
156+
},
157+
});
193158
});
194-
});
195159

196-
it('should return default replication destination config when site one is not available', () => {
197-
process.env.BACKBEAT_CONFIG_FILE = `${__dirname}/configs/replicationMultiDestConfig.json`;
198-
const conf = new Config();
199-
sinon.stub(conf.extensions.replication, 'destination').value({
200-
transport: 'https',
201-
auth: {
202-
type: 'service',
203-
account: 'service-replication',
204-
},
205-
bootstrapList: [
206-
{ site: 'aws1', type: 'aws_s3' },
207-
{ site: 'aws2', type: 'aws_s3' },
208-
{ site: 'aws3', type: 'aws_s3' }
209-
]
210-
});
211-
const destConfig = conf.getReplicationSiteDestConfig('aws3');
212-
assert.deepStrictEqual(destConfig, {
213-
transport: 'https',
214-
auth: {
215-
type: 'service',
216-
account: 'service-replication',
217-
},
218-
replicationEndpoint: {
219-
site: 'aws3',
220-
type: 'aws_s3',
221-
},
160+
it('should return default replication destination config when site one is not available', () => {
161+
process.env.BACKBEAT_CONFIG_FILE = `${__dirname}/configs/replicationMultiDestConfig.json`;
162+
const conf = new Config();
163+
sinon.stub(conf.extensions.replication, 'destination').value({
164+
transport: 'https',
165+
auth: {
166+
type: 'service',
167+
account: 'service-replication',
168+
},
169+
bootstrapList: [
170+
{ site: 'aws1', type: 'aws_s3' },
171+
{ site: 'aws2', type: 'aws_s3' },
172+
{ site: 'aws3', type: 'aws_s3' }
173+
]
174+
});
175+
const destConfig = conf.getReplicationSiteDestConfig('aws3');
176+
assert.deepStrictEqual(destConfig, {
177+
transport: 'https',
178+
auth: {
179+
type: 'service',
180+
account: 'service-replication',
181+
},
182+
replicationEndpoint: {
183+
site: 'aws3',
184+
type: 'aws_s3',
185+
},
186+
});
222187
});
223188
});
224189
});

0 commit comments

Comments
 (0)