Generate notifications when policies change
[policy/pap.git] / main / src / main / java / org / onap / policy / pap / main / rest / depundep / SessionData.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.rest.depundep;
22
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.regex.Pattern;
31 import java.util.stream.Collectors;
32 import org.apache.commons.lang3.tuple.Pair;
33 import org.onap.policy.models.base.PfModelException;
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.PdpStateChange;
37 import org.onap.policy.models.pdp.concepts.PdpUpdate;
38 import org.onap.policy.models.pdp.enums.PdpState;
39 import org.onap.policy.models.provider.PolicyModelsProvider;
40 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
41 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter;
42 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter.ToscaPolicyFilterBuilder;
43 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
44 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
45 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
46 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
47 import org.onap.policy.pap.main.notification.PolicyPdpNotificationData;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * Data used during a single REST call when updating PDP policies.
53  */
54 public class SessionData {
55     private static final Logger logger = LoggerFactory.getLogger(SessionData.class);
56
57     /**
58      * If a version string matches this, then it is just a prefix (i.e., major or
59      * major.minor).
60      */
61     private static final Pattern VERSION_PREFIX_PAT = Pattern.compile("[^.]+(?:[.][^.]+)?");
62
63     /**
64      * DB provider.
65      */
66     private final PolicyModelsProvider dao;
67
68     /**
69      * Maps a group name to its group data. This accumulates the set of groups to be
70      * created and updated when the REST call completes.
71      */
72     private final Map<String, GroupData> groupCache = new HashMap<>();
73
74     /**
75      * Maps a policy type to the list of matching groups. Every group appearing within
76      * this map has a corresponding entry in {@link #groupCache}.
77      */
78     private final Map<ToscaPolicyTypeIdentifier, List<GroupData>> type2groups = new HashMap<>();
79
80     /**
81      * Maps a PDP name to its most recently generated update and state-change requests.
82      */
83     private final Map<String, Pair<PdpUpdate, PdpStateChange>> pdpRequests = new HashMap<>();
84
85     /**
86      * Maps a policy's identifier to the policy.
87      */
88     private final Map<ToscaPolicyIdentifierOptVersion, ToscaPolicy> policyCache = new HashMap<>();
89
90     /**
91      * Maps a policy type's identifier to the policy.
92      */
93     private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> typeCache = new HashMap<>();
94
95     /**
96      * Policies to be deployed. This is just used to build up the data, which is then
97      * passed to the notifier once the update is "committed".
98      */
99     private final Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> deploy = new HashMap<>();
100
101     /**
102      * Policies to be undeployed. This is just used to build up the data, which is then
103      * passed to the notifier once the update is "committed".
104      */
105     private final Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> undeploy = new HashMap<>();
106
107
108     /**
109      * Constructs the object.
110      *
111      * @param dao DAO provider
112      */
113     public SessionData(PolicyModelsProvider dao) {
114         this.dao = dao;
115     }
116
117     /**
118      * Gets the policy type, referenced by an identifier. Loads it from the cache, if
119      * possible. Otherwise, gets it from the DB.
120      *
121      * @param desiredType policy type identifier
122      * @return the specified policy type
123      * @throws PfModelException if an error occurred
124      */
125     public ToscaPolicyType getPolicyType(ToscaPolicyTypeIdentifier desiredType) throws PfModelException {
126
127         ToscaPolicyType type = typeCache.get(desiredType);
128         if (type == null) {
129
130             List<ToscaPolicyType> lst = dao.getPolicyTypeList(desiredType.getName(), desiredType.getVersion());
131             if (lst.isEmpty()) {
132                 return null;
133             }
134
135             type = lst.get(0);
136             typeCache.put(desiredType, type);
137         }
138
139         return type;
140     }
141
142     /**
143      * Gets the policy, referenced by an identifier. Loads it from the cache, if possible.
144      * Otherwise, gets it from the DB.
145      *
146      * @param desiredPolicy policy identifier
147      * @return the specified policy
148      * @throws PfModelException if an error occurred
149      */
150     public ToscaPolicy getPolicy(ToscaPolicyIdentifierOptVersion desiredPolicy) throws PfModelException {
151
152         ToscaPolicy policy = policyCache.get(desiredPolicy);
153         if (policy == null) {
154             ToscaPolicyFilterBuilder filterBuilder = ToscaPolicyFilter.builder().name(desiredPolicy.getName());
155             setPolicyFilterVersion(filterBuilder, desiredPolicy.getVersion());
156
157             List<ToscaPolicy> lst = dao.getFilteredPolicyList(filterBuilder.build());
158             if (lst.isEmpty()) {
159                 return null;
160             }
161
162             policy = lst.get(0);
163             policyCache.put(desiredPolicy, policy);
164         }
165
166         // desired version may have only been a prefix - cache with full identifier, too
167         policyCache.putIfAbsent(new ToscaPolicyIdentifierOptVersion(policy.getIdentifier()), policy);
168
169         return policy;
170     }
171
172     /**
173      * Sets the "version" in a policy filter.
174      *
175      * @param filterBuilder filter builder whose version should be set
176      * @param desiredVersion desired version
177      */
178     private void setPolicyFilterVersion(ToscaPolicyFilterBuilder filterBuilder, String desiredVersion) {
179
180         if (desiredVersion == null) {
181             // no version specified - get the latest
182             filterBuilder.version(ToscaPolicyFilter.LATEST_VERSION);
183
184         } else if (isVersionPrefix(desiredVersion)) {
185             // version prefix provided - match the prefix and then pick the latest
186             filterBuilder.versionPrefix(desiredVersion + ".").version(ToscaPolicyFilter.LATEST_VERSION);
187
188         } else {
189             // must be an exact match
190             filterBuilder.version(desiredVersion);
191         }
192     }
193
194     /**
195      * Determines if a version contains only a prefix.
196      *
197      * @param version version to inspect
198      * @return {@code true} if the version contains only a prefix, {@code false} if it is
199      *         fully qualified
200      */
201     public static boolean isVersionPrefix(String version) {
202         return VERSION_PREFIX_PAT.matcher(version).matches();
203     }
204
205     /**
206      * Adds an update and state-change to the sets, replacing any previous entries for the
207      * given PDP.
208      *
209      * @param update the update to be added
210      * @param change the state-change to be added
211      */
212     public void addRequests(PdpUpdate update, PdpStateChange change) {
213         if (!update.getName().equals(change.getName())) {
214             throw new IllegalArgumentException("PDP name mismatch " + update.getName() + ", " + change.getName());
215         }
216
217         logger.info("add update and state-change {} {} {} policies={}", update.getName(), update.getPdpGroup(),
218                         update.getPdpSubgroup(), update.getPolicies().size());
219         pdpRequests.put(update.getName(), Pair.of(update, change));
220     }
221
222     /**
223      * Adds an update to the set of updates, replacing any previous entry for the given
224      * PDP.
225      *
226      * @param update the update to be added
227      */
228     public void addUpdate(PdpUpdate update) {
229         logger.info("add update {} {} {} policies={}", update.getName(), update.getPdpGroup(), update.getPdpSubgroup(),
230                         update.getPolicies().size());
231         pdpRequests.compute(update.getName(), (name, data) -> Pair.of(update, (data == null ? null : data.getRight())));
232     }
233
234     /**
235      * Adds a state-change to the set of state-change requests, replacing any previous
236      * entry for the given PDP.
237      *
238      * @param change the state-change to be added
239      */
240     public void addStateChange(PdpStateChange change) {
241         logger.info("add state-change {}", change.getName());
242         pdpRequests.compute(change.getName(), (name, data) -> Pair.of((data == null ? null : data.getLeft()), change));
243     }
244
245     /**
246      * Determines if any changes were made due to the REST call.
247      *
248      * @return {@code true} if nothing was changed, {@code false} if something was changed
249      */
250     public boolean isUnchanged() {
251         return groupCache.values().stream().allMatch(GroupData::isUnchanged);
252     }
253
254     /**
255      * Gets the accumulated PDP requests.
256      *
257      * @return the PDP requests
258      */
259     public Collection<Pair<PdpUpdate, PdpStateChange>> getPdpRequests() {
260         return pdpRequests.values();
261     }
262
263     /**
264      * Gets the accumulated PDP update requests.
265      *
266      * @return the PDP requests
267      */
268     public List<PdpUpdate> getPdpUpdates() {
269         return pdpRequests.values().stream().filter(req -> req.getLeft() != null).map(Pair::getLeft)
270                         .collect(Collectors.toList());
271     }
272
273     /**
274      * Gets the accumulated PDP state-change requests.
275      *
276      * @return the PDP requests
277      */
278     public List<PdpStateChange> getPdpStateChanges() {
279         return pdpRequests.values().stream().filter(req -> req.getRight() != null).map(Pair::getRight)
280                         .collect(Collectors.toList());
281     }
282
283     /**
284      * Creates a group.
285      *
286      * @param newGroup the new group
287      */
288     public void create(PdpGroup newGroup) {
289         String name = newGroup.getName();
290
291         if (groupCache.put(name, new GroupData(newGroup, true)) != null) {
292             throw new IllegalStateException("group already cached: " + name);
293         }
294
295         logger.info("create cached group {}", newGroup.getName());
296     }
297
298     /**
299      * Updates a group.
300      *
301      * @param newGroup the updated group
302      */
303     public void update(PdpGroup newGroup) {
304         String name = newGroup.getName();
305         GroupData data = groupCache.get(name);
306         if (data == null) {
307             throw new IllegalStateException("group not cached: " + name);
308         }
309
310         logger.info("update cached group {}", newGroup.getName());
311         data.update(newGroup);
312     }
313
314     /**
315      * Gets the group by the given name.
316      *
317      * @param name name of the group to get
318      * @return the group, or {@code null} if it does not exist
319      * @throws PfModelException if an error occurred
320      */
321     public PdpGroup getGroup(String name) throws PfModelException {
322
323         GroupData data = groupCache.get(name);
324         if (data == null) {
325             List<PdpGroup> lst = dao.getPdpGroups(name);
326             if (lst.isEmpty()) {
327                 logger.info("unknown group {}", name);
328                 return null;
329             }
330
331             logger.info("cache group {}", name);
332             data = new GroupData(lst.get(0));
333             groupCache.put(name, data);
334
335         } else {
336             logger.info("use cached group {}", name);
337         }
338
339         return data.getGroup();
340     }
341
342     /**
343      * Gets the active groups supporting the given policy.
344      *
345      * @param type desired policy type
346      * @return the active groups supporting the given policy
347      * @throws PfModelException if an error occurred
348      */
349     public List<PdpGroup> getActivePdpGroupsByPolicyType(ToscaPolicyTypeIdentifier type) throws PfModelException {
350         List<GroupData> data = type2groups.get(type);
351         if (data == null) {
352             PdpGroupFilter filter = PdpGroupFilter.builder().policyTypeList(Collections.singletonList(type))
353                             .groupState(PdpState.ACTIVE).build();
354
355             List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
356
357             data = groups.stream().map(this::addGroup).collect(Collectors.toList());
358             type2groups.put(type, data);
359         }
360
361         return data.stream().map(GroupData::getGroup).collect(Collectors.toList());
362     }
363
364     /**
365      * Adds a group to the group cache, if it isn't already in the cache.
366      *
367      * @param group the group to be added
368      * @return the cache entry
369      */
370     private GroupData addGroup(PdpGroup group) {
371         GroupData data = groupCache.get(group.getName());
372         if (data == null) {
373             logger.info("cache group {}", group.getName());
374             data = new GroupData(group);
375             groupCache.put(group.getName(), data);
376
377         } else {
378             logger.info("use cached group {}", group.getName());
379         }
380
381         return data;
382     }
383
384     /**
385      * Update the DB with the changes.
386      *
387      * @throws PfModelException if an error occurred
388      */
389     public void updateDb() throws PfModelException {
390         // create new groups
391         List<GroupData> created = groupCache.values().stream().filter(GroupData::isNew).collect(Collectors.toList());
392         if (!created.isEmpty()) {
393             if (logger.isInfoEnabled()) {
394                 created.forEach(group -> logger.info("creating DB group {}", group.getGroup().getName()));
395             }
396             dao.createPdpGroups(created.stream().map(GroupData::getGroup).collect(Collectors.toList()));
397         }
398
399         // update existing groups
400         List<GroupData> updated =
401                         groupCache.values().stream().filter(GroupData::isUpdated).collect(Collectors.toList());
402         if (!updated.isEmpty()) {
403             if (logger.isInfoEnabled()) {
404                 updated.forEach(group -> logger.info("updating DB group {}", group.getGroup().getName()));
405             }
406             dao.updatePdpGroups(updated.stream().map(GroupData::getGroup).collect(Collectors.toList()));
407         }
408     }
409
410     /**
411      * Deletes a group from the DB, immediately (i.e., without caching the request to be
412      * executed later).
413      *
414      * @param group the group to be deleted
415      * @throws PfModelException if an error occurred
416      */
417     public void deleteGroupFromDb(PdpGroup group) throws PfModelException {
418         logger.info("deleting DB group {}", group.getName());
419         dao.deletePdpGroup(group.getName());
420     }
421
422     /**
423      * Adds policy deployment data.
424      *
425      * @param policyId ID of the policy being deployed
426      * @param pdps PDPs to which the policy is being deployed
427      * @throws PfModelException if an error occurred
428      */
429     protected void trackDeploy(ToscaPolicyIdentifier policyId, Collection<String> pdps) throws PfModelException {
430         trackDeploy(policyId, new HashSet<>(pdps));
431     }
432
433     /**
434      * Adds policy deployment data.
435      *
436      * @param policyId ID of the policy being deployed
437      * @param pdps PDPs to which the policy is being deployed
438      * @throws PfModelException if an error occurred
439      */
440     protected void trackDeploy(ToscaPolicyIdentifier policyId, Set<String> pdps) throws PfModelException {
441         addData(policyId, pdps, deploy, undeploy);
442     }
443
444     /**
445      * Adds policy undeployment data.
446      *
447      * @param policyId ID of the policy being undeployed
448      * @param pdps PDPs to which the policy is being undeployed
449      * @throws PfModelException if an error occurred
450      */
451     protected void trackUndeploy(ToscaPolicyIdentifier policyId, Collection<String> pdps) throws PfModelException {
452         trackUndeploy(policyId, new HashSet<>(pdps));
453     }
454
455     /**
456      * Adds policy undeployment data.
457      *
458      * @param policyId ID of the policy being undeployed
459      * @param pdps PDPs to which the policy is being undeployed
460      * @throws PfModelException if an error occurred
461      */
462     protected void trackUndeploy(ToscaPolicyIdentifier policyId, Set<String> pdps) throws PfModelException {
463         addData(policyId, pdps, undeploy, deploy);
464     }
465
466     /**
467      * Adds policy deployment/undeployment data.
468      *
469      * @param policyId ID of the policy being deployed/undeployed
470      * @param pdps PDPs to which the policy is being deployed/undeployed
471      * @param addMap map to which it should be added
472      * @param removeMap map from which it should be removed
473      * @throws PfModelException if an error occurred
474      */
475     private void addData(ToscaPolicyIdentifier policyId, Set<String> pdps,
476                     Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> addMap,
477                     Map<ToscaPolicyIdentifier, PolicyPdpNotificationData> removeMap) throws PfModelException {
478
479         PolicyPdpNotificationData removeData = removeMap.get(policyId);
480         if (removeData != null) {
481             removeData.removeAll(pdps);
482         }
483
484         ToscaPolicyIdentifierOptVersion optid = new ToscaPolicyIdentifierOptVersion(policyId);
485         ToscaPolicyTypeIdentifier policyType = getPolicy(optid).getTypeIdentifier();
486
487         addMap.computeIfAbsent(policyId, key -> new PolicyPdpNotificationData(policyId, policyType)).addAll(pdps);
488     }
489
490     /**
491      * Gets the policies to be deployed.
492      *
493      * @return the policies to be deployed
494      */
495     public Collection<PolicyPdpNotificationData> getDeployData() {
496         return deploy.values();
497     }
498
499     /**
500      * Gets the policies to be undeployed.
501      *
502      * @return the policies to be undeployed
503      */
504     public Collection<PolicyPdpNotificationData> getUndeployData() {
505         return undeploy.values();
506     }
507 }