Fix sonars in policy-pap
[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, 2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2021 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.pap.main.comm;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import lombok.Setter;
32 import org.onap.policy.models.base.PfModelException;
33 import org.onap.policy.models.pap.concepts.PolicyNotification;
34 import org.onap.policy.models.pdp.concepts.Pdp;
35 import org.onap.policy.models.pdp.concepts.PdpGroup;
36 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;
37 import org.onap.policy.models.pdp.concepts.PdpMessage;
38 import org.onap.policy.models.pdp.concepts.PdpStateChange;
39 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
40 import org.onap.policy.models.pdp.concepts.PdpUpdate;
41 import org.onap.policy.models.pdp.enums.PdpState;
42 import org.onap.policy.models.provider.PolicyModelsProvider;
43 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
44 import org.onap.policy.pap.main.PolicyModelsProviderFactoryWrapper;
45 import org.onap.policy.pap.main.comm.msgdata.Request;
46 import org.onap.policy.pap.main.comm.msgdata.RequestListener;
47 import org.onap.policy.pap.main.comm.msgdata.StateChangeReq;
48 import org.onap.policy.pap.main.comm.msgdata.UpdateReq;
49 import org.onap.policy.pap.main.notification.DeploymentStatus;
50 import org.onap.policy.pap.main.notification.PolicyNotifier;
51 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
52 import org.onap.policy.pap.main.parameters.RequestParams;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * Maps a PDP name to requests that modify PDPs.
58  */
59 public class PdpModifyRequestMap {
60     private static final Logger logger = LoggerFactory.getLogger(PdpModifyRequestMap.class);
61
62     private static final String UNEXPECTED_BROADCAST = "unexpected broadcast message: ";
63
64     /**
65      * Maps a PDP name to its outstanding requests.
66      */
67     private final Map<String, PdpRequests> pdp2requests = new HashMap<>();
68
69     /**
70      * PDP modification lock.
71      */
72     private final Object modifyLock;
73
74     /**
75      * The configuration parameters.
76      */
77     private final PdpModifyRequestMapParams params;
78
79     /**
80      * Factory for PAP DAO.
81      */
82     private final PolicyModelsProviderFactoryWrapper daoFactory;
83
84     /**
85      * Used to notify when policy updates completes.
86      */
87     private final PolicyNotifier policyNotifier;
88
89     /**
90      * Used to undeploy policies from the system, when they cannot be deployed to a PDP.
91      *
92      * <p/>
93      * Note: there's a "catch-22" here. The request map needs an undeployer, but the
94      * undeployer needs the request map. Consequently, the request map is created first,
95      * then the undeployer, and finally, this field is set.
96      */
97     @Setter
98     private PolicyUndeployer policyUndeployer;
99
100
101     /**
102      * Constructs the object.
103      *
104      * @param params configuration parameters
105      *
106      * @throws IllegalArgumentException if a required parameter is not set
107      */
108     public PdpModifyRequestMap(PdpModifyRequestMapParams params) {
109         params.validate();
110
111         this.params = params;
112         this.modifyLock = params.getModifyLock();
113         this.daoFactory = params.getDaoFactory();
114         this.policyNotifier = params.getPolicyNotifier();
115     }
116
117     /**
118      * Determines if the map contains any requests.
119      *
120      * @return {@code true} if the map is empty, {@code false} otherwise
121      */
122     public boolean isEmpty() {
123         return pdp2requests.isEmpty();
124     }
125
126     /**
127      * Stops publishing requests to the given PDP.
128      *
129      * @param pdpName PDP name
130      */
131     public void stopPublishing(String pdpName) {
132         synchronized (modifyLock) {
133             PdpRequests requests = pdp2requests.remove(pdpName);
134             if (requests != null) {
135                 requests.stopPublishing();
136             }
137         }
138     }
139
140     /**
141      * Adds a pair of requests to the map.
142      *
143      * @param update the UPDATE request or {@code null}
144      * @param stateChange the STATE-CHANGE request or {@code null}
145      */
146     public void addRequest(PdpUpdate update, PdpStateChange stateChange) {
147         if (update == null) {
148             addRequest(stateChange);
149
150         } else if (stateChange == null) {
151             addRequest(update);
152
153         } else if (stateChange.getState() == PdpState.ACTIVE) {
154             // publish update before activating
155             synchronized (modifyLock) {
156                 addRequest(update);
157                 addRequest(stateChange);
158             }
159
160         } else {
161             // deactivate before publishing update
162             synchronized (modifyLock) {
163                 addRequest(stateChange);
164                 addRequest(update);
165             }
166         }
167     }
168
169     /**
170      * Adds an UPDATE request to the map.
171      *
172      * @param update the UPDATE request or {@code null}
173      */
174     public void addRequest(PdpUpdate update) {
175         if (update == null) {
176             return;
177         }
178
179         if (isBroadcast(update)) {
180             throw new IllegalArgumentException(UNEXPECTED_BROADCAST + update);
181         }
182
183         // @formatter:off
184         RequestParams reqparams = new RequestParams()
185             .setMaxRetryCount(params.getParams().getUpdateParameters().getMaxRetryCount())
186             .setTimers(params.getUpdateTimers())
187             .setModifyLock(params.getModifyLock())
188             .setPdpPublisher(params.getPdpPublisher())
189             .setResponseDispatcher(params.getResponseDispatcher());
190         // @formatter:on
191
192         String name = update.getName() + " " + PdpUpdate.class.getSimpleName();
193         var request = new UpdateReq(reqparams, name, update);
194
195         addSingleton(request);
196     }
197
198     /**
199      * Adds a STATE-CHANGE request to the map.
200      *
201      * @param stateChange the STATE-CHANGE request or {@code null}
202      */
203     public void addRequest(PdpStateChange stateChange) {
204         if (stateChange == null) {
205             return;
206         }
207
208         if (isBroadcast(stateChange)) {
209             throw new IllegalArgumentException(UNEXPECTED_BROADCAST + stateChange);
210         }
211
212         // @formatter:off
213         RequestParams reqparams = new RequestParams()
214             .setMaxRetryCount(params.getParams().getStateChangeParameters().getMaxRetryCount())
215             .setTimers(params.getStateChangeTimers())
216             .setModifyLock(params.getModifyLock())
217             .setPdpPublisher(params.getPdpPublisher())
218             .setResponseDispatcher(params.getResponseDispatcher());
219         // @formatter:on
220
221         String name = stateChange.getName() + " " + PdpStateChange.class.getSimpleName();
222         var request = new StateChangeReq(reqparams, name, stateChange);
223
224         addSingleton(request);
225     }
226
227     /**
228      * Determines if a message is a broadcast message.
229      *
230      * @param message the message to examine
231      * @return {@code true} if the message is a broadcast message, {@code false} if
232      *         destined for a single PDP
233      */
234     private boolean isBroadcast(PdpMessage message) {
235         return (message.getName() == null);
236     }
237
238     /**
239      * Configures and adds a request to the map.
240      *
241      * @param request the request to be added
242      */
243     private void addSingleton(Request request) {
244
245         synchronized (modifyLock) {
246             PdpRequests requests = pdp2requests.computeIfAbsent(request.getMessage().getName(), this::makePdpRequests);
247
248             request.setListener(new SingletonListener(requests, request));
249             requests.addSingleton(request);
250         }
251     }
252
253     /**
254      * Removes a PDP from all active groups.
255      *
256      * @param pdpName name of the PDP to be removed
257      * @return {@code true} if the PDP was removed from a group, {@code false} if it was
258      *         not assigned to a group
259      * @throws PfModelException if an error occurred
260      */
261     public boolean removeFromGroups(String pdpName) throws PfModelException {
262
263         try (PolicyModelsProvider dao = daoFactory.create()) {
264
265             PdpGroupFilter filter = PdpGroupFilter.builder().groupState(PdpState.ACTIVE).build();
266             List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
267             List<PdpGroup> updates = new ArrayList<>(1);
268
269             var status = new DeploymentStatus(dao);
270
271             for (PdpGroup group : groups) {
272                 if (removeFromGroup(pdpName, group)) {
273                     updates.add(group);
274                     status.loadByGroup(group.getName());
275                     status.deleteDeployment(pdpName);
276                 }
277             }
278
279             if (updates.isEmpty()) {
280                 return false;
281
282             } else {
283                 dao.updatePdpGroups(updates);
284
285                 var notification = new PolicyNotification();
286                 status.flush(notification);
287
288                 policyNotifier.publish(notification);
289
290                 return true;
291             }
292         }
293     }
294
295     /**
296      * Removes a PDP from a group.
297      *
298      * @param pdpName name of the PDP to be removed
299      * @param group group from which it should be removed
300      * @return {@code true} if the PDP was removed from the group, {@code false} if it was
301      *         not assigned to the group
302      */
303     private boolean removeFromGroup(String pdpName, PdpGroup group) {
304         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
305             if (removeFromSubgroup(pdpName, group, subgrp)) {
306                 return true;
307             }
308         }
309
310         return false;
311     }
312
313     /**
314      * Removes a PDP from a subgroup.
315      *
316      * @param pdpName name of the PDP to be removed
317      * @param group group from which to attempt to remove the PDP
318      * @param subgrp subgroup from which to attempt to remove the PDP
319      * @return {@code true} if the PDP was removed, {@code false} if the PDP was not in
320      *         the group
321      */
322     private boolean removeFromSubgroup(String pdpName, PdpGroup group, PdpSubGroup subgrp) {
323
324         Iterator<Pdp> iter = subgrp.getPdpInstances().iterator();
325
326         while (iter.hasNext()) {
327             Pdp instance = iter.next();
328
329             if (pdpName.equals(instance.getInstanceId())) {
330                 logger.info("removed {} from group={} subgroup={}", pdpName, group.getName(), subgrp.getPdpType());
331                 iter.remove();
332                 subgrp.setCurrentInstanceCount(subgrp.getPdpInstances().size());
333                 return true;
334             }
335         }
336
337         return false;
338     }
339
340     /**
341      * Creates a new set of requests for a PDP. May be overridden by junit tests.
342      *
343      * @param pdpName PDP name
344      * @return a new set of requests
345      */
346     protected PdpRequests makePdpRequests(String pdpName) {
347         return new PdpRequests(pdpName, policyNotifier);
348     }
349
350     /**
351      * Listener for singleton request events.
352      */
353     private class SingletonListener implements RequestListener {
354         private final PdpRequests requests;
355         private final Request request;
356         private final String pdpName;
357
358         public SingletonListener(PdpRequests requests, Request request) {
359             this.requests = requests;
360             this.request = request;
361             this.pdpName = requests.getPdpName();
362         }
363
364         @Override
365         public void failure(String responsePdpName, String reason) {
366             Collection<ToscaConceptIdentifier> undeployPolicies = requestCompleted(responsePdpName);
367             if (undeployPolicies.isEmpty()) {
368                 // nothing to undeploy
369                 return;
370             }
371
372             /*
373              * Undeploy the extra policies. Note: this will likely cause a new message to
374              * be assigned to the request, thus we must re-start it after making the
375              * change.
376              */
377             PdpMessage oldmsg = request.getMessage();
378
379             try {
380                 logger.warn("undeploy policies from {}:{} that failed to deploy: {}", oldmsg.getPdpGroup(),
381                                 oldmsg.getPdpSubgroup(), undeployPolicies);
382                 policyUndeployer.undeploy(oldmsg.getPdpGroup(), oldmsg.getPdpSubgroup(), undeployPolicies);
383             } catch (PfModelException | RuntimeException e) {
384                 logger.error("cannot undeploy policies {}", undeployPolicies, e);
385             }
386
387             if (request.getMessage() == oldmsg) {
388                 // message is unchanged - start the next request
389                 startNextRequest(request);
390             } else {
391                 // message changed - restart the request
392                 request.startPublishing();
393             }
394         }
395
396         @Override
397         public void success(String responsePdpName) {
398             requestCompleted(responsePdpName);
399         }
400
401         /**
402          * Handles a request completion, starting the next request, if there is one.
403          *
404          * @param responsePdpName name of the PDP provided in the response
405          * @return a list of policies to be undeployed
406          */
407         private Collection<ToscaConceptIdentifier> requestCompleted(String responsePdpName) {
408             if (!pdpName.equals(responsePdpName)) {
409                 return Collections.emptyList();
410             }
411
412             if (pdp2requests.get(pdpName) != requests) {
413                 logger.info("discard old requests for {}", responsePdpName);
414                 requests.stopPublishing();
415                 return Collections.emptyList();
416             }
417
418             if (!requests.isFirstInQueue(request)) {
419                 logger.error("request is not first in the queue {}", request.getMessage());
420                 return Collections.emptyList();
421             }
422
423             Collection<ToscaConceptIdentifier> undeployPolicies = request.getUndeployPolicies();
424             if (undeployPolicies.isEmpty()) {
425                 // nothing to undeploy - just start the next request
426                 startNextRequest(request);
427             }
428
429             return undeployPolicies;
430         }
431
432         @Override
433         public void retryCountExhausted() {
434             removePdp();
435         }
436
437         /**
438          * Starts the next request associated with a PDP.
439          *
440          * @param request the request that just completed
441          */
442         private void startNextRequest(Request request) {
443             if (!requests.startNextRequest(request)) {
444                 pdp2requests.remove(pdpName, requests);
445             }
446         }
447
448         /**
449          * Removes a PDP from its subgroup.
450          */
451         private void removePdp() {
452             requests.stopPublishing();
453
454             // remove the requests from the map
455             if (!pdp2requests.remove(pdpName, requests)) {
456                 // wasn't in the map - the requests must be old
457                 logger.warn("discarding old requests for {}", pdpName);
458                 return;
459             }
460
461             logger.warn("removing {}", pdpName);
462
463             // remove the PDP from all groups
464             try {
465                 removeFromGroups(pdpName);
466             } catch (PfModelException e) {
467                 logger.info("unable to remove PDP {} from subgroup", pdpName, e);
468             }
469         }
470     }
471 }