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;
34 import org.apache.commons.lang3.tuple.Pair;
35 import org.onap.policy.models.base.PfModelException;
36 import org.onap.policy.models.pap.concepts.PolicyNotification;
37 import org.onap.policy.models.pdp.concepts.PdpGroup;
38 import org.onap.policy.models.pdp.concepts.PdpGroupFilter;
39 import org.onap.policy.models.pdp.concepts.PdpStateChange;
40 import org.onap.policy.models.pdp.concepts.PdpUpdate;
41 import org.onap.policy.models.pdp.enums.PdpState;
42 import org.onap.policy.models.provider.PolicyModelsProvider;
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.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * Data used during a single REST call when updating PDP policies.
56 public class SessionData {
57 private static final Logger logger = LoggerFactory.getLogger(SessionData.class);
60 * If a version string matches this, then it is just a prefix (i.e., major or major.minor).
62 private static final Pattern VERSION_PREFIX_PAT = Pattern.compile("[^.]+(?:[.][^.]+)?");
67 private final PolicyModelsProvider dao;
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 Map<ToscaConceptIdentifier, ToscaPolicy> policiesToBeDeployed = new HashMap<>();
102 * Set of policies to be undeployed.
104 private Set<ToscaConceptIdentifier> policiesToBeUndeployed = new HashSet<>();
107 * Tracks policy deployment status so notifications can be generated.
109 private final DeploymentStatus deployStatus;
113 * Constructs the object.
115 * @param dao DAO provider
117 public SessionData(PolicyModelsProvider dao) {
119 this.deployStatus = makeDeploymentStatus(dao);
123 * Gets the policy type, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from
126 * @param desiredType policy type identifier
127 * @return the specified policy type
128 * @throws PfModelException if an error occurred
130 public ToscaPolicyType getPolicyType(ToscaConceptIdentifier desiredType) throws PfModelException {
132 ToscaPolicyType type = typeCache.get(desiredType);
135 List<ToscaPolicyType> lst = dao.getPolicyTypeList(desiredType.getName(), desiredType.getVersion());
141 typeCache.put(desiredType, type);
148 * Gets the policy, referenced by an identifier. Loads it from the cache, if possible. Otherwise, gets it from the
151 * @param desiredPolicy policy identifier
152 * @return the specified policy
153 * @throws PfModelException if an error occurred
155 public ToscaPolicy getPolicy(ToscaConceptIdentifierOptVersion desiredPolicy) throws PfModelException {
157 ToscaPolicy policy = policyCache.get(desiredPolicy);
158 if (policy == null) {
159 ToscaTypedEntityFilterBuilder<ToscaPolicy> filterBuilder =
160 ToscaTypedEntityFilter.<ToscaPolicy>builder().name(desiredPolicy.getName());
161 setPolicyFilterVersion(filterBuilder, desiredPolicy.getVersion());
163 List<ToscaPolicy> lst = dao.getFilteredPolicyList(filterBuilder.build());
169 policyCache.put(desiredPolicy, policy);
172 // desired version may have only been a prefix - cache with full identifier, too
173 policyCache.putIfAbsent(new ToscaConceptIdentifierOptVersion(policy.getIdentifier()), policy);
179 * Sets the "version" in a policy filter.
181 * @param filterBuilder filter builder whose version should be set
182 * @param desiredVersion desired version
184 private void setPolicyFilterVersion(ToscaTypedEntityFilterBuilder<ToscaPolicy> filterBuilder,
185 String desiredVersion) {
187 if (desiredVersion == null) {
188 // no version specified - get the latest
189 filterBuilder.version(ToscaTypedEntityFilter.LATEST_VERSION);
191 } else if (isVersionPrefix(desiredVersion)) {
192 // version prefix provided - match the prefix and then pick the latest
193 filterBuilder.versionPrefix(desiredVersion + ".").version(ToscaTypedEntityFilter.LATEST_VERSION);
196 // must be an exact match
197 filterBuilder.version(desiredVersion);
202 * Determines if a version contains only a prefix.
204 * @param version version to inspect
205 * @return {@code true} if the version contains only a prefix, {@code false} if it is fully qualified
207 public static boolean isVersionPrefix(String version) {
208 return VERSION_PREFIX_PAT.matcher(version).matches();
212 * Adds an update and state-change to the sets, replacing any previous entries for the given PDP.
214 * @param update the update to be added
215 * @param change the state-change to be added
217 public void addRequests(PdpUpdate update, PdpStateChange change) {
218 if (!update.getName().equals(change.getName())) {
219 throw new IllegalArgumentException("PDP name mismatch " + update.getName() + ", " + change.getName());
222 logger.info("add update and state-change {} {} {} policies={}", update.getName(), update.getPdpGroup(),
223 update.getPdpSubgroup(), update.getPoliciesToBeDeployed().size());
224 pdpRequests.put(update.getName(), Pair.of(update, change));
228 * Adds an update to the set of updates, replacing any previous entry for the given PDP.
230 * @param update the update to be added
232 public void addUpdate(PdpUpdate update) {
233 logger.info("add update {} {} {} policies={}", update.getName(), update.getPdpGroup(), update.getPdpSubgroup(),
234 update.getPoliciesToBeDeployed().size());
235 pdpRequests.compute(update.getName(), (name, data) -> Pair.of(update, (data == null ? null : data.getRight())));
239 * Adds a state-change to the set of state-change requests, replacing any previous entry for the given PDP.
241 * @param change the state-change to be added
243 public void addStateChange(PdpStateChange change) {
244 logger.info("add state-change {}", change.getName());
245 pdpRequests.compute(change.getName(), (name, data) -> Pair.of((data == null ? null : data.getLeft()), change));
249 * Determines if any changes were made due to the REST call.
251 * @return {@code true} if nothing was changed, {@code false} if something was changed
253 public boolean isUnchanged() {
254 return groupCache.values().stream().allMatch(GroupData::isUnchanged);
258 * Gets the accumulated PDP requests.
260 * @return the PDP requests
262 public Collection<Pair<PdpUpdate, PdpStateChange>> getPdpRequests() {
263 return pdpRequests.values();
267 * Gets the accumulated PDP update requests.
269 * @return the PDP requests
271 public List<PdpUpdate> getPdpUpdates() {
272 return pdpRequests.values().stream().filter(req -> req.getLeft() != null).map(Pair::getLeft)
273 .collect(Collectors.toList());
277 * Gets the accumulated PDP state-change requests.
279 * @return the PDP requests
281 public List<PdpStateChange> getPdpStateChanges() {
282 return pdpRequests.values().stream().filter(req -> req.getRight() != null).map(Pair::getRight)
283 .collect(Collectors.toList());
289 * @param newGroup the new group
291 public void create(PdpGroup newGroup) {
292 String name = newGroup.getName();
294 if (groupCache.put(name, new GroupData(newGroup, true)) != null) {
295 throw new IllegalStateException("group already cached: " + name);
298 logger.info("create cached group {}", newGroup.getName());
304 * @param newGroup the updated group
306 public void update(PdpGroup newGroup) {
307 String name = newGroup.getName();
308 GroupData data = groupCache.get(name);
310 throw new IllegalStateException("group not cached: " + name);
313 logger.info("update cached group {}", newGroup.getName());
314 data.update(newGroup);
318 * Gets the group by the given name.
320 * @param name name of the group to get
321 * @return the group, or {@code null} if it does not exist
322 * @throws PfModelException if an error occurred
324 public PdpGroup getGroup(String name) throws PfModelException {
326 GroupData data = groupCache.get(name);
328 List<PdpGroup> lst = dao.getPdpGroups(name);
330 logger.info("unknown group {}", name);
334 logger.info("cache group {}", name);
335 data = new GroupData(lst.get(0));
336 groupCache.put(name, data);
338 deployStatus.loadByGroup(name);
341 logger.info("use cached group {}", name);
344 return data.getGroup();
348 * Gets the active groups supporting the given policy.
350 * @param type desired policy type
351 * @return the active groups supporting the given policy
352 * @throws PfModelException if an error occurred
354 public List<PdpGroup> getActivePdpGroupsByPolicyType(ToscaConceptIdentifier type) throws PfModelException {
356 * Cannot use computeIfAbsent() because the enclosed code throws an unchecked exception and handling that would
357 * obfuscate the code too much, thus disabling the sonar.
359 List<GroupData> data = type2groups.get(type); // NOSONAR
361 PdpGroupFilter filter = PdpGroupFilter.builder().policyTypeList(Collections.singletonList(type))
362 .groupState(PdpState.ACTIVE).build();
364 List<PdpGroup> groups = dao.getFilteredPdpGroups(filter);
366 data = groups.stream().map(this::addGroup).collect(Collectors.toList());
367 type2groups.put(type, data);
370 return data.stream().map(GroupData::getGroup).collect(Collectors.toList());
374 * Gets the list of policies to be deployed to the PDPs.
376 * @returns a list of policies to be deployed
378 public List<ToscaPolicy> getPoliciesToBeDeployed() {
379 return new LinkedList<>(this.policiesToBeDeployed.values());
383 * Gets the list of policies to be undeployed from the PDPs.
385 * @returns a list of policies to be undeployed
387 public List<ToscaConceptIdentifier> getPoliciesToBeUndeployed() {
388 return new LinkedList<>(this.policiesToBeUndeployed);
392 * Adds a group to the group cache, if it isn't already in the cache.
394 * @param group the group to be added
395 * @return the cache entry
397 private GroupData addGroup(PdpGroup group) {
398 GroupData data = groupCache.get(group.getName());
400 logger.info("cache group {}", group.getName());
401 data = new GroupData(group);
402 groupCache.put(group.getName(), data);
405 logger.info("use cached group {}", group.getName());
412 * Update the DB with the changes.
414 * @param notification notification to which to add policy status
416 * @throws PfModelException if an error occurred
418 public void updateDb(PolicyNotification notification) throws PfModelException {
420 List<GroupData> created = groupCache.values().stream().filter(GroupData::isNew).collect(Collectors.toList());
421 if (!created.isEmpty()) {
422 if (logger.isInfoEnabled()) {
423 created.forEach(group -> logger.info("creating DB group {}", group.getGroup().getName()));
425 dao.createPdpGroups(created.stream().map(GroupData::getGroup).collect(Collectors.toList()));
428 // update existing groups
429 List<GroupData> updated =
430 groupCache.values().stream().filter(GroupData::isUpdated).collect(Collectors.toList());
431 if (!updated.isEmpty()) {
432 if (logger.isInfoEnabled()) {
433 updated.forEach(group -> logger.info("updating DB group {}", group.getGroup().getName()));
435 dao.updatePdpGroups(updated.stream().map(GroupData::getGroup).collect(Collectors.toList()));
438 // flush deployment status records to the DB
439 deployStatus.flush(notification);
443 * Deletes a group from the DB, immediately (i.e., without caching the request to be executed later).
445 * @param group the group to be deleted
446 * @throws PfModelException if an error occurred
448 public void deleteGroupFromDb(PdpGroup group) throws PfModelException {
449 logger.info("deleting DB group {}", group.getName());
450 dao.deletePdpGroup(group.getName());
454 * Adds policy deployment data.
456 * @param policy policy being deployed
457 * @param pdps PDPs to which the policy is being deployed
458 * @param pdpGroup PdpGroup containing the PDP of interest
459 * @param pdpType PDP type (i.e., PdpSubGroup) containing the PDP of interest
460 * @throws PfModelException if an error occurred
462 protected void trackDeploy(ToscaPolicy policy, Collection<String> pdps, String pdpGroup,
463 String pdpType) throws PfModelException {
464 ToscaConceptIdentifier policyId = policy.getIdentifier();
465 policiesToBeDeployed.put(policyId, policy);
467 addData(policyId, pdps, pdpGroup, pdpType, true);
471 * Adds policy undeployment data.
473 * @param policyId ID of the policy being undeployed
474 * @param pdps PDPs to which the policy is being undeployed
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 trackUndeploy(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup,
480 String pdpType) throws PfModelException {
481 policiesToBeUndeployed.add(policyId);
482 addData(policyId, pdps, pdpGroup, pdpType, false);
486 * Adds policy deployment/undeployment data.
488 * @param policyId ID of the policy being deployed/undeployed
489 * @param pdps PDPs to which the policy is being deployed/undeployed
490 * @param deploy {@code true} if the policy is being deployed, {@code false} if undeployed
491 * @param pdpGroup PdpGroup containing the PDP of interest
492 * @param pdpType PDP type (i.e., PdpSubGroup) containing the PDP of interest
493 * @throws PfModelException if an error occurred
495 private void addData(ToscaConceptIdentifier policyId, Collection<String> pdps, String pdpGroup, String pdpType,
496 boolean deploy) throws PfModelException {
498 // delete all records whose "deploy" flag is the opposite of what we want
499 deployStatus.deleteDeployment(policyId, !deploy);
501 var optid = new ToscaConceptIdentifierOptVersion(policyId);
502 ToscaConceptIdentifier policyType = getPolicy(optid).getTypeIdentifier();
504 for (String pdp : pdps) {
505 deployStatus.deploy(pdp, policyId, policyType, pdpGroup, pdpType, deploy);
509 // these may be overridden by junit tests
511 protected DeploymentStatus makeDeploymentStatus(PolicyModelsProvider dao) {
512 return new DeploymentStatus(dao);