1635c16cef7eda73adbce5698701b0acb672bbca
[policy/models.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2024 Nordix Foundation
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.controlloop.actorserviceprovider.impl;
23
24 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
25 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
26 import static org.junit.jupiter.api.Assertions.assertEquals;
27 import static org.junit.jupiter.api.Assertions.assertFalse;
28 import static org.junit.jupiter.api.Assertions.assertSame;
29 import static org.junit.jupiter.api.Assertions.assertTrue;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.Mockito.doThrow;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.spy;
34 import static org.mockito.Mockito.times;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37
38 import java.util.Iterator;
39 import java.util.Map;
40 import java.util.stream.Collectors;
41 import org.junit.jupiter.api.BeforeEach;
42 import org.junit.jupiter.api.Test;
43 import org.onap.policy.common.parameters.ObjectValidationResult;
44 import org.onap.policy.common.parameters.ValidationStatus;
45 import org.onap.policy.controlloop.actorserviceprovider.Operation;
46 import org.onap.policy.controlloop.actorserviceprovider.Operator;
47 import org.onap.policy.controlloop.actorserviceprovider.parameters.ActorParams;
48 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
49 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
50
51 class ActorImplTest {
52     private static final String EXPECTED_EXCEPTION = "expected exception";
53     private static final String ACTOR_NAME = "my-actor";
54     private static final String OPER1 = "add";
55     private static final String OPER2 = "subtract";
56     private static final String OPER3 = "multiply";
57     private static final String OPER4 = "divide";
58
59     private MyOper oper1;
60     private MyOper oper2;
61     private MyOper oper3;
62     private MyOper oper4;
63
64     private Map<String, Object> sub1;
65     private Map<String, Object> sub2;
66     private Map<String, Object> sub3;
67     private Map<String, Object> sub4;
68     private Map<String, Object> params;
69
70     private ActorImpl actor;
71
72
73     /**
74      * Initializes the fields, including a fully populated {@link #actor}.
75      */
76     @BeforeEach
77    void setUp() {
78         oper1 = spy(new MyOper(OPER1));
79         oper2 = spy(new MyOper(OPER2));
80         oper3 = spy(new MyOper(OPER3));
81         oper4 = spy(new MyOper(OPER4));
82
83         sub1 = Map.of("sub A", "value A");
84         sub2 = Map.of("sub B", "value B");
85         sub3 = Map.of("sub C", "value C");
86         sub4 = Map.of("sub D", "value D");
87
88         params = Map.of(ActorParams.OPERATIONS_FIELD, Map.of(OPER1, sub1, OPER2, sub2, OPER3, sub3, OPER4, sub4));
89
90         actor = makeActor(oper1, oper2, oper3, oper4);
91     }
92
93     @Test
94    void testActorImpl_testGetName() {
95         assertEquals(ACTOR_NAME, actor.getName());
96         assertEquals(4, actor.getOperationNames().size());
97         assertEquals(0, actor.getSequenceNumber());
98     }
99
100     @Test
101    void testDoStart() {
102         actor.configure(params);
103         assertEquals(4, actor.getOperationNames().size());
104
105         /*
106          * arrange for second operator to be unconfigured and the third operator to throw
107          * an exception
108          */
109         Iterator<Operator> iter = actor.getOperators().iterator();
110         iter.next();
111         when(iter.next().isConfigured()).thenReturn(false);
112         when(iter.next().start()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
113
114         /*
115          * Start the actor.
116          */
117         actor.start();
118         assertTrue(actor.isAlive());
119
120         iter = actor.getOperators().iterator();
121         verify(iter.next()).start();
122         // this one isn't configured, so shouldn't attempt to start it
123         verify(iter.next(), never()).start();
124         // this one threw an exception
125         iter.next();
126         verify(iter.next()).start();
127
128         // no other types of operations
129         verify(oper1, never()).stop();
130         verify(oper1, never()).shutdown();
131     }
132
133     @Test
134    void testDoStop() {
135         actor.configure(params);
136         actor.start();
137         assertEquals(4, actor.getOperationNames().size());
138
139         // arrange for second operator to throw an exception
140         Iterator<Operator> iter = actor.getOperators().iterator();
141         iter.next();
142         when(iter.next().stop()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
143
144         /*
145          * Stop the actor.
146          */
147         actor.stop();
148         assertFalse(actor.isAlive());
149
150         iter = actor.getOperators().iterator();
151         verify(iter.next()).stop();
152         // this one threw an exception
153         iter.next();
154         verify(iter.next()).stop();
155         verify(iter.next()).stop();
156
157         // no additional types of operations
158         verify(oper1).configure(any());
159         verify(oper1).start();
160
161         // no other types of operation
162         verify(oper1, never()).shutdown();
163     }
164
165     @Test
166    void testDoShutdown() {
167         actor.configure(params);
168         actor.start();
169         assertEquals(4, actor.getOperationNames().size());
170
171         // arrange for second operator to throw an exception
172         Iterator<Operator> iter = actor.getOperators().iterator();
173         iter.next();
174         doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(iter.next()).shutdown();
175
176         /*
177          * Stop the actor.
178          */
179         actor.shutdown();
180         assertFalse(actor.isAlive());
181
182         iter = actor.getOperators().iterator();
183         verify(iter.next()).shutdown();
184         // this one threw an exception
185         iter.next();
186         verify(iter.next()).shutdown();
187         verify(iter.next()).shutdown();
188
189         // no additional types of operations
190         verify(oper1).configure(any());
191         verify(oper1).start();
192
193         // no other types of operation
194         verify(oper1, never()).stop();
195     }
196
197     @Test
198     void testAddOperator() {
199         // cannot add operators if already configured
200         actor.configure(params);
201         assertThatIllegalStateException().isThrownBy(() -> actor.addOperator(oper1));
202
203         /*
204          * make an actor where operators two and four have names that are duplicates of
205          * the others
206          */
207         oper2 = spy(new MyOper(OPER1));
208         oper4 = spy(new MyOper(OPER3));
209
210         actor = makeActor(oper1, oper2, oper3, oper4);
211
212         assertEquals(2, actor.getOperationNames().size());
213
214         assertSame(oper1, actor.getOperator(OPER1));
215         assertSame(oper3, actor.getOperator(OPER3));
216     }
217
218     @Test
219     void testGetOperator() {
220         assertSame(oper1, actor.getOperator(OPER1));
221         assertSame(oper3, actor.getOperator(OPER3));
222
223         assertThatIllegalArgumentException().isThrownBy(() -> actor.getOperator("unknown name"));
224     }
225
226     @Test
227     void testGetOperators() {
228         // @formatter:off
229         assertEquals("[add, divide, multiply, subtract]",
230                         actor.getOperators().stream()
231                             .map(Operator::getName)
232                             .sorted()
233                             .collect(Collectors.toList())
234                             .toString());
235         // @formatter:on
236     }
237
238     @Test
239     void testGetOperationNames() {
240         // @formatter:off
241         assertEquals("[add, divide, multiply, subtract]",
242                         actor.getOperationNames().stream()
243                             .sorted()
244                             .collect(Collectors.toList())
245                             .toString());
246         // @formatter:on
247     }
248
249     @Test
250     void testDoConfigure() {
251         actor.configure(params);
252         assertTrue(actor.isConfigured());
253
254         verify(oper1).configure(sub1);
255         verify(oper2).configure(sub2);
256         verify(oper3).configure(sub3);
257         verify(oper4).configure(sub4);
258
259         // no other types of operations
260         verify(oper1, never()).start();
261         verify(oper1, never()).stop();
262         verify(oper1, never()).shutdown();
263     }
264
265     /**
266      * Tests doConfigure() where operators throw parameter validation and runtime
267      * exceptions.
268      */
269     @Test
270     void testDoConfigureExceptions() {
271         makeValidException(oper1);
272         makeRuntimeException(oper2);
273         makeValidException(oper3);
274
275         actor.configure(params);
276         assertTrue(actor.isConfigured());
277     }
278
279     /**
280      * Tests doConfigure(). Arranges for the following:
281      * <ul>
282      * <li>one operator is configured, but has parameters</li>
283      * <li>another operator is configured, but has no parameters</li>
284      * <li>another operator has no parameters and is not configured</li>
285      * </ul>
286      */
287     @Test
288     void testDoConfigureConfigure() {
289         // configure one operator
290         oper1.configure(sub1);
291
292         // configure another and remove its parameters
293         oper2.configure(sub2);
294         params = Map.of(ActorParams.OPERATIONS_FIELD, Map.of(OPER1, sub1, OPER3, sub3, OPER4, sub4));
295
296         // create a new, unconfigured actor
297         Operator oper5 = spy(new MyOper("UNCONFIGURED"));
298         actor = makeActor(oper1, oper2, oper3, oper4, oper5);
299
300         /*
301          * Configure it.
302          */
303         actor.configure(params);
304         assertTrue(actor.isConfigured());
305
306         // this should have been configured again
307         verify(oper1, times(2)).configure(sub1);
308
309         // no parameters, so this should not have been configured again
310         verify(oper2).configure(sub2);
311
312         // these were only configured once
313         verify(oper3).configure(sub3);
314         verify(oper4).configure(sub4);
315
316         // never configured
317         verify(oper5, never()).configure(any());
318         assertFalse(oper5.isConfigured());
319
320         // start and verify that all are started except for the last
321         actor.start();
322         verify(oper1).start();
323         verify(oper2).start();
324         verify(oper3).start();
325         verify(oper4).start();
326         verify(oper5, never()).start();
327     }
328
329     /**
330      * Arranges for an operator to throw a validation exception when
331      * {@link Operator#configure(Map)} is invoked.
332      *
333      * @param oper operator of interest
334      */
335     private void makeValidException(Operator oper) {
336         ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(
337                         new ObjectValidationResult(actor.getName(), null, ValidationStatus.INVALID, "null"));
338         doThrow(ex).when(oper).configure(any());
339     }
340
341     /**
342      * Arranges for an operator to throw a runtime exception when
343      * {@link Operator#configure(Map)} is invoked.
344      *
345      * @param oper operator of interest
346      */
347     private void makeRuntimeException(Operator oper) {
348         IllegalStateException ex = new IllegalStateException(EXPECTED_EXCEPTION);
349         doThrow(ex).when(oper).configure(any());
350     }
351
352     @Test
353     void testMakeOperatorParameters() {
354         actor.configure(params);
355
356         // each operator should have received its own parameters
357         verify(oper1).configure(sub1);
358         verify(oper2).configure(sub2);
359         verify(oper3).configure(sub3);
360         verify(oper4).configure(sub4);
361     }
362
363     /**
364      * Makes an actor with the given operators.
365      *
366      * @param operators associated operators
367      * @return a new actor
368      */
369     private ActorImpl makeActor(Operator... operators) {
370         ActorImpl actor = new ActorImpl(ACTOR_NAME);
371
372         for (Operator oper : operators) {
373             actor.addOperator(oper);
374         }
375
376         return actor;
377     }
378
379     private static class MyOper extends OperatorPartial {
380
381         public MyOper(String name) {
382             super(ACTOR_NAME, name);
383         }
384
385         @Override
386         public Operation buildOperation(ControlLoopOperationParams params) {
387             return null;
388         }
389     }
390 }