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 ]).
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
4550init ({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
8289start_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
221228get_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
224267get_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+ 344+ " couchbase-new-pxxxxxxx.svc" ,
345+ 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