4028449625e7e06e97859ee9bb9406ac614dc570
[policy/pap.git] / main / src / test / java / org / onap / policy / pap / main / comm / PdpModifyRequestMapTest.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP PAP
4  * ================================================================================
5  * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2020-2021,2023 Nordix Foundation.
7  * Modifications Copyright (C) 2022 Bell Canada. All rights reserved.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.pap.main.comm;
24
25 import static org.assertj.core.api.Assertions.assertThat;
26 import static org.assertj.core.api.Assertions.assertThatCode;
27 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertNotNull;
31 import static org.junit.Assert.assertSame;
32 import static org.junit.Assert.assertTrue;
33 import static org.mockito.ArgumentMatchers.any;
34 import static org.mockito.ArgumentMatchers.eq;
35 import static org.mockito.Mockito.doAnswer;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.never;
38 import static org.mockito.Mockito.times;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41
42 import java.time.Instant;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.stream.Collectors;
50 import javax.ws.rs.core.Response.Status;
51 import org.assertj.core.api.Assertions;
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.ArgumentCaptor;
56 import org.mockito.Captor;
57 import org.mockito.Mock;
58 import org.mockito.junit.MockitoJUnitRunner;
59 import org.onap.policy.models.base.PfModelException;
60 import org.onap.policy.models.base.PfModelRuntimeException;
61 import org.onap.policy.models.pdp.concepts.Pdp;
62 import org.onap.policy.models.pdp.concepts.PdpGroup;
63 import org.onap.policy.models.pdp.concepts.PdpMessage;
64 import org.onap.policy.models.pdp.concepts.PdpStateChange;
65 import org.onap.policy.models.pdp.concepts.PdpStatus;
66 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
67 import org.onap.policy.models.pdp.concepts.PdpUpdate;
68 import org.onap.policy.models.pdp.enums.PdpState;
69 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
70 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
71 import org.onap.policy.pap.main.comm.msgdata.Request;
72 import org.onap.policy.pap.main.comm.msgdata.RequestListener;
73 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
74 import org.onap.policy.pap.main.service.PdpGroupService;
75 import org.onap.policy.pap.main.service.PolicyStatusService;
76 import org.springframework.test.util.ReflectionTestUtils;
77
78 @RunWith(MockitoJUnitRunner.class)
79 public class PdpModifyRequestMapTest extends CommonRequestBase {
80     private static final String MY_REASON = "my reason";
81     private static final int EXPIRED_SECONDS = 100;
82
83     /**
84      * Used to capture input to dao.createPdpGroups().
85      */
86     @Captor
87     private ArgumentCaptor<List<PdpGroup>> createCaptor;
88
89
90     /**
91      * Used to capture input to dao.updatePdpGroups().
92      */
93     @Captor
94     private ArgumentCaptor<List<PdpGroup>> updateCaptor;
95
96     /**
97      * Used to capture input to undeployer.undeploy().
98      */
99     @Captor
100     private ArgumentCaptor<Collection<ToscaConceptIdentifier>> undeployCaptor;
101
102     @Mock
103     private PdpRequests requests;
104
105     @Mock
106     private PolicyUndeployer undeployer;
107
108     @Mock
109     private PdpStatusMessageHandler responseHandler;
110
111     @Mock
112     private PdpGroupService pdpGroupService;
113
114     @Mock
115     private PolicyStatusService policyStatusService;
116
117     private MyMap map;
118     private PdpUpdate update;
119     private PdpStateChange change;
120     private PdpStatus response;
121
122     /**
123      * Sets up.
124      *
125      * @throws Exception if an error occurs
126      */
127     @Before
128     @Override
129     public void setUp() throws Exception {
130         super.setUp();
131
132         response = new PdpStatus();
133
134         update = makeUpdate(PDP1, MY_GROUP, MY_SUBGROUP);
135         change = makeStateChange(PDP1, MY_STATE);
136
137         when(requests.getPdpName()).thenReturn(PDP1);
138         when(requests.isFirstInQueue(any())).thenReturn(true);
139
140         response.setName(MY_NAME);
141         response.setState(MY_STATE);
142         response.setPdpGroup(update.getPdpGroup());
143         response.setPdpSubgroup(update.getPdpSubgroup());
144         response.setPolicies(Collections.emptyList());
145
146         map = new MyMap(mapParams);
147     }
148
149     @Test
150     public void testPdpModifyRequestMap() {
151         assertSame(mapParams, ReflectionTestUtils.getField(map, "params"));
152         assertSame(lock, ReflectionTestUtils.getField(map, "modifyLock"));
153     }
154
155     @Test
156     public void testIsEmpty() {
157         assertTrue(map.isEmpty());
158
159         map.addRequest(change);
160         assertFalse(map.isEmpty());
161
162         // indicate success
163         getListener(getSingletons(1).get(0)).success(PDP1, response);
164
165         assertTrue(map.isEmpty());
166         verify(responseHandler, never()).handlePdpStatus(response);
167     }
168
169     @Test
170     public void testStopPublishing() {
171         // try with non-existent PDP
172         map.stopPublishing(PDP1);
173
174         // now start a PDP and try it
175         map.addRequest(change);
176         map.stopPublishing(PDP1);
177         verify(requests).stopPublishing();
178
179         // try again - it shouldn't stop publishing again
180         map.stopPublishing(PDP1);
181         verify(requests, times(1)).stopPublishing();
182     }
183
184     @Test
185     public void testAddRequestPdpUpdatePdpStateChange_BothNull() {
186         // nulls should be ok
187         Assertions.assertThatCode(() -> map.addRequest(null, null)).doesNotThrowAnyException();
188     }
189
190     @Test
191     public void testAddRequestPdpUpdatePdpStateChange_NullUpdate() {
192         map.addRequest(null, change);
193
194         Request req = getSingletons(1).get(0);
195         assertSame(change, req.getMessage());
196         assertEquals("pdp_1 PdpStateChange", req.getName());
197     }
198
199     @Test
200     public void testAddRequestPdpUpdatePdpStateChange_NullStateChange() {
201         map.addRequest(update, null);
202
203         Request req = getSingletons(1).get(0);
204         assertSame(update, req.getMessage());
205         assertEquals("pdp_1 PdpUpdate", req.getName());
206     }
207
208     /**
209      * Tests addRequest() when two requests are provided and the second is an "activate"
210      * message.
211      */
212     @Test
213     public void testAddRequestPdpUpdatePdpStateChange_BothProvided_Active() {
214         change.setState(PdpState.ACTIVE);
215         map.addRequest(update, change);
216
217         // should have only allocated one request structure
218         assertEquals(1, map.nalloc);
219
220         // both requests should have been added
221         List<Request> values = getSingletons(2);
222
223         // update should appear first
224         Request req = values.remove(0);
225         assertSame(update, req.getMessage());
226         assertEquals("pdp_1 PdpUpdate", req.getName());
227
228         req = values.remove(0);
229         assertSame(change, req.getMessage());
230         assertEquals("pdp_1 PdpStateChange", req.getName());
231     }
232
233     /**
234      * Tests addRequest() when two requests are provided and the second is "deactivate"
235      * message.
236      */
237     @Test
238     public void testAddRequestPdpUpdatePdpStateChange_BothProvided_Passive() {
239         change.setState(PdpState.PASSIVE);
240         map.addRequest(update, change);
241
242         // should have only allocated one request structure
243         assertEquals(1, map.nalloc);
244
245         // both requests should have been added
246         List<Request> values = getSingletons(2);
247
248         // state-change should appear first
249         Request req = values.remove(0);
250         assertSame(change, req.getMessage());
251         assertEquals("pdp_1 PdpStateChange", req.getName());
252
253         req = values.remove(0);
254         assertSame(update, req.getMessage());
255         assertEquals("pdp_1 PdpUpdate", req.getName());
256     }
257
258     @Test
259     public void testAddRequestPdpUpdatePdpStateChange() {
260         // null should be ok
261         map.addRequest(null, null);
262
263         map.addRequest(change);
264
265         Request req = getSingletons(1).get(0);
266         assertSame(change, req.getMessage());
267         assertEquals("pdp_1 PdpStateChange", req.getName());
268
269         // broadcast should throw an exception
270         change.setName(null);
271         assertThatIllegalArgumentException().isThrownBy(() -> map.addRequest(change))
272                         .withMessageStartingWith("unexpected broadcast message: PdpStateChange");
273     }
274
275     @Test
276     public void testAddRequestPdpUpdate() {
277         // null should be ok
278         map.addRequest((PdpUpdate) null);
279
280         map.addRequest(update);
281
282         Request req = getSingletons(1).get(0);
283         assertSame(update, req.getMessage());
284         assertEquals("pdp_1 PdpUpdate", req.getName());
285
286         // broadcast should throw an exception
287         update.setName(null);
288         assertThatIllegalArgumentException().isThrownBy(() -> map.addRequest(update))
289                         .withMessageStartingWith("unexpected broadcast message: PdpUpdate");
290     }
291
292     @Test
293     public void testAddRequestPdpStateChange() {
294         // null should be ok
295         map.addRequest((PdpStateChange) null);
296
297         map.addRequest(change);
298
299         Request req = getSingletons(1).get(0);
300         assertSame(change, req.getMessage());
301         assertEquals("pdp_1 PdpStateChange", req.getName());
302
303         // broadcast should throw an exception
304         change.setName(null);
305         assertThatIllegalArgumentException().isThrownBy(() -> map.addRequest(change))
306                         .withMessageStartingWith("unexpected broadcast message: PdpStateChange");
307     }
308
309     @Test
310     public void testAddSingleton() {
311         map.addRequest(change);
312         assertEquals(1, map.nalloc);
313
314         // should have one singleton
315         getSingletons(1);
316
317         // add another request with the same PDP
318         map.addRequest(makeStateChange(PDP1, MY_STATE));
319         assertEquals(1, map.nalloc);
320
321         // should now have another singleton
322         getSingletons(2);
323
324
325         // add another request with a different PDP
326         map.addRequest(makeStateChange(DIFFERENT, MY_STATE));
327
328         // should now have another allocation
329         assertEquals(2, map.nalloc);
330
331         // should now have another singleton
332         getSingletons(3);
333     }
334
335     @Test
336     public void testStartNextRequest_NoMore() {
337         map.addRequest(change);
338
339         // indicate success
340         getListener(getSingletons(1).get(0)).success(PDP1, response);
341
342         verify(responseHandler, never()).handlePdpStatus(response);
343
344         /*
345          * the above should have removed the requests so next time should allocate a new
346          * one
347          */
348         map.addRequest(change);
349         assertEquals(2, map.nalloc);
350     }
351
352     @Test
353     public void testStartNextRequest_HaveMore() {
354         map.addRequest(update);
355         map.addRequest(change);
356
357         Request updateReq = getSingletons(2).get(0);
358
359         // indicate success with the update
360         when(requests.startNextRequest(updateReq)).thenReturn(true);
361         getListener(updateReq).success(PDP1, response);
362
363         // should be called for the update
364         verify(responseHandler).handlePdpStatus(response);
365
366         // should have started the next request
367         verify(requests).startNextRequest(updateReq);
368
369         /*
370          * requests should still be there, so adding another request should not allocate a
371          * new one
372          */
373         map.addRequest(update);
374         assertEquals(1, map.nalloc);
375     }
376
377     @Test
378     public void testRemoveExpiredPdps() throws Exception {
379         PdpGroup group1 = makeGroup(MY_GROUP);
380         group1.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP1)));
381
382         PdpGroup group2 = makeGroup(MY_GROUP2);
383         group2.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP2, PDP3), makeSubGroup(MY_SUBGROUP2, PDP4)));
384
385         // expire all items in group2's first subgroup
386         Instant expired = Instant.now().minusSeconds(EXPIRED_SECONDS);
387         group2.getPdpSubgroups().get(0).getPdpInstances().forEach(pdp -> pdp.setLastUpdate(expired));
388
389         when(pdpGroupService.getFilteredPdpGroups(any())).thenReturn(List.of(group1, group2));
390
391         // run it
392         map.removeExpiredPdps();
393
394         // should have removed from the group
395         List<PdpGroup> groups = getGroupUpdates();
396         assertThat(groups).hasSize(1);
397         assertThat(groups.get(0)).isSameAs(group2);
398         assertThat(group2.getPdpSubgroups()).hasSize(2);
399
400         final Iterator<PdpSubGroup> iter = group2.getPdpSubgroups().iterator();
401
402         PdpSubGroup subgrp = iter.next();
403         assertThat(subgrp.getPdpInstances()).isEmpty();
404         assertThat(subgrp.getCurrentInstanceCount()).isZero();
405
406         subgrp = iter.next();
407         assertThat(subgrp.getPdpInstances()).hasSize(1);
408         assertThat(subgrp.getCurrentInstanceCount()).isEqualTo(1);
409         assertThat(subgrp.getPdpInstances().get(0).getInstanceId()).isEqualTo(PDP4);
410     }
411
412     @Test
413     public void testRemoveExpiredPdps_NothingExpired() throws Exception {
414         PdpGroup group1 = makeGroup(MY_GROUP);
415         group1.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP1)));
416
417         when(pdpGroupService.getFilteredPdpGroups(any())).thenReturn(List.of(group1));
418
419         // run it
420         map.removeExpiredPdps();
421
422         verify(pdpGroupService, never()).updatePdpGroups(any());
423         verify(publisher, never()).enqueue(any());
424     }
425
426     @Test
427     public void testRemoveExpiredPdps_DaoEx() throws Exception {
428         when(pdpGroupService.getFilteredPdpGroups(any())).thenThrow(makeRuntimeException());
429
430         assertThatCode(map::removeExpiredPdps).doesNotThrowAnyException();
431     }
432
433     @Test
434     public void testRemoveExpiredPdps_DaoRtEx() throws Exception {
435         when(pdpGroupService.getFilteredPdpGroups(any())).thenThrow(makeRuntimeException());
436
437         assertThatCode(map::removeExpiredPdps).doesNotThrowAnyException();
438     }
439
440     @Test
441     public void testRemoveFromSubgroup() throws Exception {
442         PdpGroup group = makeGroup(MY_GROUP);
443         group.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP1, PDP2, PDP3)));
444
445         // expire pdp1 and pdp3
446         Instant expired = Instant.now().minusSeconds(EXPIRED_SECONDS);
447         List<Pdp> pdps = group.getPdpSubgroups().get(0).getPdpInstances();
448         pdps.get(0).setLastUpdate(expired);
449         pdps.get(2).setLastUpdate(expired);
450         when(pdpGroupService.getFilteredPdpGroups(any())).thenReturn(List.of(group));
451
452         // run it
453         map.removeExpiredPdps();
454
455         // should have removed from the group
456         List<PdpGroup> groups = getGroupUpdates();
457         assertThat(groups).hasSize(1);
458         assertThat(groups.get(0)).isSameAs(group);
459         assertThat(group.getPdpSubgroups()).hasSize(1);
460         assertThat(group.getPdpSubgroups().get(0).getCurrentInstanceCount()).isEqualTo(1);
461
462         pdps = group.getPdpSubgroups().get(0).getPdpInstances();
463         assertThat(pdps).hasSize(1);
464         assertThat(pdps.get(0).getInstanceId()).isEqualTo(PDP2);
465     }
466
467     protected PfModelException makeException() {
468         return new PfModelException(Status.BAD_REQUEST, "expected exception");
469     }
470
471     protected PfModelRuntimeException makeRuntimeException() {
472         return new PfModelRuntimeException(Status.BAD_REQUEST, "expected exception");
473     }
474
475     @Test
476     public void testMakePdpRequests() {
477         // this should invoke the real method without throwing an exception
478         PdpModifyRequestMap reqMap =
479             new PdpModifyRequestMap(pdpGroupService, policyStatusService, responseHandler, undeployer, notifier);
480         reqMap.initialize(mapParams);
481         reqMap.addRequest(change);
482
483         QueueToken<PdpMessage> token = queue.poll();
484         assertNotNull(token);
485         assertSame(change, token.get());
486
487         verify(dispatcher).register(eq(change.getRequestId()), any());
488         verify(timers).register(eq(change.getRequestId()), any());
489     }
490
491     @Test
492     public void testSingletonListenerFailure() throws Exception {
493         map.addRequest(change);
494
495         // invoke the method
496         invokeFailureHandler(1);
497
498         verify(undeployer, never()).undeploy(any(), any(), any());
499         verify(requests, never()).stopPublishing();
500
501         // requests should have been removed from the map so this should allocate another
502         map.addRequest(update);
503         assertEquals(2, map.nalloc);
504     }
505
506     /**
507      * Tests Listener.failure() when something has to be undeployed.
508      */
509     @Test
510     public void testSingletonListenerFailureUndeploy() throws Exception {
511
512         ToscaConceptIdentifier ident = new ToscaConceptIdentifier("undeployed", "2.3.4");
513         ToscaPolicy policy = mock(ToscaPolicy.class);
514         when(policy.getIdentifier()).thenReturn(ident);
515
516         // add some policies to the update
517         update.setPoliciesToBeDeployed(Arrays.asList(policy));
518
519         map.addRequest(update);
520
521         /*
522          * Reconfigure the request when undeploy() is called. Also arrange for undeploy()
523          * to throw an exception.
524          */
525         Request req = getSingletons(1).get(0);
526
527         doAnswer(ans -> {
528             PdpUpdate update2 = new PdpUpdate(update);
529             update2.setPoliciesToBeDeployed(Collections.emptyList());
530             update2.setPoliciesToBeUndeployed(Arrays.asList(policy.getIdentifier()));
531             assertTrue(req.reconfigure(update2));
532             throw makeException();
533         }).when(undeployer).undeploy(any(), any(), any());
534
535         // indicate that all policies failed (because response has no policies)
536         response.setName(PDP1);
537         req.setNotifier(notifier);
538         req.checkResponse(response);
539
540         // invoke the method
541         invokeFailureHandler(1);
542
543         verify(undeployer).undeploy(eq(MY_GROUP), eq(MY_SUBGROUP), undeployCaptor.capture());
544         assertEquals(Arrays.asList(ident).toString(), undeployCaptor.getValue().toString());
545
546         // no effect on the map
547         map.addRequest(update);
548         assertEquals(1, map.nalloc);
549     }
550
551     /**
552      * Tests Listener.failure() when something has to be undeployed, but the message
553      * remains unchanged.
554      */
555     @Test
556     public void testSingletonListenerFailureUndeployMessageUnchanged() throws Exception {
557
558         ToscaConceptIdentifier ident = new ToscaConceptIdentifier("msg-unchanged", "8.7.6");
559         ToscaPolicy policy = mock(ToscaPolicy.class);
560         when(policy.getIdentifier()).thenReturn(ident);
561
562         // add some policies to the update
563         update.setPoliciesToBeDeployed(Arrays.asList(policy));
564
565         map.addRequest(update);
566
567         // indicate that all policies failed (because response has no policies)
568         response.setName(PDP1);
569         Request req = getSingletons(1).get(0);
570         req.setNotifier(notifier);
571         req.checkResponse(response);
572
573         // invoke the method
574         invokeFailureHandler(1);
575
576         verify(undeployer).undeploy(eq(MY_GROUP), eq(MY_SUBGROUP), undeployCaptor.capture());
577         assertEquals(Arrays.asList(ident).toString(), undeployCaptor.getValue().toString());
578
579         // requests should have been removed from the map so this should allocate another
580         map.addRequest(update);
581         assertEquals(2, map.nalloc);
582     }
583
584     @Test
585     public void testSingletonListenerSuccess() throws Exception {
586         map.addRequest(change);
587
588         // invoke the method
589         invokeSuccessHandler(1);
590
591         verify(requests, never()).stopPublishing();
592
593         // requests should have been removed from the map so this should allocate another
594         map.addRequest(update);
595         assertEquals(2, map.nalloc);
596     }
597
598     @Test
599     public void testRequestCompleted_LastRequest() throws Exception {
600         map.addRequest(change);
601
602         // invoke the method
603         invokeSuccessHandler(1);
604
605         verify(requests, never()).stopPublishing();
606
607         // requests should have been removed from the map so this should allocate another
608         map.addRequest(update);
609         assertEquals(2, map.nalloc);
610     }
611
612     @Test
613     public void testRequestCompleted_NameMismatch() throws Exception {
614         // use a different name
615         when(requests.getPdpName()).thenReturn(DIFFERENT);
616
617         map.addRequest(change);
618
619         // put the PDP in a group
620         PdpGroup group = makeGroup(MY_GROUP);
621         group.setPdpSubgroups(Arrays.asList(makeSubGroup(MY_SUBGROUP, PDP1, DIFFERENT)));
622
623         // invoke the method - with a different name (i.e., PDP1 instead of DIFFERENT)
624         invokeSuccessHandler(1);
625
626         verify(requests, never()).stopPublishing();
627
628         // no effect on the map
629         map.addRequest(update);
630         assertEquals(1, map.nalloc);
631
632         // no updates
633         verify(pdpGroupService, never()).updatePdpGroups(any());
634     }
635
636     @Test
637     public void testRequestCompleted_AlreadyStopped() throws Exception {
638         map.addRequest(change);
639
640         map.stopPublishing(PDP1);
641
642         // invoke the method
643         invokeSuccessHandler(1);
644
645         // should have called this a second time
646         verify(requests, times(2)).stopPublishing();
647
648         // requests should have been removed from the map so this should allocate another
649         map.addRequest(update);
650         assertEquals(2, map.nalloc);
651     }
652
653     @Test
654     public void testRequestCompleted_NotFirstInQueue() throws Exception {
655         map.addRequest(change);
656
657         when(requests.isFirstInQueue(any())).thenReturn(false);
658
659         // invoke the method
660         invokeSuccessHandler(1);
661
662         // should not have called this
663         verify(requests, never()).stopPublishing();
664
665         // no effect on the map
666         map.addRequest(update);
667         assertEquals(1, map.nalloc);
668     }
669
670     @Test
671     public void testSingletonListenerRetryCountExhausted() throws Exception {
672         final var request = map.addRequest(change);
673
674         // invoke the method
675         invokeLastRetryHandler(1, request);
676
677         verify(requests).stopPublishing();
678     }
679
680
681     /**
682      * Invokes the first request's listener.success() method.
683      *
684      * @param count expected number of requests
685      */
686     private void invokeSuccessHandler(int count) {
687         getListener(getSingletons(count).get(0)).success(PDP1, response);
688     }
689
690     /**
691      * Invokes the first request's listener.failure() method.
692      *
693      * @param count expected number of requests
694      */
695     private void invokeFailureHandler(int count) {
696         getListener(getSingletons(count).get(0)).failure(PDP1, MY_REASON);
697     }
698
699     /**
700      * Invokes the first request's listener.retryCountExhausted() method.
701      *
702      * @param count expected number of requests
703      * @param request request whose count was exhausted
704      */
705     private void invokeLastRetryHandler(int count, Request request) {
706         getListener(getSingletons(count).get(0)).retryCountExhausted(request);
707     }
708
709     /**
710      * Gets the singleton requests added to {@link #requests}.
711      *
712      * @param count number of singletons expected
713      * @return the singleton requests
714      */
715     private List<Request> getSingletons(int count) {
716         ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
717
718         verify(requests, times(count)).addSingleton(captor.capture());
719         return captor.getAllValues();
720     }
721
722     /**
723      * Gets the listener from a request.
724      *
725      * @param request request of interest
726      * @return the request's listener
727      */
728     private RequestListener getListener(Request request) {
729         return (RequestListener) ReflectionTestUtils.getField(request, "listener");
730     }
731
732     private PdpGroup makeGroup(String name) {
733         PdpGroup group = new PdpGroup();
734
735         group.setName(name);
736
737         return group;
738     }
739
740     private PdpSubGroup makeSubGroup(String pdpType, String... pdpNames) {
741         PdpSubGroup subgroup = new PdpSubGroup();
742
743         subgroup.setPdpType(pdpType);
744         subgroup.setCurrentInstanceCount(pdpNames.length);
745         subgroup.setPdpInstances(Arrays.asList(pdpNames).stream().map(this::makePdp).collect(Collectors.toList()));
746
747         return subgroup;
748     }
749
750     private Pdp makePdp(String pdpName) {
751         Pdp pdp = new Pdp();
752         pdp.setInstanceId(pdpName);
753         pdp.setLastUpdate(Instant.now());
754
755         return pdp;
756     }
757
758     /**
759      * Gets the input to the method.
760      *
761      * @return the input that was passed to the dao.updatePdpGroups() method
762      * @throws Exception if an error occurred
763      */
764     private List<PdpGroup> getGroupUpdates() throws Exception {
765         verify(pdpGroupService).updatePdpGroups(updateCaptor.capture());
766
767         return copyList(updateCaptor.getValue());
768     }
769
770     /**
771      * Copies a list and sorts it by group name.
772      *
773      * @param source source list to copy
774      * @return a copy of the source list
775      */
776     private List<PdpGroup> copyList(List<PdpGroup> source) {
777         List<PdpGroup> newlst = new ArrayList<>(source);
778         Collections.sort(newlst, (left, right) -> left.getName().compareTo(right.getName()));
779         return newlst;
780     }
781
782     private class MyMap extends PdpModifyRequestMap {
783         /**
784          * Number of times requests were allocated.
785          */
786         private int nalloc = 0;
787
788         public MyMap(PdpModifyRequestMapParams params) {
789             super(pdpGroupService, policyStatusService, responseHandler, undeployer, notifier);
790             super.initialize(params);;
791         }
792
793         @Override
794         protected PdpRequests makePdpRequests(String pdpName) {
795             ++nalloc;
796             return requests;
797         }
798     }
799 }