PAP should not deactivate PDPs
[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.notification.PolicyNotifier;
44 import org.onap.policy.pap.main.parameters.PdpModifyRequestMapParams;
45 import org.onap.policy.pap.main.parameters.RequestParams;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * Maps a PDP name to requests that modify PDPs.
51  */
52 public class PdpModifyRequestMap {
53     private static final Logger logger = LoggerFactory.getLogger(PdpModifyRequestMap.class);
54
55     private static final String UNEXPECTED_BROADCAST = "unexpected broadcast message: ";
56
57     /**
58      * Maps a PDP name to its outstanding requests.
59      */
60     private final Map<String, PdpRequests> pdp2requests = new HashMap<>();
61
62     /**
63      * PDP modification lock.
64      */
65     private final Object modifyLock;
66
67     /**
68      * The configuration parameters.
69      */
70     private final PdpModifyRequestMapParams params;
71
72     /**
73      * Factory for PAP DAO.
74      */
75     private final PolicyModelsProviderFactoryWrapper daoFactory;
76
77     /**
78      * Used to notify when policy updates completes.
79      */
80     private final PolicyNotifier policyNotifier;
81
82
83     /**
84      * Constructs the object.
85      *
86      * @param params configuration parameters
87      *
88      * @throws IllegalArgumentException if a required parameter is not set
89      */
90     public PdpModifyRequestMap(PdpModifyRequestMapParams params) {
91         params.validate();
92
93         this.params = params;
94         this.modifyLock = params.getModifyLock();
95         this.daoFactory = params.getDaoFactory();
96         this.policyNotifier = params.getPolicyNotifier();
97     }
98
99     /**
100      * Determines if the map contains any requests.
101      *
102      * @return {@code true} if the map is empty, {@code false} otherwise
103      */
104     public boolean isEmpty() {
105         return pdp2requests.isEmpty();
106     }
107
108     /**
109      * Stops publishing requests to the given PDP.
110      *
111      * @param pdpName PDP name
112      */
113     public void stopPublishing(String pdpName) {
114         synchronized (modifyLock) {
115             PdpRequests requests = pdp2requests.remove(pdpName);
116             if (requests != null) {
117                 requests.stopPublishing();
118             }
119         }
120     }
121
122     /**
123      * Adds a pair of requests to the map.
124      *
125      * @param update the UPDATE request or {@code null}
126      * @param stateChange the STATE-CHANGE request or {@code null}
127      */
128     public void addRequest(PdpUpdate update, PdpStateChange stateChange) {
129         if (update == null) {
130             addRequest(stateChange);
131
132         } else if (stateChange == null) {
133             addRequest(update);
134
135         } else if (stateChange.getState() == PdpState.ACTIVE) {
136             // publish update before activating
137             synchronized (modifyLock) {
138                 addRequest(update);
139                 addRequest(stateChange);
140             }
141
142         } else {
143             // deactivate before publishing update
144             synchronized (modifyLock) {
145                 addRequest(stateChange);
146                 addRequest(update);
147             }
148         }
149     }
150
151     /**
152      * Adds an UPDATE request to the map.
153      *
154      * @param update the UPDATE request or {@code null}
155      */
156     public void addRequest(PdpUpdate update) {
157         if (update == null) {
158             return;
159         }
160
161         if (isBroadcast(update)) {
162             throw new IllegalArgumentException(UNEXPECTED_BROADCAST + update);
163         }
164
165         // @formatter:off
166         RequestParams reqparams = new RequestParams()
167             .setMaxRetryCount(params.getParams().getUpdateParameters().getMaxRetryCount())
168             .setTimers(params.getUpdateTimers())
169             .setModifyLock(params.getModifyLock())
170             .setPdpPublisher(params.getPdpPublisher())
171             .setResponseDispatcher(params.getResponseDispatcher());
172         // @formatter:on
173
174         String name = update.getName() + " " + PdpUpdate.class.getSimpleName();
175         UpdateReq request = new UpdateReq(reqparams, name, update);
176
177         addSingleton(request);
178     }
179
180     /**
181      * Adds a STATE-CHANGE request to the map.
182      *
183      * @param stateChange the STATE-CHANGE request or {@code null}
184      */
185     public void addRequest(PdpStateChange stateChange) {
186         if (stateChange == null) {
187             return;
188         }
189
190         if (isBroadcast(stateChange)) {
191             throw new IllegalArgumentException(UNEXPECTED_BROADCAST + stateChange);
192         }
193
194         // @formatter:off
195         RequestParams reqparams = new RequestParams()
196             .setMaxRetryCount(params.getParams().getStateChangeParameters().getMaxRetryCount())
197             .setTimers(params.getStateChangeTimers())
198             .setModifyLock(params.getModifyLock())
199             .setPdpPublisher(params.getPdpPublisher())
200             .setResponseDispatcher(params.getResponseDispatcher());
201         // @formatter:on
202
203         String name = stateChange.getName() + " " + PdpStateChange.class.getSimpleName();
204         StateChangeReq request = new StateChangeReq(reqparams, name, stateChange);
205
206         addSingleton(request);
207     }
208
209     /**
210      * Determines if a message is a broadcast message.
211      *
212      * @param message the message to examine
213      * @return {@code true} if the message is a broadcast message, {@code false} if
214      *         destined for a single PDP
215      */
216     private boolean isBroadcast(PdpMessage message) {
217         return (message.getName() == null);
218     }
219
220     /**
221      * Configures and adds a request to the map.
222      *
223      * @param request the request to be added
224      */
225     private void addSingleton(Request request) {
226
227         synchronized (modifyLock) {
228             PdpRequests requests = pdp2requests.computeIfAbsent(request.getMessage().getName(), this::makePdpRequests);
229
230             request.setListener(new SingletonListener(requests, request));
231             requests.addSingleton(request);
232         }
233     }
234
235     /**
236      * Removes a PDP from all active groups.
237      *
238      * @param pdpName name of the PDP to be removed
239      * @return {@code true} if the PDP was removed from a group, {@code false} if it was
240      *         not assigned to a group
241      * @throws PfModelException if an error occurred
242      */
243     public boolean removeFromGroups(String pdpName) throws PfModelException {
244
245         try (PolicyModelsProvider dao = daoFactory.create()) {
246
247             PdpGroupFilter filter = PdpGroupFilter.builder().groupState(PdpState.ACTIVE).build();
248             List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
249             List<PdpGroup> updates = new ArrayList<>(1);
250
251             for (PdpGroup group : groups) {
252                 if (removeFromGroup(pdpName, group)) {
253                     updates.add(group);
254                 }
255             }
256
257             if (updates.isEmpty()) {
258                 return false;
259
260             } else {
261                 dao.updatePdpGroups(updates);
262                 return true;
263             }
264         }
265     }
266
267     /**
268      * Removes a PDP from a group.
269      *
270      * @param pdpName name of the PDP to be removed
271      * @param group group from which it should be removed
272      * @return {@code true} if the PDP was removed from the group, {@code false} if it was
273      *         not assigned to the group
274      */
275     private boolean removeFromGroup(String pdpName, PdpGroup group) {
276         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
277             if (removeFromSubgroup(pdpName, group, subgrp)) {
278                 return true;
279             }
280         }
281
282         return false;
283     }
284
285     /**
286      * Removes a PDP from a subgroup.
287      *
288      * @param pdpName name of the PDP to be removed
289      * @param group group from which to attempt to remove the PDP
290      * @param subgrp subgroup from which to attempt to remove the PDP
291      * @return {@code true} if the PDP was removed, {@code false} if the PDP was not in
292      *         the group
293      */
294     private boolean removeFromSubgroup(String pdpName, PdpGroup group, PdpSubGroup subgrp) {
295
296         Iterator<Pdp> iter = subgrp.getPdpInstances().iterator();
297
298         while (iter.hasNext()) {
299             Pdp instance = iter.next();
300
301             if (pdpName.equals(instance.getInstanceId())) {
302                 logger.info("removed {} from group={} subgroup={}", pdpName, group.getName(), subgrp.getPdpType());
303                 iter.remove();
304                 subgrp.setCurrentInstanceCount(subgrp.getPdpInstances().size());
305                 return true;
306             }
307         }
308
309         return false;
310     }
311
312     /**
313      * Creates a new set of requests for a PDP. May be overridden by junit tests.
314      *
315      * @param pdpName PDP name
316      * @return a new set of requests
317      */
318     protected PdpRequests makePdpRequests(String pdpName) {
319         return new PdpRequests(pdpName, policyNotifier);
320     }
321
322     /**
323      * Listener for singleton request events.
324      */
325     private class SingletonListener implements RequestListener {
326         private final PdpRequests requests;
327         private final Request request;
328         private final String pdpName;
329
330         public SingletonListener(PdpRequests requests, Request request) {
331             this.requests = requests;
332             this.request = request;
333             this.pdpName = requests.getPdpName();
334         }
335
336         @Override
337         public void failure(String responsePdpName, String reason) {
338             requestCompleted(responsePdpName);
339         }
340
341         @Override
342         public void success(String responsePdpName) {
343             requestCompleted(responsePdpName);
344         }
345
346         /**
347          * Handles a request completion, starting the next request, if there is one.
348          *
349          * @param responsePdpName name of the PDP provided in the response
350          */
351         private void requestCompleted(String responsePdpName) {
352             if (pdpName.equals(responsePdpName)) {
353                 if (pdp2requests.get(pdpName) == requests) {
354                     startNextRequest(request);
355
356                 } else {
357                     logger.info("discard old requests for {}", responsePdpName);
358                     requests.stopPublishing();
359                 }
360             }
361         }
362
363         @Override
364         public void retryCountExhausted() {
365             removePdp();
366         }
367
368         /**
369          * Starts the next request associated with a PDP.
370          *
371          * @param request the request that just completed
372          */
373         private void startNextRequest(Request request) {
374             if (!requests.startNextRequest(request)) {
375                 pdp2requests.remove(pdpName, requests);
376             }
377         }
378
379         /**
380          * Removes a PDP from its subgroup.
381          */
382         private void removePdp() {
383             requests.stopPublishing();
384
385             // remove the requests from the map
386             if (!pdp2requests.remove(pdpName, requests)) {
387                 // wasn't in the map - the requests must be old
388                 logger.warn("discarding old requests for {}", pdpName);
389                 return;
390             }
391
392             logger.warn("removing {}", pdpName);
393
394             policyNotifier.removePdp(pdpName);
395
396             // remove the PDP from all groups
397             try {
398                 removeFromGroups(pdpName);
399             } catch (PfModelException e) {
400                 logger.info("unable to remove PDP {} from subgroup", pdpName, e);
401             }
402         }
403     }
404 }