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