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