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