2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.policy.pap.main.rest;
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;
33 import java.util.stream.Collectors;
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;
55 * Data used during a single REST call when updating PDP policies.
57 public class SessionData {
58 private static final Logger logger = LoggerFactory.getLogger(SessionData.class);
61 * If a version string matches this, then it is just a prefix (i.e., major or major.minor).
63 private static final Pattern VERSION_PREFIX_PAT = Pattern.compile("[^.]+(?:[.][^.]+)?");
68 private final PolicyModelsProvider dao;
71 * Maps a group name to its group data. This accumulates the set of groups to be created and updated when the REST
74 private final Map<String, GroupData> groupCache = new HashMap<>();
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}.
80 private final Map<ToscaConceptIdentifier, List<GroupData>> type2groups = new HashMap<>();
83 * Maps a PDP name to its most recently generated update and state-change requests.
85 private final Map<String, Pair<PdpUpdate, PdpStateChange>> pdpRequests = new HashMap<>();
88 * Maps a policy's identifier to the policy.
90 private final Map<ToscaConceptIdentifierOptVersion, ToscaPolicy> policyCache = new HashMap<>();
93 * Maps a policy type's identifier to the policy.
95 private final Map<ToscaConceptIdentifier, ToscaPolicyType> typeCache = new HashMap<>();
98 * Map's a policy's identifier to the policies for deployment.
100 private Map<ToscaConceptIdentifier, ToscaPolicy> policiesToBeDeployed = new HashMap<>();
103 * Set of policies to be undeployed.
105 private Set<ToscaConceptIdentifier> policiesToBeUndeployed = new HashSet<>();
108 * User starting requests.
111 private final String user;
114 * Tracks policy deployment status so notifications can be generated.
116 private final DeploymentStatus deployStatus;
118 private PolicyAuditManager auditManager;
121 * Constructs the object.
123 * @param dao DAO provider
124 * @param user user triggering the request
126 public SessionData(PolicyModelsProvider dao, String user) {
128 this.deployStatus = makeDeploymentStatus(dao);
129 this.auditManager = makePolicyAuditManager(dao);
134 * Gets the policy type, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from
137 * @param desiredType policy type identifier
138 * @return the specified policy type
139 * @throws PfModelException if an error occurred
141 public ToscaPolicyType getPolicyType(ToscaConceptIdentifier desiredType) throws PfModelException {
143 ToscaPolicyType type = typeCache.get(desiredType);
146 List<ToscaPolicyType> lst = dao.getPolicyTypeList(desiredType.getName(), desiredType.getVersion());
152 typeCache.put(desiredType, type);
159 * Gets the policy, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from the
162 * @param desiredPolicy policy identifier
163 * @return the specified policy
164 * @throws PfModelException if an error occurred
166 public ToscaPolicy getPolicy(ToscaConceptIdentifierOptVersion desiredPolicy) throws PfModelException {
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());
174 List<ToscaPolicy> lst = dao.getFilteredPolicyList(filterBuilder.build());
180 policyCache.put(desiredPolicy, policy);
183 // desired version may have only been a prefix - cache with full identifier, too
184 policyCache.putIfAbsent(new ToscaConceptIdentifierOptVersion(policy.getIdentifier()), policy);
190 * Sets the "version" in a policy filter.
192 * @param filterBuilder filter builder whose version should be set
193 * @param desiredVersion desired version
195 private void setPolicyFilterVersion(ToscaTypedEntityFilterBuilder<ToscaPolicy> filterBuilder,
196 String desiredVersion) {
198 if (desiredVersion == null) {
199 // no version specified - get the latest
200 filterBuilder.version(ToscaTypedEntityFilter.LATEST_VERSION);
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);
207 // must be an exact match
208 filterBuilder.version(desiredVersion);
213 * Determines if a version contains only a prefix.
215 * @param version version to inspect
216 * @return {@code true} if the version contains only a prefix, {@code false} if it is fully qualified
218 public static boolean isVersionPrefix(String version) {
219 return VERSION_PREFIX_PAT.matcher(version).matches();
223 * Adds an update and state-change to the sets, replacing any previous entries for the given PDP.
225 * @param update the update to be added
226 * @param change the state-change to be added
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());
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));
239 * Adds an update to the set of updates, replacing any previous entry for the given PDP.
241 * @param update the update to be added
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())));
250 * Adds a state-change to the set of state-change requests, replacing any previous entry for the given PDP.
252 * @param change the state-change to be added
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));
260 * Determines if any changes were made due to the REST call.
262 * @return {@code true} if nothing was changed, {@code false} if something was changed
264 public boolean isUnchanged() {
265 return groupCache.values().stream().allMatch(GroupData::isUnchanged);
269 * Gets the accumulated PDP requests.
271 * @return the PDP requests
273 public Collection<Pair<PdpUpdate, PdpStateChange>> getPdpRequests() {
274 return pdpRequests.values();
278 * Gets the accumulated PDP update requests.
280 * @return the PDP requests
282 public List<PdpUpdate> getPdpUpdates() {
283 return pdpRequests.values().stream().filter(req -> req.getLeft() != null).map(Pair::getLeft)
284 .collect(Collectors.toList());
288 * Gets the accumulated PDP state-change requests.
290 * @return the PDP requests
292 public List<PdpStateChange> getPdpStateChanges() {
293 return pdpRequests.values().stream().filter(req -> req.getRight() != null).map(Pair::getRight)
294 .collect(Collectors.toList());
300 * @param newGroup the new group
302 public void create(PdpGroup newGroup) {
303 String name = newGroup.getName();
305 if (groupCache.put(name, new GroupData(newGroup, true)) != null) {
306 throw new IllegalStateException("group already cached: " + name);
309 logger.info("create cached group {}", newGroup.getName());
315 * @param newGroup the updated group
317 public void update(PdpGroup newGroup) {
318 String name = newGroup.getName();
319 GroupData data = groupCache.get(name);
321 throw new IllegalStateException("group not cached: " + name);
324 logger.info("update cached group {}", newGroup.getName());
325 data.update(newGroup);
329 * Gets the group by the given name.
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
335 public PdpGroup getGroup(String name) throws PfModelException {
337 GroupData data = groupCache.get(name);
339 List<PdpGroup> lst = dao.getPdpGroups(name);
341 logger.info("unknown group {}", name);
345 logger.info("cache group {}", name);
346 data = new GroupData(lst.get(0));
347 groupCache.put(name, data);
349 deployStatus.loadByGroup(name);
352 logger.info("use cached group {}", name);
355 return data.getGroup();
359 * Gets the active groups supporting the given policy.
361 * @param type desired policy type
362 * @return the active groups supporting the given policy
363 * @throws PfModelException if an error occurred
365 public List<PdpGroup> getActivePdpGroupsByPolicyType(ToscaConceptIdentifier type) throws PfModelException {
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.
370 List<GroupData> data = type2groups.get(type); // NOSONAR
372 PdpGroupFilter filter = PdpGroupFilter.builder().policyTypeList(Collections.singletonList(type))
373 .groupState(PdpState.ACTIVE).build();
375 List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
377 data = groups.stream().map(this::addGroup).collect(Collectors.toList());
378 type2groups.put(type, data);
381 return data.stream().map(GroupData::getGroup).collect(Collectors.toList());
385 * Gets the list of policies to be deployed to the PDPs.
387 * @returns a list of policies to be deployed
389 public List<ToscaPolicy> getPoliciesToBeDeployed() {
390 return new LinkedList<>(this.policiesToBeDeployed.values());
394 * Gets the list of policies to be undeployed from the PDPs.
396 * @returns a list of policies to be undeployed
398 public List<ToscaConceptIdentifier> getPoliciesToBeUndeployed() {
399 return new LinkedList<>(this.policiesToBeUndeployed);
403 * Adds a group to the group cache, if it isn't already in the cache.
405 * @param group the group to be added
406 * @return the cache entry
408 private GroupData addGroup(PdpGroup group) {
409 GroupData data = groupCache.get(group.getName());
411 logger.info("cache group {}", group.getName());
412 data = new GroupData(group);
413 groupCache.put(group.getName(), data);
416 logger.info("use cached group {}", group.getName());
423 * Update the DB with the changes.
425 * @param notification notification to which to add policy status
427 * @throws PfModelException if an error occurred
429 public void updateDb(PolicyNotification notification) throws PfModelException {
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()));
436 dao.createPdpGroups(created.stream().map(GroupData::getGroup).collect(Collectors.toList()));
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()));
446 dao.updatePdpGroups(updated.stream().map(GroupData::getGroup).collect(Collectors.toList()));
449 // send audits records to DB
450 auditManager.saveRecordsToDb();
452 // flush deployment status records to the DB
453 deployStatus.flush(notification);
457 * Deletes a group from the DB, immediately (i.e., without caching the request to be executed later).
459 * @param group the group to be deleted
460 * @throws PfModelException if an error occurred
462 public void deleteGroupFromDb(PdpGroup group) throws PfModelException {
463 logger.info("deleting DB group {}", group.getName());
464 dao.deletePdpGroup(group.getName());
468 * Adds policy deployment data.
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
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);
481 addData(policyId, pdps, pdpGroup, pdpType, true);
482 auditManager.addDeploymentAudit(policyId, pdpGroup, pdpType, user);
486 * Adds policy undeployment data.
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
494 protected void trackUndeploy(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup,
495 String pdpType) throws PfModelException {
496 policiesToBeUndeployed.add(policyId);
498 addData(policyId, pdps, pdpGroup, pdpType, false);
499 auditManager.addUndeploymentAudit(policyId, pdpGroup, pdpType, user);
503 * Adds policy deployment/undeployment data.
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
512 private void addData(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup, String pdpType,
513 boolean deploy) throws PfModelException {
515 // delete all records whose "deploy" flag is the opposite of what we want
516 deployStatus.deleteDeployment(policyId, !deploy);
518 var optid = new ToscaConceptIdentifierOptVersion(policyId);
519 ToscaConceptIdentifier policyType = getPolicy(optid).getTypeIdentifier();
521 for (String pdp : pdps) {
522 deployStatus.deploy(pdp, policyId, policyType, pdpGroup, pdpType, deploy);
526 // these may be overridden by junit tests
528 protected DeploymentStatus makeDeploymentStatus(PolicyModelsProvider dao) {
529 return new DeploymentStatus(dao);
532 protected PolicyAuditManager makePolicyAuditManager(PolicyModelsProvider dao) {
533 return new PolicyAuditManager(dao);