2 * ============LICENSE_START=======================================================
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
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;
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;
58 * Data used during a single REST call when updating PDP policies.
60 public class SessionData {
61 private static final Logger logger = LoggerFactory.getLogger(SessionData.class);
64 * If a version string matches this, then it is just a prefix (i.e., major or major.minor).
66 private static final Pattern VERSION_PREFIX_PAT = Pattern.compile("[^.]+(?:[.][^.]+)?");
69 * Maps a group name to its group data. This accumulates the set of groups to be created and updated when the REST
72 private final Map<String, GroupData> groupCache = new HashMap<>();
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}.
78 private final Map<ToscaConceptIdentifier, List<GroupData>> type2groups = new HashMap<>();
81 * Maps a PDP name to its most recently generated update and state-change requests.
83 private final Map<String, Pair<PdpUpdate, PdpStateChange>> pdpRequests = new HashMap<>();
86 * Maps a policy's identifier to the policy.
88 private final Map<ToscaConceptIdentifierOptVersion, ToscaPolicy> policyCache = new HashMap<>();
91 * Maps a policy type's identifier to the policy.
93 private final Map<ToscaConceptIdentifier, ToscaPolicyType> typeCache = new HashMap<>();
96 * Map's a policy's identifier to the policies for deployment.
98 private final Map<ToscaConceptIdentifier, ToscaPolicy> policiesToBeDeployed = new HashMap<>();
101 * Set of policies to be undeployed.
103 private final Set<ToscaConceptIdentifier> policiesToBeUndeployed = new HashSet<>();
106 * User starting requests.
109 private final String user;
112 * Tracks policy deployment status so notifications can be generated.
114 private final DeploymentStatus deployStatus;
116 private final PolicyAuditManager auditManager;
118 private final ToscaServiceTemplateService toscaService;
120 private final PdpGroupService pdpGroupService;
123 * Constructs the object.
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
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);
141 * Gets the policy type, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from
144 * @param desiredType policy type identifier
145 * @return the specified policy type
146 * @throws PfModelException if an error occurred
148 public ToscaPolicyType getPolicyType(ToscaConceptIdentifier desiredType) throws PfModelException {
150 ToscaPolicyType type = typeCache.get(desiredType);
153 List<ToscaPolicyType> lst = toscaService.getPolicyTypeList(desiredType.getName(), desiredType.getVersion());
159 typeCache.put(desiredType, type);
166 * Gets the policy, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from the
169 * @param desiredPolicy policy identifier
170 * @return the specified policy
171 * @throws PfModelException if an error occurred
173 public ToscaPolicy getPolicy(ToscaConceptIdentifierOptVersion desiredPolicy) throws PfModelException {
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());
181 List<ToscaPolicy> lst = toscaService.getFilteredPolicyList(filterBuilder.build());
187 policyCache.put(desiredPolicy, policy);
190 // desired version may have only been a prefix - cache with full identifier, too
191 policyCache.putIfAbsent(new ToscaConceptIdentifierOptVersion(policy.getIdentifier()), policy);
197 * Sets the "version" in a policy filter.
199 * @param filterBuilder filter builder whose version should be set
200 * @param desiredVersion desired version
202 private void setPolicyFilterVersion(ToscaTypedEntityFilterBuilder<ToscaPolicy> filterBuilder,
203 String desiredVersion) {
205 if (desiredVersion == null) {
206 // no version specified - get the latest
207 filterBuilder.version(ToscaTypedEntityFilter.LATEST_VERSION);
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);
214 // must be an exact match
215 filterBuilder.version(desiredVersion);
220 * Determines if a version contains only a prefix.
222 * @param version version to inspect
223 * @return {@code true} if the version contains only a prefix, {@code false} if it is fully qualified
225 public static boolean isVersionPrefix(String version) {
226 return VERSION_PREFIX_PAT.matcher(version).matches();
230 * Adds an update and state-change to the sets, replacing any previous entries for the given PDP.
232 * @param update the update to be added
233 * @param change the state-change to be added
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());
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));
246 * Adds an update to the set of updates, replacing any previous entry for the given PDP.
248 * @param update the update to be added
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())));
257 * Adds a state-change to the set of state-change requests, replacing any previous entry for the given PDP.
259 * @param change the state-change to be added
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));
267 * Determines if any changes were made due to the REST call.
269 * @return {@code true} if nothing was changed, {@code false} if something was changed
271 public boolean isUnchanged() {
272 return groupCache.values().stream().allMatch(GroupData::isUnchanged);
276 * Gets the accumulated PDP requests.
278 * @return the PDP requests
280 public Collection<Pair<PdpUpdate, PdpStateChange>> getPdpRequests() {
281 return pdpRequests.values();
285 * Gets the accumulated PDP update requests.
287 * @return the PDP requests
289 public List<PdpUpdate> getPdpUpdates() {
290 return pdpRequests.values().stream().filter(req -> req.getLeft() != null)
291 .map(Pair::getLeft).toList();
295 * Gets the accumulated PDP state-change requests.
297 * @return the PDP requests
299 public List<PdpStateChange> getPdpStateChanges() {
300 return pdpRequests.values().stream().filter(req -> req.getRight() != null)
301 .map(Pair::getRight).toList();
307 * @param newGroup the new group
309 public void create(PdpGroup newGroup) {
310 String name = newGroup.getName();
312 if (groupCache.put(name, new GroupData(newGroup, true)) != null) {
313 throw new IllegalStateException("group already cached: " + name);
316 logger.info("create cached group {}", newGroup.getName());
322 * @param newGroup the updated group
324 public void update(PdpGroup newGroup) {
325 String name = newGroup.getName();
326 GroupData data = groupCache.get(name);
328 throw new IllegalStateException("group not cached: " + name);
331 logger.info("update cached group {}", newGroup.getName());
332 data.update(newGroup);
336 * Gets the group by the given name.
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
342 public PdpGroup getGroup(String name) throws PfModelException {
344 GroupData data = groupCache.get(name);
346 List<PdpGroup> lst = pdpGroupService.getPdpGroups(name);
348 logger.info("unknown group {}", name);
352 logger.info("cache group {}", name);
353 data = new GroupData(lst.get(0));
354 groupCache.put(name, data);
356 deployStatus.loadByGroup(name);
359 logger.info("use cached group {}", name);
362 return data.getGroup();
366 * Gets the active groups supporting the given policy.
368 * @param type desired policy type
369 * @return the active groups supporting the given policy
371 public List<PdpGroup> getActivePdpGroupsByPolicyType(ToscaConceptIdentifier type) {
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.
376 List<GroupData> data = type2groups.get(type); // NOSONAR
378 PdpGroupFilter filter = PdpGroupFilter.builder().policyTypeList(Collections.singletonList(type))
379 .groupState(PdpState.ACTIVE).build();
381 List<PdpGroup> groups = pdpGroupService.getFilteredPdpGroups(filter);
383 data = groups.stream().map(this::addGroup).toList();
384 type2groups.put(type, data);
387 return data.stream().map(GroupData::getGroup).toList();
391 * Gets the list of policies to be deployed to the PDPs.
393 * @return a list of policies to be deployed
395 public List<ToscaPolicy> getPoliciesToBeDeployed() {
396 return new LinkedList<>(this.policiesToBeDeployed.values());
400 * Gets the list of policies to be undeployed from the PDPs.
402 * @return a list of policies to be undeployed
404 public List<ToscaConceptIdentifier> getPoliciesToBeUndeployed() {
405 return new LinkedList<>(this.policiesToBeUndeployed);
409 * Adds a group to the group cache, if it isn't already in the cache.
411 * @param group the group to be added
412 * @return the cache entry
414 private GroupData addGroup(PdpGroup group) {
415 GroupData data = groupCache.get(group.getName());
417 logger.info("cache group {}", group.getName());
418 data = new GroupData(group);
419 groupCache.put(group.getName(), data);
422 logger.info("use cached group {}", group.getName());
429 * Update the DB with the changes.
431 * @param notification notification to which to add policy status
433 public void updateDb(PolicyNotification notification) {
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()));
440 pdpGroupService.createPdpGroups(created.stream().map(GroupData::getGroup).toList());
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()));
450 pdpGroupService.updatePdpGroups(updated.stream().map(GroupData::getGroup).toList());
453 // send audits records to DB
454 auditManager.saveRecordsToDb();
456 // flush deployment status records to the DB
457 deployStatus.flush(notification);
461 * Deletes a group from the DB, immediately (i.e., without caching the request to be executed later).
463 * @param group the group to be deleted
465 public void deleteGroupFromDb(PdpGroup group) {
466 logger.info("deleting DB group {}", group.getName());
467 pdpGroupService.deletePdpGroup(group.getName());
471 * Adds policy deployment data.
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
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);
484 addData(policyId, pdps, pdpGroup, pdpType, true);
485 auditManager.addDeploymentAudit(policyId, pdpGroup, pdpType, user);
489 * Adds policy undeployment data.
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
497 protected void trackUndeploy(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup,
498 String pdpType) throws PfModelException {
499 policiesToBeUndeployed.add(policyId);
501 addData(policyId, pdps, pdpGroup, pdpType, false);
502 auditManager.addUndeploymentAudit(policyId, pdpGroup, pdpType, user);
506 * Adds policy deployment/undeployment data.
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
515 private void addData(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup, String pdpType,
516 boolean deploy) throws PfModelException {
518 // delete all records whose "deploy" flag is the opposite of what we want
519 deployStatus.deleteDeployment(policyId, !deploy);
521 var optid = new ToscaConceptIdentifierOptVersion(policyId);
522 ToscaConceptIdentifier policyType = getPolicy(optid).getTypeIdentifier();
524 for (String pdp : pdps) {
525 deployStatus.deploy(pdp, policyId, policyType, pdpGroup, pdpType, deploy);
529 // these may be overridden by junit tests
531 protected DeploymentStatus makeDeploymentStatus(PolicyStatusService policyStatusService) {
532 return new DeploymentStatus(policyStatusService);
535 protected PolicyAuditManager makePolicyAuditManager(PolicyAuditService policyAuditService) {
536 return new PolicyAuditManager(policyAuditService);