Skip to content

Commit e82ca46

Browse files
committed
MB-49271 [BP] Limit DCP connection names to 200 chars max
This is a backport of: MB-35881: Limit DCP connection names to 200 chars max Reviewed-on: http://review.couchbase.org/c/ns_server/+/159432 http://review.couchbase.org/c/ns_server/+/158400 Change-Id: I8b7f45bfd0c12825df8c7f4d7b7537f7d3ba0cb9 Reviewed-on: http://review.couchbase.org/c/ns_server/+/164897 Well-Formed: Restriction Checker Reviewed-by: Abhijeeth Nuthan <[email protected]> Tested-by: Steve Watanabe <[email protected]>
1 parent 14cd8b8 commit e82ca46

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

include/ns_common.hrl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
-define(MAX_BUCKETS_SUPPORTED, 30).
5555
-define(VBMAP_HISTORY_SIZE, ?MAX_BUCKETS_SUPPORTED).
5656

57+
-define(MAX_DCP_CONNECTION_NAME, 200).
58+
5759
-define(DEFAULT_LOG_FILENAME, "info.log").
5860
-define(ERRORS_LOG_FILENAME, "error.log").
5961
-define(VIEWS_LOG_FILENAME, "views.log").

src/dcp_replicator.erl

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
-include("ns_common.hrl").
2323

24+
-ifdef(TEST).
25+
-include_lib("eunit/include/eunit.hrl").
26+
-endif.
27+
2428
%% gen_server callbacks
2529
-export([init/1, handle_call/3, handle_cast/2,
2630
handle_info/2, terminate/2, code_change/3]).
@@ -41,6 +45,7 @@
4145

4246
-define(VBUCKET_POLL_INTERVAL, 100).
4347
-define(SHUT_CONSUMER_TIMEOUT, ?get_timeout(dcp_shut_consumer, 60000)).
48+
-define(MIN_NODE_NAME, 35).
4449

4550
init({ConsumerNode, ProducerNode, Bucket, ConnName, RepFeatures}) ->
4651
process_flag(trap_exit, true),
@@ -52,10 +57,12 @@ init({ConsumerNode, ProducerNode, Bucket, ConnName, RepFeatures}) ->
5257

5358
Proxies = dcp_proxy:connect_proxies(ConsumerConn, ProducerConn),
5459

55-
?log_debug("initiated new dcp replication with consumer side: ~p and producer side: ~p", [ConsumerConn, ProducerConn]),
60+
?log_debug("initiated new dcp replication with consumer side: ~p and "
61+
"producer side: ~p", [ConsumerConn, ProducerConn]),
5662

5763
master_activity_events:note_dcp_replicator_start(Bucket, ConnName,
58-
ProducerNode, ConsumerConn, ProducerConn),
64+
ProducerNode, ConsumerConn,
65+
ProducerConn),
5966

6067
{ok, #state{
6168
proxies = Proxies,
@@ -81,7 +88,7 @@ start_link(Name, ConsumerNode, ProducerNode, Bucket, ConnName, RepFeatures) ->
8188

8289
start_link(ProducerNode, Bucket, RepFeatures) ->
8390
ConsumerNode = node(),
84-
ConnName = get_connection_name(ConsumerNode, ProducerNode, Bucket),
91+
ConnName = get_connection_name(ConsumerNode, ProducerNode, Bucket),
8592
start_link(server_name(ProducerNode, Bucket),
8693
ConsumerNode, ProducerNode, Bucket, ConnName, RepFeatures).
8794

@@ -219,7 +226,43 @@ get_docs_estimate(Bucket, Partition, ConsumerNode) ->
219226
ns_memcached:get_dcp_docs_estimate(Bucket, Partition, Connection).
220227

221228
get_connection_name(ConsumerNode, ProducerNode, Bucket) ->
222-
"replication:" ++ atom_to_list(ProducerNode) ++ "->" ++ atom_to_list(ConsumerNode) ++ ":" ++ Bucket.
229+
ConsumerNodeList = atom_to_list(ConsumerNode),
230+
ProducerNodeList = atom_to_list(ProducerNode),
231+
CName = "replication:" ++ ProducerNodeList ++ "->" ++ ConsumerNodeList ++
232+
":" ++ Bucket,
233+
234+
case length(CName) =< ?MAX_DCP_CONNECTION_NAME of
235+
true ->
236+
CName;
237+
false ->
238+
%% Find the longest common prefix for the two nodes and chop
239+
%% it off (but not below a minimal length).
240+
LCP = binary:longest_common_prefix(
241+
[atom_to_binary(ConsumerNode, latin1),
242+
atom_to_binary(ProducerNode, latin1)]),
243+
CNode = maybe_cut_name(ConsumerNodeList, LCP),
244+
PNode = maybe_cut_name(ProducerNodeList, LCP),
245+
246+
Hash = binary_to_list(base64:encode(crypto:hash(sha, CName))),
247+
Bkt = string:slice(Bucket, 0, 60),
248+
249+
CName2 = "replication:" ++ PNode ++ "->" ++ CNode ++ ":" ++ Bkt ++
250+
":" ++ Hash,
251+
true = length(CName2) =< ?MAX_DCP_CONNECTION_NAME,
252+
CName2
253+
end.
254+
255+
%% Cut the specified number of bytes from the front of the name but don't
256+
%% shorten below a minimum length.
257+
maybe_cut_name(Name, MaxToChop) ->
258+
Len = length(Name),
259+
Start = case (Len - MaxToChop) >= ?MIN_NODE_NAME of
260+
true ->
261+
MaxToChop;
262+
false ->
263+
max(0, Len - ?MIN_NODE_NAME)
264+
end,
265+
string:slice(Name, Start, ?MIN_NODE_NAME).
223266

224267
get_connections(Bucket) ->
225268
{ok, Connections} =
@@ -280,3 +323,62 @@ maybe_shut_consumer(Reason, Consumer) ->
280323
false ->
281324
ok
282325
end.
326+
327+
-ifdef(TEST).
328+
get_connection_name_test() ->
329+
330+
%% Connection name fits into the maximum allowed
331+
332+
NodeA = 'nodeA.eng.couchbase.com',
333+
NodeB = 'nodeB.eng.couchbase.com',
334+
BucketAB = "bucket1",
335+
ConnAB = get_connection_name(NodeA, NodeB, BucketAB),
336+
?assertEqual("replication:nodeB.eng.couchbase.com->"
337+
"nodeA.eng.couchbase.com:bucket1", ConnAB),
338+
?assertEqual(true, length(ConnAB) =< ?MAX_DCP_CONNECTION_NAME),
339+
340+
%% Test where the connection name, using the previous method, won't
341+
%% fit into the maximum allowed.
342+
343+
Node1 = "[email protected]."
344+
"couchbase-new-pxxxxxxx.svc",
345+
Node2 = "[email protected]."
346+
"couchbase-new-pxxxxxxx.svc",
347+
Bucket12 = "com.yyyyyy.digital.ms.shoppingcart.shoppingcart.1234567890"
348+
"12345678901234567890",
349+
Conn12 = get_connection_name(list_to_atom(Node1), list_to_atom(Node2),
350+
Bucket12),
351+
?assertEqual("replication:1.platform-couchbase-cluster.couchb->"
352+
"0.platform-couchbase-cluster.couchb:com.yyyyyy.digital.ms."
353+
"shoppingcart.shoppingcart.123456789012:"
354+
"TYFMH5ZD2gPLOaLgcuA2VijsZvc=", Conn12),
355+
356+
%% Test that the node names aren't shortened too much (note the only
357+
%% difference is the last character).
358+
359+
Node3 = "ManyManyManyManyCommonCharacters_ns_1@platform-couchbase-cluster"
360+
"-0000",
361+
Node4 = "ManyManyManyManyCommonCharacters_ns_1@platform-couchbase-cluster"
362+
"-0001",
363+
LongBucket = "travel-sample-with-a-very-very-very-very-long-bucket-name",
364+
Conn34 = get_connection_name(list_to_atom(Node3), list_to_atom(Node4),
365+
LongBucket),
366+
?assertEqual("replication:s_1@platform-couchbase-cluster-0001->"
367+
"s_1@platform-couchbase-cluster-0000:travel-sample-with-a-"
368+
"very-very-very-very-long-bucket-name:"
369+
"D/D56MpAKsDt/0yqg6IXKBEaIcY=", Conn34),
370+
371+
%% Test with unique node names but one is much longer than the other.
372+
373+
Node5 = "AShortNodeName",
374+
Node6 = "ManyManyManyManyCommonCharacters_ns_1@platform-couchbase-cluster"
375+
"-AndEvenMoreCharactersToMakeThisNodeNameLongEnoughToRequireIt"
376+
"ToBeShortened",
377+
Conn56 = get_connection_name(list_to_atom(Node5), list_to_atom(Node6),
378+
LongBucket),
379+
?assertEqual("replication:ManyManyManyManyCommonCharacters_ns->"
380+
"AShortNodeName:travel-sample-with-a-very-very-very-very-"
381+
"long-bucket-name:A3aPD1Sik+5ZIz43M6NNTGn9XFw=", Conn56).
382+
383+
384+
-endif.

0 commit comments

Comments
 (0)