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