Add end-to-end junits for PAP REST API
[policy/pap.git] / main / src / main / java / org / onap / policy / pap / main / comm / PdpModifyRequestMap.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP PAP
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.pap.main.comm;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import org.onap.policy.models.base.PfModelException;
29 import org.onap.policy.models.pdp.concepts.Pdp;
30 import org.onap.policy.models.pdp.concepts.PdpGroup;
31 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;
32 import org.onap.policy.models.pdp.concepts.PdpMessage;
33 import org.onap.policy.models.pdp.concepts.PdpStateChange;
34 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
35 import org.onap.policy.models.pdp.concepts.PdpUpdate;
36 import org.onap.policy.models.pdp.enums.PdpState;
37 import org.onap.policy.models.provider.PolicyModelsProvider;
38 import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
39 import org.onap.policy.pap.main.comm.msgdata.Request;
40 import org.onap.policy.pap.main.comm.msgdata.RequestListener;
41 import org.onap.policy.pap.main.comm.msgdata.StateChangeReq;
42 import org.onap.policy.pap.main.comm.msgdata.UpdateReq;
43 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
44 import org.onap.policy.pap.main.parameters.RequestParams;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * Maps a PDP name to requests that modify PDPs.
50  */
51 public class PdpModifyRequestMap {
52     private static final Logger logger = LoggerFactory.getLogger(PdpModifyRequestMap.class);
53
54     private static final String UNEXPECTED_BROADCAST = "unexpected broadcast message: ";
55
56     /**
57      * Maps a PDP name to its outstanding requests.
58      */
59     private final Map<String, PdpRequests> pdp2requests = new HashMap<>();
60
61     /**
62      * PDP modification lock.
63      */
64     private final Object modifyLock;
65
66     /**
67      * The configuration parameters.
68      */
69     private final PdpModifyRequestMapParams params;
70
71     /**
72      * Factory for PAP DAO.
73      */
74     private final PolicyModelsProviderFactoryWrapper daoFactory;
75
76
77     /**
78      * Constructs the object.
79      *
80      * @param params configuration parameters
81      *
82      * @throws IllegalArgumentException if a required parameter is not set
83      */
84     public PdpModifyRequestMap(PdpModifyRequestMapParams params) {
85         params.validate();
86
87         this.params = params;
88         this.modifyLock = params.getModifyLock();
89         this.daoFactory = params.getDaoFactory();
90     }
91
92     /**
93      * Determines if the map contains any requests.
94      *
95      * @return {@code true} if the map is empty, {@code false} otherwise
96      */
97     public boolean isEmpty() {
98         return pdp2requests.isEmpty();
99     }
100
101     /**
102      * Stops publishing requests to the given PDP.
103      *
104      * @param pdpName PDP name
105      */
106     public void stopPublishing(String pdpName) {
107         synchronized (modifyLock) {
108             PdpRequests requests = pdp2requests.remove(pdpName);
109             if (requests != null) {
110                 requests.stopPublishing();
111             }
112         }
113     }
114
115     /**
116      * Adds a pair of requests to the map.
117      *
118      * @param update the UPDATE request or {@code null}
119      * @param stateChange the STATE-CHANGE request or {@code null}
120      */
121     public void addRequest(PdpUpdate update, PdpStateChange stateChange) {
122         if (update == null) {
123             addRequest(stateChange);
124
125         } else {
126             synchronized (modifyLock) {
127                 addRequest(update);
128                 addRequest(stateChange);
129             }
130         }
131     }
132
133     /**
134      * Adds an UPDATE request to the map.
135      *
136      * @param update the UPDATE request or {@code null}
137      */
138     public void addRequest(PdpUpdate update) {
139         if (update == null) {
140             return;
141         }
142
143         if (isBroadcast(update)) {
144             throw new IllegalArgumentException(UNEXPECTED_BROADCAST + update);
145         }
146
147         // @formatter:off
148         RequestParams reqparams = new RequestParams()
149             .setMaxRetryCount(params.getParams().getUpdateParameters().getMaxRetryCount())
150             .setTimers(params.getUpdateTimers())
151             .setModifyLock(params.getModifyLock())
152             .setPublisher(params.getPublisher())
153             .setResponseDispatcher(params.getResponseDispatcher());
154         // @formatter:on
155
156         String name = update.getName() + " " + PdpUpdate.class.getSimpleName();
157         UpdateReq request = new UpdateReq(reqparams, name, update);
158
159         addSingleton(request);
160     }
161
162     /**
163      * Adds a STATE-CHANGE request to the map.
164      *
165      * @param stateChange the STATE-CHANGE request or {@code null}
166      */
167     public void addRequest(PdpStateChange stateChange) {
168         if (stateChange == null) {
169             return;
170         }
171
172         if (isBroadcast(stateChange)) {
173             throw new IllegalArgumentException(UNEXPECTED_BROADCAST + stateChange);
174         }
175
176         // @formatter:off
177         RequestParams reqparams = new RequestParams()
178             .setMaxRetryCount(params.getParams().getStateChangeParameters().getMaxRetryCount())
179             .setTimers(params.getStateChangeTimers())
180             .setModifyLock(params.getModifyLock())
181             .setPublisher(params.getPublisher())
182             .setResponseDispatcher(params.getResponseDispatcher());
183         // @formatter:on
184
185         String name = stateChange.getName() + " " + PdpStateChange.class.getSimpleName();
186         StateChangeReq request = new StateChangeReq(reqparams, name, stateChange);
187
188         addSingleton(request);
189     }
190
191     /**
192      * Determines if a message is a broadcast message.
193      *
194      * @param message the message to examine
195      * @return {@code true} if the message is a broadcast message, {@code false} if
196      *         destined for a single PDP
197      */
198     private boolean isBroadcast(PdpMessage message) {
199         return (message.getName() == null);
200     }
201
202     /**
203      * Configures and adds a request to the map.
204      *
205      * @param request the request to be added
206      */
207     private void addSingleton(Request request) {
208
209         synchronized (modifyLock) {
210             PdpRequests requests = pdp2requests.computeIfAbsent(request.getMessage().getName(), this::makePdpRequests);
211
212             request.setListener(new SingletonListener(requests, request));
213             requests.addSingleton(request);
214         }
215     }
216
217     /**
218      * Removes a PDP from all active groups.
219      *
220      * @param pdpName name of the PDP to be removed
221      * @return {@code true} if the PDP was removed from a group, {@code false} if it was
222      *         not assigned to a group
223      * @throws PfModelException if an error occurs
224      */
225     public boolean removeFromGroups(String pdpName) throws PfModelException {
226
227         try (PolicyModelsProvider dao = daoFactory.create()) {
228
229             PdpGroupFilter filter = PdpGroupFilter.builder().groupState(PdpState.ACTIVE).build();
230             List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
231             List<PdpGroup> updates = new ArrayList<>(1);
232
233             for (PdpGroup group : groups) {
234                 if (removeFromGroup(pdpName, group)) {
235                     updates.add(group);
236                 }
237             }
238
239             if (updates.isEmpty()) {
240                 return false;
241
242             } else {
243                 dao.updatePdpGroups(updates);
244                 return true;
245             }
246         }
247     }
248
249     /**
250      * Removes a PDP from a group.
251      *
252      * @param pdpName name of the PDP to be removed
253      * @param group group from which it should be removed
254      * @return {@code true} if the PDP was removed from the group, {@code false} if it was
255      *         not assigned to the group
256      */
257     private boolean removeFromGroup(String pdpName, PdpGroup group) {
258         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
259             if (removeFromSubgroup(pdpName, group, subgrp)) {
260                 return true;
261             }
262         }
263
264         return false;
265     }
266
267     /**
268      * Removes a PDP from a subgroup.
269      *
270      * @param pdpName name of the PDP to be removed
271      * @param group group from which to attempt to remove the PDP
272      * @param subgrp subgroup from which to attempt to remove the PDP
273      * @return {@code true} if the PDP was removed, {@code false} if the PDP was not in
274      *         the group
275      */
276     private boolean removeFromSubgroup(String pdpName, PdpGroup group, PdpSubGroup subgrp) {
277
278         Iterator<Pdp> iter = subgrp.getPdpInstances().iterator();
279
280         while (iter.hasNext()) {
281             Pdp instance = iter.next();
282
283             if (pdpName.equals(instance.getInstanceId())) {
284                 logger.info("removed {} from group={} subgroup={}", pdpName, group.getName(), subgrp.getPdpType());
285                 iter.remove();
286                 subgrp.setCurrentInstanceCount(subgrp.getPdpInstances().size());
287                 return true;
288             }
289         }
290
291         return false;
292     }
293
294     /**
295      * Creates a new set of requests for a PDP. May be overridden by junit tests.
296      *
297      * @param pdpName PDP name
298      * @return a new set of requests
299      */
300     protected PdpRequests makePdpRequests(String pdpName) {
301         return new PdpRequests(pdpName);
302     }
303
304     /**
305      * Listener for singleton request events.
306      */
307     private class SingletonListener implements RequestListener {
308         private final PdpRequests requests;
309         private final Request request;
310
311         public SingletonListener(PdpRequests requests, Request request) {
312             this.requests = requests;
313             this.request = request;
314         }
315
316         @Override
317         public void failure(String pdpName, String reason) {
318             if (requests.getPdpName().equals(pdpName)) {
319                 disablePdp(requests);
320             }
321         }
322
323         @Override
324         public void success(String pdpName) {
325             if (requests.getPdpName().equals(pdpName)) {
326                 if (pdp2requests.get(requests.getPdpName()) == requests) {
327                     startNextRequest(requests, request);
328
329                 } else {
330                     logger.info("discard old requests for {}", pdpName);
331                     requests.stopPublishing();
332                 }
333             }
334         }
335
336         @Override
337         public void retryCountExhausted() {
338             disablePdp(requests);
339         }
340
341         /**
342          * Starts the next request associated with a PDP.
343          *
344          * @param requests current set of requests
345          * @param request the request that just completed
346          */
347         private void startNextRequest(PdpRequests requests, Request request) {
348             if (!requests.startNextRequest(request)) {
349                 pdp2requests.remove(requests.getPdpName(), requests);
350             }
351         }
352
353         /**
354          * Disables a PDP by removing it from its subgroup and then sending it a PASSIVE
355          * request.
356          *
357          * @param requests the requests associated with the PDP to be disabled
358          */
359         private void disablePdp(PdpRequests requests) {
360
361             // remove the requests from the map
362             if (!pdp2requests.remove(requests.getPdpName(), requests)) {
363                 // don't have the info we need to disable it
364                 logger.warn("no requests with which to disable {}", requests.getPdpName());
365                 return;
366             }
367
368             logger.warn("disabling {}", requests.getPdpName());
369
370             requests.stopPublishing();
371
372             // remove the PDP from all groups
373             boolean removed = false;
374             try {
375                 removed = removeFromGroups(requests.getPdpName());
376             } catch (PfModelException e) {
377                 logger.info("unable to remove PDP {} from subgroup", requests.getPdpName(), e);
378             }
379
380             // send the state change
381             PdpStateChange change = new PdpStateChange();
382             change.setName(requests.getPdpName());
383             change.setState(PdpState.PASSIVE);
384
385             if (removed) {
386                 // send an update, too
387                 PdpUpdate update = new PdpUpdate();
388                 update.setName(requests.getPdpName());
389
390                 addRequest(update, change);
391
392             } else {
393                 addRequest(change);
394             }
395         }
396     }
397 }