2 * ============LICENSE_START=======================================================
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
23 package org.onap.policy.pap.main.rest;
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;
34 import java.util.stream.Collectors;
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;
59 * Data used during a single REST call when updating PDP policies.
61 public class SessionData {
62 private static final Logger logger = LoggerFactory.getLogger(SessionData.class);
65 * If a version string matches this, then it is just a prefix (i.e., major or major.minor).
67 private static final Pattern VERSION_PREFIX_PAT = Pattern.compile("[^.]+(?:[.][^.]+)?");
70 * Maps a group name to its group data. This accumulates the set of groups to be created and updated when the REST
73 private final Map<String, GroupData> groupCache = new HashMap<>();
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}.
79 private final Map<ToscaConceptIdentifier, List<GroupData>> type2groups = new HashMap<>();
82 * Maps a PDP name to its most recently generated update and state-change requests.
84 private final Map<String, Pair<PdpUpdate, PdpStateChange>> pdpRequests = new HashMap<>();
87 * Maps a policy's identifier to the policy.
89 private final Map<ToscaConceptIdentifierOptVersion, ToscaPolicy> policyCache = new HashMap<>();
92 * Maps a policy type's identifier to the policy.
94 private final Map<ToscaConceptIdentifier, ToscaPolicyType> typeCache = new HashMap<>();
97 * Map's a policy's identifier to the policies for deployment.
99 private final Map<ToscaConceptIdentifier, ToscaPolicy> policiesToBeDeployed = new HashMap<>();
102 * Set of policies to be undeployed.
104 private final Set<ToscaConceptIdentifier> policiesToBeUndeployed = new HashSet<>();
107 * User starting requests.
110 private final String user;
113 * Tracks policy deployment status so notifications can be generated.
115 private final DeploymentStatus deployStatus;
117 private final PolicyAuditManager auditManager;
119 private final ToscaServiceTemplateService toscaService;
121 private final PdpGroupService pdpGroupService;
124 * Constructs the object.
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
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);
142 * Gets the policy type, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from
145 * @param desiredType policy type identifier
146 * @return the specified policy type
147 * @throws PfModelException if an error occurred
149 public ToscaPolicyType getPolicyType(ToscaConceptIdentifier desiredType) throws PfModelException {
151 ToscaPolicyType type = typeCache.get(desiredType);
154 List<ToscaPolicyType> lst = toscaService.getPolicyTypeList(desiredType.getName(), desiredType.getVersion());
160 typeCache.put(desiredType, type);
167 * Gets the policy, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from the
170 * @param desiredPolicy policy identifier
171 * @return the specified policy
172 * @throws PfModelException if an error occurred
174 public ToscaPolicy getPolicy(ToscaConceptIdentifierOptVersion desiredPolicy) throws PfModelException {
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());
182 List<ToscaPolicy> lst = toscaService.getFilteredPolicyList(filterBuilder.build());
188 policyCache.put(desiredPolicy, policy);
191 // desired version may have only been a prefix - cache with full identifier, too
192 policyCache.putIfAbsent(new ToscaConceptIdentifierOptVersion(policy.getIdentifier()), policy);
198 * Sets the "version" in a policy filter.
200 * @param filterBuilder filter builder whose version should be set
201 * @param desiredVersion desired version
203 private void setPolicyFilterVersion(ToscaTypedEntityFilterBuilder<ToscaPolicy> filterBuilder,
204 String desiredVersion) {
206 if (desiredVersion == null) {
207 // no version specified - get the latest
208 filterBuilder.version(ToscaTypedEntityFilter.LATEST_VERSION);
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);
215 // must be an exact match
216 filterBuilder.version(desiredVersion);
221 * Determines if a version contains only a prefix.
223 * @param version version to inspect
224 * @return {@code true} if the version contains only a prefix, {@code false} if it is fully qualified
226 public static boolean isVersionPrefix(String version) {
227 return VERSION_PREFIX_PAT.matcher(version).matches();
231 * Adds an update and state-change to the sets, replacing any previous entries for the given PDP.
233 * @param update the update to be added
234 * @param change the state-change to be added
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());
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));
247 * Adds an update to the set of updates, replacing any previous entry for the given PDP.
249 * @param update the update to be added
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())));
258 * Adds a state-change to the set of state-change requests, replacing any previous entry for the given PDP.
260 * @param change the state-change to be added
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));
268 * Determines if any changes were made due to the REST call.
270 * @return {@code true} if nothing was changed, {@code false} if something was changed
272 public boolean isUnchanged() {
273 return groupCache.values().stream().allMatch(GroupData::isUnchanged);
277 * Gets the accumulated PDP requests.
279 * @return the PDP requests
281 public Collection<Pair<PdpUpdate, PdpStateChange>> getPdpRequests() {
282 return pdpRequests.values();
286 * Gets the accumulated PDP update requests.
288 * @return the PDP requests
290 public List<PdpUpdate> getPdpUpdates() {
291 return pdpRequests.values().stream().filter(req -> req.getLeft() != null).map(Pair::getLeft)
292 .collect(Collectors.toList());
296 * Gets the accumulated PDP state-change requests.
298 * @return the PDP requests
300 public List<PdpStateChange> getPdpStateChanges() {
301 return pdpRequests.values().stream().filter(req -> req.getRight() != null).map(Pair::getRight)
302 .collect(Collectors.toList());
308 * @param newGroup the new group
310 public void create(PdpGroup newGroup) {
311 String name = newGroup.getName();
313 if (groupCache.put(name, new GroupData(newGroup, true)) != null) {
314 throw new IllegalStateException("group already cached: " + name);
317 logger.info("create cached group {}", newGroup.getName());
323 * @param newGroup the updated group
325 public void update(PdpGroup newGroup) {
326 String name = newGroup.getName();
327 GroupData data = groupCache.get(name);
329 throw new IllegalStateException("group not cached: " + name);
332 logger.info("update cached group {}", newGroup.getName());
333 data.update(newGroup);
337 * Gets the group by the given name.
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
343 public PdpGroup getGroup(String name) throws PfModelException {
345 GroupData data = groupCache.get(name);
347 List<PdpGroup> lst = pdpGroupService.getPdpGroups(name);
349 logger.info("unknown group {}", name);
353 logger.info("cache group {}", name);
354 data = new GroupData(lst.get(0));
355 groupCache.put(name, data);
357 deployStatus.loadByGroup(name);
360 logger.info("use cached group {}", name);
363 return data.getGroup();
367 * Gets the active groups supporting the given policy.
369 * @param type desired policy type
370 * @return the active groups supporting the given policy
372 public List<PdpGroup> getActivePdpGroupsByPolicyType(ToscaConceptIdentifier type) {
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.
377 List<GroupData> data = type2groups.get(type); // NOSONAR
379 PdpGroupFilter filter = PdpGroupFilter.builder().policyTypeList(Collections.singletonList(type))
380 .groupState(PdpState.ACTIVE).build();
382 List<PdpGroup> groups = pdpGroupService.getFilteredPdpGroups(filter);
384 data = groups.stream().map(this::addGroup).collect(Collectors.toList());
385 type2groups.put(type, data);
388 return data.stream().map(GroupData::getGroup).collect(Collectors.toList());
392 * Gets the list of policies to be deployed to the PDPs.
394 * @return a list of policies to be deployed
396 public List<ToscaPolicy> getPoliciesToBeDeployed() {
397 return new LinkedList<>(this.policiesToBeDeployed.values());
401 * Gets the list of policies to be undeployed from the PDPs.
403 * @return a list of policies to be undeployed
405 public List<ToscaConceptIdentifier> getPoliciesToBeUndeployed() {
406 return new LinkedList<>(this.policiesToBeUndeployed);
410 * Adds a group to the group cache, if it isn't already in the cache.
412 * @param group the group to be added
413 * @return the cache entry
415 private GroupData addGroup(PdpGroup group) {
416 GroupData data = groupCache.get(group.getName());
418 logger.info("cache group {}", group.getName());
419 data = new GroupData(group);
420 groupCache.put(group.getName(), data);
423 logger.info("use cached group {}", group.getName());
430 * Update the DB with the changes.
432 * @param notification notification to which to add policy status
434 public void updateDb(PolicyNotification notification) {
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()));
441 pdpGroupService.createPdpGroups(created.stream().map(GroupData::getGroup).collect(Collectors.toList()));
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()));
451 pdpGroupService.updatePdpGroups(updated.stream().map(GroupData::getGroup).collect(Collectors.toList()));
454 // send audits records to DB
455 auditManager.saveRecordsToDb();
457 // flush deployment status records to the DB
458 deployStatus.flush(notification);
462 * Deletes a group from the DB, immediately (i.e., without caching the request to be executed later).
464 * @param group the group to be deleted
466 public void deleteGroupFromDb(PdpGroup group) {
467 logger.info("deleting DB group {}", group.getName());
468 pdpGroupService.deletePdpGroup(group.getName());
472 * Adds policy deployment data.
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
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);
485 addData(policyId, pdps, pdpGroup, pdpType, true);
486 auditManager.addDeploymentAudit(policyId, pdpGroup, pdpType, user);
490 * Adds policy undeployment data.
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
498 protected void trackUndeploy(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup,
499 String pdpType) throws PfModelException {
500 policiesToBeUndeployed.add(policyId);
502 addData(policyId, pdps, pdpGroup, pdpType, false);
503 auditManager.addUndeploymentAudit(policyId, pdpGroup, pdpType, user);
507 * Adds policy deployment/undeployment data.
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
516 private void addData(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup, String pdpType,
517 boolean deploy) throws PfModelException {
519 // delete all records whose "deploy" flag is the opposite of what we want
520 deployStatus.deleteDeployment(policyId, !deploy);
522 var optid = new ToscaConceptIdentifierOptVersion(policyId);
523 ToscaConceptIdentifier policyType = getPolicy(optid).getTypeIdentifier();
525 for (String pdp : pdps) {
526 deployStatus.deploy(pdp, policyId, policyType, pdpGroup, pdpType, deploy);
530 // these may be overridden by junit tests
532 protected DeploymentStatus makeDeploymentStatus(PolicyStatusService policyStatusService) {
533 return new DeploymentStatus(policyStatusService);
536 protected PolicyAuditManager makePolicyAuditManager(PolicyAuditService policyAuditService) {
537 return new PolicyAuditManager(policyAuditService);