2121
2222import org .elasticsearch .action .ActionListener ;
2323import org .elasticsearch .cluster .routing .AllocationId ;
24+ import org .elasticsearch .common .collect .Tuple ;
2425import org .elasticsearch .common .settings .Settings ;
2526import org .elasticsearch .common .unit .TimeValue ;
2627import org .elasticsearch .index .IndexSettings ;
3031import java .util .Collection ;
3132import java .util .Collections ;
3233import java .util .HashMap ;
34+ import java .util .List ;
3335import java .util .Map ;
3436import java .util .concurrent .atomic .AtomicBoolean ;
3537import java .util .concurrent .atomic .AtomicLong ;
@@ -67,17 +69,17 @@ public void testAddOrRenewRetentionLease() {
6769 minimumRetainingSequenceNumbers [i ] = randomLongBetween (SequenceNumbers .NO_OPS_PERFORMED , Long .MAX_VALUE );
6870 replicationTracker .addRetentionLease (
6971 Integer .toString (i ), minimumRetainingSequenceNumbers [i ], "test-" + i , ActionListener .wrap (() -> {}));
70- assertRetentionLeases (replicationTracker , i + 1 , minimumRetainingSequenceNumbers , () -> 0L );
72+ assertRetentionLeases (replicationTracker , i + 1 , minimumRetainingSequenceNumbers , () -> 0L , true );
7173 }
7274
7375 for (int i = 0 ; i < length ; i ++) {
7476 minimumRetainingSequenceNumbers [i ] = randomLongBetween (minimumRetainingSequenceNumbers [i ], Long .MAX_VALUE );
7577 replicationTracker .renewRetentionLease (Integer .toString (i ), minimumRetainingSequenceNumbers [i ], "test-" + i );
76- assertRetentionLeases (replicationTracker , length , minimumRetainingSequenceNumbers , () -> 0L );
78+ assertRetentionLeases (replicationTracker , length , minimumRetainingSequenceNumbers , () -> 0L , true );
7779 }
7880 }
7981
80- public void testOnNewRetentionLease () {
82+ public void testAddRetentionLeaseCausesRetentionLeaseSync () {
8183 final AllocationId allocationId = AllocationId .newInitializing ();
8284 final Map <String , Long > retentionLeases = new HashMap <>();
8385 final AtomicBoolean invoked = new AtomicBoolean ();
@@ -113,14 +115,23 @@ public void testOnNewRetentionLease() {
113115 replicationTracker .addRetentionLease (id , retainingSequenceNumber , "test" , ActionListener .wrap (() -> {}));
114116 // assert that the new retention lease callback was invoked
115117 assertTrue (invoked .get ());
118+
116119 // reset the invocation marker so that we can assert the callback was not invoked when renewing the lease
117120 invoked .set (false );
118121 replicationTracker .renewRetentionLease (id , retainingSequenceNumber , "test" );
119122 assertFalse (invoked .get ());
120123 }
121124 }
122125
123- public void testExpiration () {
126+ public void testExpirationOnPrimary () {
127+ runExpirationTest (true );
128+ }
129+
130+ public void testExpirationOnReplica () {
131+ runExpirationTest (false );
132+ }
133+
134+ private void runExpirationTest (final boolean primaryMode ) {
124135 final AllocationId allocationId = AllocationId .newInitializing ();
125136 final AtomicLong currentTimeMillis = new AtomicLong (randomLongBetween (0 , 1024 ));
126137 final long retentionLeaseMillis = randomLongBetween (1 , TimeValue .timeValueHours (12 ).millis ());
@@ -141,42 +152,136 @@ public void testExpiration() {
141152 Collections .singleton (allocationId .getId ()),
142153 routingTable (Collections .emptySet (), allocationId ),
143154 Collections .emptySet ());
144- replicationTracker .activatePrimaryMode (SequenceNumbers .NO_OPS_PERFORMED );
155+ if (primaryMode ) {
156+ replicationTracker .activatePrimaryMode (SequenceNumbers .NO_OPS_PERFORMED );
157+ }
145158 final long [] retainingSequenceNumbers = new long [1 ];
146159 retainingSequenceNumbers [0 ] = randomLongBetween (SequenceNumbers .NO_OPS_PERFORMED , Long .MAX_VALUE );
147- replicationTracker .addRetentionLease ("0" , retainingSequenceNumbers [0 ], "test-0" , ActionListener .wrap (() -> {}));
160+ if (primaryMode ) {
161+ replicationTracker .addRetentionLease ("0" , retainingSequenceNumbers [0 ], "test-0" , ActionListener .wrap (() -> {}));
162+ } else {
163+ replicationTracker .updateRetentionLeasesOnReplica (
164+ Collections .singleton (new RetentionLease ("0" , retainingSequenceNumbers [0 ], currentTimeMillis .get (), "test-0" )));
165+ }
148166
149167 {
150168 final Collection <RetentionLease > retentionLeases = replicationTracker .getRetentionLeases ();
151169 assertThat (retentionLeases , hasSize (1 ));
152170 final RetentionLease retentionLease = retentionLeases .iterator ().next ();
153171 assertThat (retentionLease .timestamp (), equalTo (currentTimeMillis .get ()));
154- assertRetentionLeases (replicationTracker , 1 , retainingSequenceNumbers , currentTimeMillis ::get );
172+ assertRetentionLeases (replicationTracker , 1 , retainingSequenceNumbers , currentTimeMillis ::get , primaryMode );
155173 }
156174
157175 // renew the lease
158176 currentTimeMillis .set (currentTimeMillis .get () + randomLongBetween (0 , 1024 ));
159177 retainingSequenceNumbers [0 ] = randomLongBetween (retainingSequenceNumbers [0 ], Long .MAX_VALUE );
160- replicationTracker .renewRetentionLease ("0" , retainingSequenceNumbers [0 ], "test-0" );
178+ if (primaryMode ) {
179+ replicationTracker .renewRetentionLease ("0" , retainingSequenceNumbers [0 ], "test-0" );
180+ } else {
181+ replicationTracker .updateRetentionLeasesOnReplica (
182+ Collections .singleton (new RetentionLease ("0" , retainingSequenceNumbers [0 ], currentTimeMillis .get (), "test-0" )));
183+ }
161184
162185 {
163186 final Collection <RetentionLease > retentionLeases = replicationTracker .getRetentionLeases ();
164187 assertThat (retentionLeases , hasSize (1 ));
165188 final RetentionLease retentionLease = retentionLeases .iterator ().next ();
166189 assertThat (retentionLease .timestamp (), equalTo (currentTimeMillis .get ()));
167- assertRetentionLeases (replicationTracker , 1 , retainingSequenceNumbers , currentTimeMillis ::get );
190+ assertRetentionLeases (replicationTracker , 1 , retainingSequenceNumbers , currentTimeMillis ::get , primaryMode );
168191 }
169192
170193 // now force the lease to expire
171194 currentTimeMillis .set (currentTimeMillis .get () + randomLongBetween (retentionLeaseMillis , Long .MAX_VALUE - currentTimeMillis .get ()));
172- assertRetentionLeases (replicationTracker , 0 , retainingSequenceNumbers , currentTimeMillis ::get );
195+ if (primaryMode ) {
196+ assertRetentionLeases (replicationTracker , 0 , retainingSequenceNumbers , currentTimeMillis ::get , true );
197+ } else {
198+ // leases do not expire on replicas until synced from the primary
199+ assertRetentionLeases (replicationTracker , 1 , retainingSequenceNumbers , currentTimeMillis ::get , false );
200+ }
201+ }
202+
203+ public void testRetentionLeaseExpirationCausesRetentionLeaseSync () {
204+ final AllocationId allocationId = AllocationId .newInitializing ();
205+ final AtomicLong currentTimeMillis = new AtomicLong (randomLongBetween (0 , 1024 ));
206+ final long retentionLeaseMillis = randomLongBetween (1 , TimeValue .timeValueHours (12 ).millis ());
207+ final Settings settings = Settings
208+ .builder ()
209+ .put (IndexSettings .INDEX_SOFT_DELETES_RETENTION_LEASE_SETTING .getKey (), TimeValue .timeValueMillis (retentionLeaseMillis ))
210+ .build ();
211+ final Map <String , Tuple <Long , Long >> retentionLeases = new HashMap <>();
212+ final AtomicBoolean invoked = new AtomicBoolean ();
213+ final AtomicReference <ReplicationTracker > reference = new AtomicReference <>();
214+ final ReplicationTracker replicationTracker = new ReplicationTracker (
215+ new ShardId ("test" , "_na" , 0 ),
216+ allocationId .getId (),
217+ IndexSettingsModule .newIndexSettings ("test" , settings ),
218+ UNASSIGNED_SEQ_NO ,
219+ value -> {},
220+ currentTimeMillis ::get ,
221+ (leases , listener ) -> {
222+ // we do not want to hold a lock on the replication tracker in the callback!
223+ assertFalse (Thread .holdsLock (reference .get ()));
224+ invoked .set (true );
225+ assertThat (
226+ leases .stream ().collect (Collectors .toMap (RetentionLease ::id , ReplicationTrackerRetentionLeaseTests ::toTuple )),
227+ equalTo (retentionLeases ));
228+ });
229+ reference .set (replicationTracker );
230+ replicationTracker .updateFromMaster (
231+ randomNonNegativeLong (),
232+ Collections .singleton (allocationId .getId ()),
233+ routingTable (Collections .emptySet (), allocationId ),
234+ Collections .emptySet ());
235+ replicationTracker .activatePrimaryMode (SequenceNumbers .NO_OPS_PERFORMED );
236+
237+ final int length = randomIntBetween (0 , 8 );
238+ for (int i = 0 ; i < length ; i ++) {
239+ final String id = randomAlphaOfLength (8 );
240+ final long retainingSequenceNumber = randomLongBetween (SequenceNumbers .NO_OPS_PERFORMED , Long .MAX_VALUE );
241+ retentionLeases .put (id , Tuple .tuple (retainingSequenceNumber , currentTimeMillis .get ()));
242+ replicationTracker .addRetentionLease (id , retainingSequenceNumber , "test" , ActionListener .wrap (() -> {}));
243+ // assert that the new retention lease callback was invoked
244+ assertTrue (invoked .get ());
245+
246+ // reset the invocation marker so that we can assert the callback was not invoked when renewing the lease
247+ invoked .set (false );
248+ currentTimeMillis .set (1 + currentTimeMillis .get ());
249+ retentionLeases .put (id , Tuple .tuple (retainingSequenceNumber , currentTimeMillis .get ()));
250+ replicationTracker .renewRetentionLease (id , retainingSequenceNumber , "test" );
251+
252+ // reset the invocation marker so that we can assert the callback was invoked if any leases are expired
253+ assertFalse (invoked .get ());
254+ // randomly expire some leases
255+ final long currentTimeMillisIncrement = randomLongBetween (0 , Long .MAX_VALUE - currentTimeMillis .get ());
256+ // calculate the expired leases and update our tracking map
257+ final List <String > expiredIds = retentionLeases .entrySet ()
258+ .stream ()
259+ .filter (r -> currentTimeMillis .get () + currentTimeMillisIncrement > r .getValue ().v2 () + retentionLeaseMillis )
260+ .map (Map .Entry ::getKey )
261+ .collect (Collectors .toList ());
262+ expiredIds .forEach (retentionLeases ::remove );
263+ currentTimeMillis .set (currentTimeMillis .get () + currentTimeMillisIncrement );
264+ // getting the leases has the side effect of calculating which leases are expired and invoking the sync callback
265+ final Collection <RetentionLease > current = replicationTracker .getRetentionLeases ();
266+ // the current leases should equal our tracking map
267+ assertThat (
268+ current .stream ().collect (Collectors .toMap (RetentionLease ::id , ReplicationTrackerRetentionLeaseTests ::toTuple )),
269+ equalTo (retentionLeases ));
270+ // the callback should only be invoked if there were expired leases
271+ assertThat (invoked .get (), equalTo (expiredIds .isEmpty () == false ));
272+ }
273+ }
274+
275+ private static Tuple <Long , Long > toTuple (final RetentionLease retentionLease ) {
276+ return Tuple .tuple (retentionLease .retainingSequenceNumber (), retentionLease .timestamp ());
173277 }
174278
175279 private void assertRetentionLeases (
176280 final ReplicationTracker replicationTracker ,
177281 final int size ,
178282 final long [] minimumRetainingSequenceNumbers ,
179- final LongSupplier currentTimeMillisSupplier ) {
283+ final LongSupplier currentTimeMillisSupplier ,
284+ final boolean primaryMode ) {
180285 final Collection <RetentionLease > retentionLeases = replicationTracker .getRetentionLeases ();
181286 final Map <String , RetentionLease > idToRetentionLease = new HashMap <>();
182287 for (final RetentionLease retentionLease : retentionLeases ) {
@@ -188,9 +293,12 @@ private void assertRetentionLeases(
188293 assertThat (idToRetentionLease .keySet (), hasItem (Integer .toString (i )));
189294 final RetentionLease retentionLease = idToRetentionLease .get (Integer .toString (i ));
190295 assertThat (retentionLease .retainingSequenceNumber (), equalTo (minimumRetainingSequenceNumbers [i ]));
191- assertThat (
192- currentTimeMillisSupplier .getAsLong () - retentionLease .timestamp (),
193- lessThanOrEqualTo (replicationTracker .indexSettings ().getRetentionLeaseMillis ()));
296+ if (primaryMode ) {
297+ // retention leases can be expired on replicas, so we can only assert on primaries here
298+ assertThat (
299+ currentTimeMillisSupplier .getAsLong () - retentionLease .timestamp (),
300+ lessThanOrEqualTo (replicationTracker .indexSettings ().getRetentionLeaseMillis ()));
301+ }
194302 assertThat (retentionLease .source (), equalTo ("test-" + i ));
195303 }
196304 }
0 commit comments