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