2 * ============LICENSE_START=======================================================
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
23 package org.onap.policy.pap.main.comm;
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;
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;
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;
84 * Used to capture input to dao.createPdpGroups().
87 private ArgumentCaptor<List<PdpGroup>> createCaptor;
91 * Used to capture input to dao.updatePdpGroups().
94 private ArgumentCaptor<List<PdpGroup>> updateCaptor;
97 * Used to capture input to undeployer.undeploy().
100 private ArgumentCaptor<Collection<ToscaConceptIdentifier>> undeployCaptor;
103 private PdpRequests requests;
106 private PolicyUndeployer undeployer;
109 private PdpStatusMessageHandler responseHandler;
112 private PdpGroupService pdpGroupService;
115 private PolicyStatusService policyStatusService;
118 private PdpUpdate update;
119 private PdpStateChange change;
120 private PdpStatus response;
125 * @throws Exception if an error occurs
129 public void setUp() throws Exception {
132 response = new PdpStatus();
134 update = makeUpdate(PDP1, MY_GROUP, MY_SUBGROUP);
135 change = makeStateChange(PDP1, MY_STATE);
137 when(requests.getPdpName()).thenReturn(PDP1);
138 when(requests.isFirstInQueue(any())).thenReturn(true);
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());
146 map = new MyMap(mapParams);
150 public void testPdpModifyRequestMap() {
151 assertSame(mapParams, ReflectionTestUtils.getField(map, "params"));
152 assertSame(lock, ReflectionTestUtils.getField(map, "modifyLock"));
156 public void testIsEmpty() {
157 assertTrue(map.isEmpty());
159 map.addRequest(change);
160 assertFalse(map.isEmpty());
163 getListener(getSingletons(1).get(0)).success(PDP1, response);
165 assertTrue(map.isEmpty());
166 verify(responseHandler, never()).handlePdpStatus(response);
170 public void testStopPublishing() {
171 // try with non-existent PDP
172 map.stopPublishing(PDP1);
174 // now start a PDP and try it
175 map.addRequest(change);
176 map.stopPublishing(PDP1);
177 verify(requests).stopPublishing();
179 // try again - it shouldn't stop publishing again
180 map.stopPublishing(PDP1);
181 verify(requests, times(1)).stopPublishing();
185 public void testAddRequestPdpUpdatePdpStateChange_BothNull() {
186 // nulls should be ok
187 Assertions.assertThatCode(() -> map.addRequest(null, null)).doesNotThrowAnyException();
191 public void testAddRequestPdpUpdatePdpStateChange_NullUpdate() {
192 map.addRequest(null, change);
194 Request req = getSingletons(1).get(0);
195 assertSame(change, req.getMessage());
196 assertEquals("pdp_1 PdpStateChange", req.getName());
200 public void testAddRequestPdpUpdatePdpStateChange_NullStateChange() {
201 map.addRequest(update, null);
203 Request req = getSingletons(1).get(0);
204 assertSame(update, req.getMessage());
205 assertEquals("pdp_1 PdpUpdate", req.getName());
209 * Tests addRequest() when two requests are provided and the second is an "activate"
213 public void testAddRequestPdpUpdatePdpStateChange_BothProvided_Active() {
214 change.setState(PdpState.ACTIVE);
215 map.addRequest(update, change);
217 // should have only allocated one request structure
218 assertEquals(1, map.nalloc);
220 // both requests should have been added
221 List<Request> values = getSingletons(2);
223 // update should appear first
224 Request req = values.remove(0);
225 assertSame(update, req.getMessage());
226 assertEquals("pdp_1 PdpUpdate", req.getName());
228 req = values.remove(0);
229 assertSame(change, req.getMessage());
230 assertEquals("pdp_1 PdpStateChange", req.getName());
234 * Tests addRequest() when two requests are provided and the second is "deactivate"
238 public void testAddRequestPdpUpdatePdpStateChange_BothProvided_Passive() {
239 change.setState(PdpState.PASSIVE);
240 map.addRequest(update, change);
242 // should have only allocated one request structure
243 assertEquals(1, map.nalloc);
245 // both requests should have been added
246 List<Request> values = getSingletons(2);
248 // state-change should appear first
249 Request req = values.remove(0);
250 assertSame(change, req.getMessage());
251 assertEquals("pdp_1 PdpStateChange", req.getName());
253 req = values.remove(0);
254 assertSame(update, req.getMessage());
255 assertEquals("pdp_1 PdpUpdate", req.getName());
259 public void testAddRequestPdpUpdatePdpStateChange() {
261 map.addRequest(null, null);
263 map.addRequest(change);
265 Request req = getSingletons(1).get(0);
266 assertSame(change, req.getMessage());
267 assertEquals("pdp_1 PdpStateChange", req.getName());
269 // broadcast should throw an exception
270 change.setName(null);
271 assertThatIllegalArgumentException().isThrownBy(() -> map.addRequest(change))
272 .withMessageStartingWith("unexpected broadcast message: PdpStateChange");
276 public void testAddRequestPdpUpdate() {
278 map.addRequest((PdpUpdate) null);
280 map.addRequest(update);
282 Request req = getSingletons(1).get(0);
283 assertSame(update, req.getMessage());
284 assertEquals("pdp_1 PdpUpdate", req.getName());
286 // broadcast should throw an exception
287 update.setName(null);
288 assertThatIllegalArgumentException().isThrownBy(() -> map.addRequest(update))
289 .withMessageStartingWith("unexpected broadcast message: PdpUpdate");
293 public void testAddRequestPdpStateChange() {
295 map.addRequest((PdpStateChange) null);
297 map.addRequest(change);
299 Request req = getSingletons(1).get(0);
300 assertSame(change, req.getMessage());
301 assertEquals("pdp_1 PdpStateChange", req.getName());
303 // broadcast should throw an exception
304 change.setName(null);
305 assertThatIllegalArgumentException().isThrownBy(() -> map.addRequest(change))
306 .withMessageStartingWith("unexpected broadcast message: PdpStateChange");
310 public void testAddSingleton() {
311 map.addRequest(change);
312 assertEquals(1, map.nalloc);
314 // should have one singleton
317 // add another request with the same PDP
318 map.addRequest(makeStateChange(PDP1, MY_STATE));
319 assertEquals(1, map.nalloc);
321 // should now have another singleton
325 // add another request with a different PDP
326 map.addRequest(makeStateChange(DIFFERENT, MY_STATE));
328 // should now have another allocation
329 assertEquals(2, map.nalloc);
331 // should now have another singleton
336 public void testStartNextRequest_NoMore() {
337 map.addRequest(change);
340 getListener(getSingletons(1).get(0)).success(PDP1, response);
342 verify(responseHandler, never()).handlePdpStatus(response);
345 * the above should have removed the requests so next time should allocate a new
348 map.addRequest(change);
349 assertEquals(2, map.nalloc);
353 public void testStartNextRequest_HaveMore() {
354 map.addRequest(update);
355 map.addRequest(change);
357 Request updateReq = getSingletons(2).get(0);
359 // indicate success with the update
360 when(requests.startNextRequest(updateReq)).thenReturn(true);
361 getListener(updateReq).success(PDP1, response);
363 // should be called for the update
364 verify(responseHandler).handlePdpStatus(response);
366 // should have started the next request
367 verify(requests).startNextRequest(updateReq);
370 * requests should still be there, so adding another request should not allocate a
373 map.addRequest(update);
374 assertEquals(1, map.nalloc);
378 public void testRemoveExpiredPdps() throws Exception {
379 PdpGroup group1 = makeGroup(MY_GROUP);
380 group1.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP1)));
382 PdpGroup group2 = makeGroup(MY_GROUP2);
383 group2.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP2, PDP3), makeSubGroup(MY_SUBGROUP2, PDP4)));
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));
389 when(pdpGroupService.getFilteredPdpGroups(any())).thenReturn(List.of(group1, group2));
392 map.removeExpiredPdps();
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);
400 final Iterator<PdpSubGroup> iter = group2.getPdpSubgroups().iterator();
402 PdpSubGroup subgrp = iter.next();
403 assertThat(subgrp.getPdpInstances()).isEmpty();
404 assertThat(subgrp.getCurrentInstanceCount()).isZero();
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);
413 public void testRemoveExpiredPdps_NothingExpired() throws Exception {
414 PdpGroup group1 = makeGroup(MY_GROUP);
415 group1.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP1)));
417 when(pdpGroupService.getFilteredPdpGroups(any())).thenReturn(List.of(group1));
420 map.removeExpiredPdps();
422 verify(pdpGroupService, never()).updatePdpGroups(any());
423 verify(publisher, never()).enqueue(any());
427 public void testRemoveExpiredPdps_DaoEx() throws Exception {
428 when(pdpGroupService.getFilteredPdpGroups(any())).thenThrow(makeRuntimeException());
430 assertThatCode(map::removeExpiredPdps).doesNotThrowAnyException();
434 public void testRemoveExpiredPdps_DaoRtEx() throws Exception {
435 when(pdpGroupService.getFilteredPdpGroups(any())).thenThrow(makeRuntimeException());
437 assertThatCode(map::removeExpiredPdps).doesNotThrowAnyException();
441 public void testRemoveFromSubgroup() throws Exception {
442 PdpGroup group = makeGroup(MY_GROUP);
443 group.setPdpSubgroups(List.of(makeSubGroup(MY_SUBGROUP, PDP1, PDP2, PDP3)));
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));
453 map.removeExpiredPdps();
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);
462 pdps = group.getPdpSubgroups().get(0).getPdpInstances();
463 assertThat(pdps).hasSize(1);
464 assertThat(pdps.get(0).getInstanceId()).isEqualTo(PDP2);
467 protected PfModelException makeException() {
468 return new PfModelException(Status.BAD_REQUEST, "expected exception");
471 protected PfModelRuntimeException makeRuntimeException() {
472 return new PfModelRuntimeException(Status.BAD_REQUEST, "expected exception");
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);
483 QueueToken<PdpMessage> token = queue.poll();
484 assertNotNull(token);
485 assertSame(change, token.get());
487 verify(dispatcher).register(eq(change.getRequestId()), any());
488 verify(timers).register(eq(change.getRequestId()), any());
492 public void testSingletonListenerFailure() throws Exception {
493 map.addRequest(change);
496 invokeFailureHandler(1);
498 verify(undeployer, never()).undeploy(any(), any(), any());
499 verify(requests, never()).stopPublishing();
501 // requests should have been removed from the map so this should allocate another
502 map.addRequest(update);
503 assertEquals(2, map.nalloc);
507 * Tests Listener.failure() when something has to be undeployed.
510 public void testSingletonListenerFailureUndeploy() throws Exception {
512 ToscaConceptIdentifier ident = new ToscaConceptIdentifier("undeployed", "2.3.4");
513 ToscaPolicy policy = mock(ToscaPolicy.class);
514 when(policy.getIdentifier()).thenReturn(ident);
516 // add some policies to the update
517 update.setPoliciesToBeDeployed(Arrays.asList(policy));
519 map.addRequest(update);
522 * Reconfigure the request when undeploy() is called. Also arrange for undeploy()
523 * to throw an exception.
525 Request req = getSingletons(1).get(0);
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());
535 // indicate that all policies failed (because response has no policies)
536 response.setName(PDP1);
537 req.setNotifier(notifier);
538 req.checkResponse(response);
541 invokeFailureHandler(1);
543 verify(undeployer).undeploy(eq(MY_GROUP), eq(MY_SUBGROUP), undeployCaptor.capture());
544 assertEquals(Arrays.asList(ident).toString(), undeployCaptor.getValue().toString());
546 // no effect on the map
547 map.addRequest(update);
548 assertEquals(1, map.nalloc);
552 * Tests Listener.failure() when something has to be undeployed, but the message
556 public void testSingletonListenerFailureUndeployMessageUnchanged() throws Exception {
558 ToscaConceptIdentifier ident = new ToscaConceptIdentifier("msg-unchanged", "8.7.6");
559 ToscaPolicy policy = mock(ToscaPolicy.class);
560 when(policy.getIdentifier()).thenReturn(ident);
562 // add some policies to the update
563 update.setPoliciesToBeDeployed(Arrays.asList(policy));
565 map.addRequest(update);
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);
574 invokeFailureHandler(1);
576 verify(undeployer).undeploy(eq(MY_GROUP), eq(MY_SUBGROUP), undeployCaptor.capture());
577 assertEquals(Arrays.asList(ident).toString(), undeployCaptor.getValue().toString());
579 // requests should have been removed from the map so this should allocate another
580 map.addRequest(update);
581 assertEquals(2, map.nalloc);
585 public void testSingletonListenerSuccess() throws Exception {
586 map.addRequest(change);
589 invokeSuccessHandler(1);
591 verify(requests, never()).stopPublishing();
593 // requests should have been removed from the map so this should allocate another
594 map.addRequest(update);
595 assertEquals(2, map.nalloc);
599 public void testRequestCompleted_LastRequest() throws Exception {
600 map.addRequest(change);
603 invokeSuccessHandler(1);
605 verify(requests, never()).stopPublishing();
607 // requests should have been removed from the map so this should allocate another
608 map.addRequest(update);
609 assertEquals(2, map.nalloc);
613 public void testRequestCompleted_NameMismatch() throws Exception {
614 // use a different name
615 when(requests.getPdpName()).thenReturn(DIFFERENT);
617 map.addRequest(change);
619 // put the PDP in a group
620 PdpGroup group = makeGroup(MY_GROUP);
621 group.setPdpSubgroups(Arrays.asList(makeSubGroup(MY_SUBGROUP, PDP1, DIFFERENT)));
623 // invoke the method - with a different name (i.e., PDP1 instead of DIFFERENT)
624 invokeSuccessHandler(1);
626 verify(requests, never()).stopPublishing();
628 // no effect on the map
629 map.addRequest(update);
630 assertEquals(1, map.nalloc);
633 verify(pdpGroupService, never()).updatePdpGroups(any());
637 public void testRequestCompleted_AlreadyStopped() throws Exception {
638 map.addRequest(change);
640 map.stopPublishing(PDP1);
643 invokeSuccessHandler(1);
645 // should have called this a second time
646 verify(requests, times(2)).stopPublishing();
648 // requests should have been removed from the map so this should allocate another
649 map.addRequest(update);
650 assertEquals(2, map.nalloc);
654 public void testRequestCompleted_NotFirstInQueue() throws Exception {
655 map.addRequest(change);
657 when(requests.isFirstInQueue(any())).thenReturn(false);
660 invokeSuccessHandler(1);
662 // should not have called this
663 verify(requests, never()).stopPublishing();
665 // no effect on the map
666 map.addRequest(update);
667 assertEquals(1, map.nalloc);
671 public void testSingletonListenerRetryCountExhausted() throws Exception {
672 final var request = map.addRequest(change);
675 invokeLastRetryHandler(1, request);
677 verify(requests).stopPublishing();
682 * Invokes the first request's listener.success() method.
684 * @param count expected number of requests
686 private void invokeSuccessHandler(int count) {
687 getListener(getSingletons(count).get(0)).success(PDP1, response);
691 * Invokes the first request's listener.failure() method.
693 * @param count expected number of requests
695 private void invokeFailureHandler(int count) {
696 getListener(getSingletons(count).get(0)).failure(PDP1, MY_REASON);
700 * Invokes the first request's listener.retryCountExhausted() method.
702 * @param count expected number of requests
703 * @param request request whose count was exhausted
705 private void invokeLastRetryHandler(int count, Request request) {
706 getListener(getSingletons(count).get(0)).retryCountExhausted(request);
710 * Gets the singleton requests added to {@link #requests}.
712 * @param count number of singletons expected
713 * @return the singleton requests
715 private List<Request> getSingletons(int count) {
716 ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
718 verify(requests, times(count)).addSingleton(captor.capture());
719 return captor.getAllValues();
723 * Gets the listener from a request.
725 * @param request request of interest
726 * @return the request's listener
728 private RequestListener getListener(Request request) {
729 return (RequestListener) ReflectionTestUtils.getField(request, "listener");
732 private PdpGroup makeGroup(String name) {
733 PdpGroup group = new PdpGroup();
740 private PdpSubGroup makeSubGroup(String pdpType, String... pdpNames) {
741 PdpSubGroup subgroup = new PdpSubGroup();
743 subgroup.setPdpType(pdpType);
744 subgroup.setCurrentInstanceCount(pdpNames.length);
745 subgroup.setPdpInstances(Arrays.asList(pdpNames).stream().map(this::makePdp).collect(Collectors.toList()));
750 private Pdp makePdp(String pdpName) {
752 pdp.setInstanceId(pdpName);
753 pdp.setLastUpdate(Instant.now());
759 * Gets the input to the method.
761 * @return the input that was passed to the dao.updatePdpGroups() method
762 * @throws Exception if an error occurred
764 private List<PdpGroup> getGroupUpdates() throws Exception {
765 verify(pdpGroupService).updatePdpGroups(updateCaptor.capture());
767 return copyList(updateCaptor.getValue());
771 * Copies a list and sorts it by group name.
773 * @param source source list to copy
774 * @return a copy of the source list
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()));
782 private class MyMap extends PdpModifyRequestMap {
784 * Number of times requests were allocated.
786 private int nalloc = 0;
788 public MyMap(PdpModifyRequestMapParams params) {
789 super(pdpGroupService, policyStatusService, responseHandler, undeployer, notifier);
790 super.initialize(params);;
794 protected PdpRequests makePdpRequests(String pdpName) {