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