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