2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.pap.main.rest.depundep;
23 import com.att.aft.dme2.internal.apache.commons.lang.ObjectUtils;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
31 import java.util.function.Consumer;
32 import java.util.stream.Collectors;
33 import javax.ws.rs.core.Response.Status;
34 import org.onap.policy.common.parameters.BeanValidationResult;
35 import org.onap.policy.common.parameters.ObjectValidationResult;
36 import org.onap.policy.common.parameters.ValidationResult;
37 import org.onap.policy.common.parameters.ValidationStatus;
38 import org.onap.policy.common.utils.services.Registry;
39 import org.onap.policy.models.base.PfModelException;
40 import org.onap.policy.models.base.PfModelRuntimeException;
41 import org.onap.policy.models.pap.concepts.PdpDeployPolicies;
42 import org.onap.policy.models.pdp.concepts.Pdp;
43 import org.onap.policy.models.pdp.concepts.PdpGroup;
44 import org.onap.policy.models.pdp.concepts.PdpGroups;
45 import org.onap.policy.models.pdp.concepts.PdpStateChange;
46 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
47 import org.onap.policy.models.pdp.concepts.PdpUpdate;
48 import org.onap.policy.models.pdp.enums.PdpState;
49 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
50 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier;
51 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifierOptVersion;
52 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * Provider for PAP component to deploy PDP groups. The following items must be in the
60 * <li>PDP Modification Lock</li>
61 * <li>PDP Modify Request Map</li>
62 * <li>PAP DAO Factory</li>
65 public class PdpGroupDeployProvider extends ProviderBase {
66 private static final Logger logger = LoggerFactory.getLogger(PdpGroupDeployProvider.class);
68 private static final String POLICY_RESULT_NAME = "policy";
72 * Constructs the object.
74 public PdpGroupDeployProvider() {
79 * Creates or updates PDP groups.
81 * @param groups PDP group configurations to be created or updated
82 * @throws PfModelException if an error occurred
84 public void createOrUpdateGroups(PdpGroups groups) throws PfModelException {
85 ValidationResult result = groups.validatePapRest();
87 if (!result.isValid()) {
88 String msg = result.getResult().trim();
90 throw new PfModelException(Status.BAD_REQUEST, msg);
93 process(groups, this::createOrUpdate);
97 * Creates or updates PDP groups. This is the method that does the actual work.
99 * @param data session data
100 * @param groups PDP group configurations
101 * @throws PfModelException if an error occurred
103 private void createOrUpdate(SessionData data, PdpGroups groups) throws PfModelException {
104 BeanValidationResult result = new BeanValidationResult("groups", groups);
106 for (PdpGroup group : groups.getGroups()) {
107 PdpGroup dbgroup = data.getGroup(group.getName());
109 if (dbgroup == null) {
110 result.addResult(addGroup(data, group));
113 result.addResult(updateGroup(data, dbgroup, group));
117 if (!result.isValid()) {
118 throw new PfModelException(Status.BAD_REQUEST, result.getResult().trim());
125 * @param data session data
126 * @param group the group to be added
127 * @return the validation result
128 * @throws PfModelException if an error occurred
130 private ValidationResult addGroup(SessionData data, PdpGroup group) throws PfModelException {
131 BeanValidationResult result = new BeanValidationResult(group.getName(), group);
133 validateGroupOnly(group, result);
134 if (!result.isValid()) {
139 if (group.getPdpGroupState() == null) {
140 group.setPdpGroupState(PdpState.ACTIVE);
143 for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
144 result.addResult(addSubGroup(data, subgrp));
147 if (result.isValid()) {
155 * Performs additional validations of a group, but does not examine the subgroups.
157 * @param group the group to be validated
158 * @param result the validation result
160 private void validateGroupOnly(PdpGroup group, BeanValidationResult result) {
161 if (group.getPdpGroupState() == null) {
165 switch (group.getPdpGroupState()) {
171 result.addResult(new ObjectValidationResult("pdpGroupState", group.getPdpGroupState(),
172 ValidationStatus.INVALID, "must be null, ACTIVE, or PASSIVE"));
178 * Updates an existing group.
180 * @param data session data
181 * @param dbgroup the group, as it appears within the DB
182 * @param group the group to be added
183 * @return the validation result
184 * @throws PfModelException if an error occurred
186 private ValidationResult updateGroup(SessionData data, PdpGroup dbgroup, PdpGroup group) throws PfModelException {
187 BeanValidationResult result = new BeanValidationResult(group.getName(), group);
189 if (!ObjectUtils.equals(dbgroup.getProperties(), group.getProperties())) {
190 result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
191 "cannot change properties"));
194 boolean updated = updateField(dbgroup.getDescription(), group.getDescription(), dbgroup::setDescription);
195 updated = notifyPdpsDelSubGroups(data, dbgroup, group) || updated;
196 updated = addOrUpdateSubGroups(data, dbgroup, group, result) || updated;
198 if (result.isValid() && updated) {
206 * Updates a field, if the new value is different than the old value.
208 * @param oldValue old value
209 * @param newValue new value
210 * @param setter function to set the field to the new value
211 * @return {@code true} if the field was updated, {@code false} if it already matched
214 private <T> boolean updateField(T oldValue, T newValue, Consumer<T> setter) {
215 if (oldValue == newValue) {
219 if (oldValue != null && oldValue.equals(newValue)) {
223 setter.accept(newValue);
228 * Adds or updates subgroups within the group.
230 * @param data session data
231 * @param dbgroup the group, as it appears within the DB
232 * @param group the group to be added
233 * @param result the validation result
234 * @return {@code true} if the DB group was modified, {@code false} otherwise
235 * @throws PfModelException if an error occurred
237 private boolean addOrUpdateSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group,
238 BeanValidationResult result) throws PfModelException {
240 // create a map of existing subgroups
241 Map<String, PdpSubGroup> type2sub = new HashMap<>();
242 dbgroup.getPdpSubgroups().forEach(subgrp -> type2sub.put(subgrp.getPdpType(), subgrp));
244 boolean updated = false;
246 for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
247 PdpSubGroup dbsub = type2sub.get(subgrp.getPdpType());
248 BeanValidationResult subResult = new BeanValidationResult(subgrp.getPdpType(), subgrp);
252 subResult.addResult(addSubGroup(data, subgrp));
253 dbgroup.getPdpSubgroups().add(subgrp);
256 updated = updateSubGroup(data, group, dbsub, subgrp, subResult) || updated;
259 result.addResult(subResult);
266 * Notifies any PDPs whose subgroups are being removed.
268 * @param data session data
269 * @param dbgroup the group, as it appears within the DB
270 * @param group the group being updated
271 * @return {@code true} if a subgroup was removed, {@code false} otherwise
272 * @throws PfModelException if an error occurred
274 private boolean notifyPdpsDelSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group) throws PfModelException {
275 boolean updated = false;
277 // subgroups, as they appear within the updated group
278 Set<String> subgroups = new HashSet<>();
279 group.getPdpSubgroups().forEach(subgrp -> subgroups.add(subgrp.getPdpType()));
281 // loop through subgroups as they appear within the DB
282 for (PdpSubGroup subgrp : dbgroup.getPdpSubgroups()) {
284 if (!subgroups.contains(subgrp.getPdpType())) {
285 // this subgroup no longer appears - notify its PDPs
287 notifyPdpsDelSubGroup(data, subgrp);
288 trackPdpsDelSubGroup(data, subgrp);
296 * Notifies the PDPs that their subgroup is being removed.
298 * @param data session data
299 * @param subgrp subgroup that is being removed
301 private void notifyPdpsDelSubGroup(SessionData data, PdpSubGroup subgrp) {
302 for (Pdp pdp : subgrp.getPdpInstances()) {
303 String name = pdp.getInstanceId();
306 PdpStateChange change = new PdpStateChange();
307 change.setName(name);
308 change.setState(PdpState.PASSIVE);
310 // remove it from subgroup and undeploy all policies
311 PdpUpdate update = new PdpUpdate();
312 update.setName(name);
314 data.addRequests(update, change);
319 * Tracks PDP responses when their subgroup is removed.
321 * @param data session data
322 * @param subgrp subgroup that is being removed
323 * @throws PfModelException if an error occurred
325 private void trackPdpsDelSubGroup(SessionData data, PdpSubGroup subgrp) throws PfModelException {
326 Set<String> pdps = subgrp.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
328 for (ToscaPolicyIdentifier policyId : subgrp.getPolicies()) {
329 data.trackUndeploy(policyId, pdps);
334 * Adds a new subgroup.
336 * @param data session data
337 * @param subgrp the subgroup to be added, updated to fully qualified versions upon
339 * @return the validation result
340 * @throws PfModelException if an error occurred
342 private ValidationResult addSubGroup(SessionData data, PdpSubGroup subgrp) throws PfModelException {
343 subgrp.setCurrentInstanceCount(0);
344 subgrp.setPdpInstances(Collections.emptyList());
346 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
348 result.addResult(validateSupportedTypes(data, subgrp));
349 result.addResult(validatePolicies(data, null, subgrp));
355 * Updates an existing subgroup.
357 * @param data session data
358 * @param dbgroup the group, from the DB, containing the subgroup
359 * @param dbsub the subgroup, from the DB
360 * @param subgrp the subgroup to be updated, updated to fully qualified versions upon
362 * @param container container for additional validation results
363 * @return {@code true} if the subgroup content was changed, {@code false} if there
365 * @throws PfModelException if an error occurred
367 private boolean updateSubGroup(SessionData data, PdpGroup dbgroup, PdpSubGroup dbsub, PdpSubGroup subgrp,
368 BeanValidationResult container) throws PfModelException {
370 // perform additional validations first
371 if (!validateSubGroup(data, dbsub, subgrp, container)) {
376 * first, apply the changes about which the PDPs care
378 boolean updated = updatePolicies(data, dbsub, subgrp);
380 // publish any changes to the PDPs
382 makeUpdates(data, dbgroup, dbsub);
386 * now make any remaining changes
388 updated = updateList(dbsub.getSupportedPolicyTypes(), subgrp.getSupportedPolicyTypes(),
389 dbsub::setSupportedPolicyTypes) || updated;
391 return updateField(dbsub.getDesiredInstanceCount(), subgrp.getDesiredInstanceCount(),
392 dbsub::setDesiredInstanceCount) || updated;
395 private boolean updatePolicies(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp) throws PfModelException {
396 Set<ToscaPolicyIdentifier> undeployed = new HashSet<>(dbsub.getPolicies());
397 undeployed.removeAll(subgrp.getPolicies());
399 Set<ToscaPolicyIdentifier> deployed = new HashSet<>(subgrp.getPolicies());
400 deployed.removeAll(dbsub.getPolicies());
402 if (deployed.isEmpty() && undeployed.isEmpty()) {
403 // lists are identical
408 Set<String> pdps = subgrp.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
410 for (ToscaPolicyIdentifier policyId : deployed) {
411 data.trackDeploy(policyId, pdps);
414 for (ToscaPolicyIdentifier policyId : undeployed) {
415 data.trackUndeploy(policyId, pdps);
418 dbsub.setPolicies(new ArrayList<>(subgrp.getPolicies()));
423 * Performs additional validations of a subgroup.
425 * @param data session data
426 * @param dbsub the subgroup, from the DB
427 * @param subgrp the subgroup to be validated, updated to fully qualified versions
429 * @param container container for additional validation results
430 * @return {@code true} if the subgroup is valid, {@code false} otherwise
431 * @throws PfModelException if an error occurred
433 private boolean validateSubGroup(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp,
434 BeanValidationResult container) throws PfModelException {
436 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
438 if (!ObjectUtils.equals(dbsub.getProperties(), subgrp.getProperties())) {
439 result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
440 "cannot change properties"));
443 result.addResult(validatePolicies(data, dbsub, subgrp));
444 container.addResult(result);
446 return result.isValid();
450 * Updates a DB list with items from a new list.
452 * @param dblist the list from the DB
453 * @param newList the new list
454 * @param setter function to set the new list
455 * @return {@code true} if the list changed, {@code false} if the lists were the same
457 private <T> boolean updateList(List<T> dblist, List<T> newList, Consumer<List<T>> setter) {
459 Set<T> dbTypes = new HashSet<>(dblist);
460 Set<T> newTypes = new HashSet<>(newList);
462 if (dbTypes.equals(newTypes)) {
466 setter.accept(new ArrayList<>(newTypes));
472 * Performs additional validations of the supported policy types within a subgroup.
474 * @param data session data
475 * @param subgrp the subgroup to be validated
476 * @param result the validation result
477 * @throws PfModelException if an error occurred
479 private ValidationResult validateSupportedTypes(SessionData data, PdpSubGroup subgrp) throws PfModelException {
480 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
482 for (ToscaPolicyTypeIdentifier type : subgrp.getSupportedPolicyTypes()) {
483 if (data.getPolicyType(type) == null) {
484 result.addResult(new ObjectValidationResult("policy type", type, ValidationStatus.INVALID,
485 "unknown policy type"));
493 * Performs additional validations of the policies within a subgroup.
495 * @param data session data
496 * @param dbsub subgroup from the DB, or {@code null} if this is a new subgroup
497 * @param subgrp the subgroup whose policies are to be validated, updated to fully
498 * qualified versions upon return
499 * @param result the validation result
500 * @throws PfModelException if an error occurred
502 private ValidationResult validatePolicies(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp)
503 throws PfModelException {
505 // build a map of the DB data, from policy name to (fully qualified) policy
507 Map<String, String> dbname2vers = new HashMap<>();
509 dbsub.getPolicies().forEach(ident -> dbname2vers.put(ident.getName(), ident.getVersion()));
512 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
514 for (ToscaPolicyIdentifier ident : subgrp.getPolicies()) {
515 // note: "ident" may not have a fully qualified version
517 String expectedVersion = dbname2vers.get(ident.getName());
518 if (expectedVersion != null) {
519 // policy exists in the DB list - compare the versions
520 validateVersion(expectedVersion, ident, result);
521 ident.setVersion(expectedVersion);
525 // policy doesn't appear in the DB's policy list - look it up
527 ToscaPolicy policy = data.getPolicy(new ToscaPolicyIdentifierOptVersion(ident));
528 if (policy == null) {
529 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
532 } else if (!subgrp.getSupportedPolicyTypes().contains(policy.getTypeIdentifier())) {
533 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
534 "not a supported policy for the subgroup"));
537 // replace version with the fully qualified version from the policy
538 ident.setVersion(policy.getVersion());
546 * Determines if the new version matches the version in the DB.
548 * @param dbvers fully qualified version from the DB
549 * @param ident identifier whose version is to be validated; the version need not be
551 * @param result the validation result
553 private void validateVersion(String dbvers, ToscaPolicyIdentifier ident, BeanValidationResult result) {
554 String idvers = ident.getVersion();
555 if (dbvers.equals(idvers)) {
559 // did not match - see if it's a prefix
561 if (SessionData.isVersionPrefix(idvers) && dbvers.startsWith(idvers + ".")) {
562 // ident has a prefix of this version
566 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
567 "different version already deployed: " + dbvers));
571 * Deploys or updates PDP policies using the simple API.
573 * @param policies PDP policies
574 * @throws PfModelException if an error occurred
576 public void deployPolicies(PdpDeployPolicies policies) throws PfModelException {
577 process(policies, this::deploySimplePolicies);
581 * Deploys or updates PDP policies using the simple API. This is the method that does
584 * @param data session data
585 * @param extPolicies external PDP policies
586 * @return a list of requests that should be sent to configure the PDPs
587 * @throws PfModelException if an error occurred
589 private void deploySimplePolicies(SessionData data, PdpDeployPolicies policies) throws PfModelException {
591 for (ToscaPolicyIdentifierOptVersion desiredPolicy : policies.getPolicies()) {
594 processPolicy(data, desiredPolicy);
596 } catch (PfModelException | RuntimeException e) {
597 // no need to log the error here, as it will be logged by the invoker
598 logger.warn("failed to deploy policy: {}", desiredPolicy);
605 * Adds a policy to a subgroup, if it isn't there already.
608 protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
609 ToscaPolicyIdentifierOptVersion requestedIdent) {
611 ToscaPolicyIdentifier desiredIdent = policy.getIdentifier();
612 ToscaPolicyTypeIdentifier desiredType = policy.getTypeIdentifier();
614 return (group, subgroup) -> {
616 if (!subgroup.getSupportedPolicyTypes().contains(desiredType)) {
617 // doesn't support the desired policy type
621 if (containsPolicy(group, subgroup, desiredIdent)) {
625 if (subgroup.getPdpInstances().isEmpty()) {
626 throw new PfModelRuntimeException(Status.BAD_REQUEST, "group " + group.getName() + " subgroup "
627 + subgroup.getPdpType() + " has no active PDPs");
631 // add the policy to the subgroup
632 subgroup.getPolicies().add(desiredIdent);
634 logger.info("add policy {} {} to subgroup {} {} count={}", desiredIdent.getName(),
635 desiredIdent.getVersion(), group.getName(), subgroup.getPdpType(),
636 subgroup.getPolicies().size());
638 Set<String> pdps = subgroup.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
639 data.trackDeploy(desiredIdent, pdps);
646 * Determines if a subgroup already contains the desired policy.
648 * @param group group that contains the subgroup
649 * @param subgroup subgroup of interest
650 * @param desiredIdent identifier of the desired policy
651 * @return {@code true} if the subgroup contains the desired policy, {@code false}
653 * @throws PfModelRuntimeException if the subgroup contains a different version of the
656 private boolean containsPolicy(PdpGroup group, PdpSubGroup subgroup, ToscaPolicyIdentifier desiredIdent) {
657 String desnm = desiredIdent.getName();
658 String desvers = desiredIdent.getVersion();
660 for (ToscaPolicyIdentifier actualIdent : subgroup.getPolicies()) {
661 if (!actualIdent.getName().equals(desnm)) {
665 // found the policy - ensure the version matches
666 if (!actualIdent.getVersion().equals(desvers)) {
667 throw new PfModelRuntimeException(Status.BAD_REQUEST,
668 "group " + group.getName() + " subgroup " + subgroup.getPdpType() + " policy " + desnm
669 + " " + desvers + " different version already deployed: "
670 + actualIdent.getVersion());
673 // already has the desired policy & version
674 logger.info("subgroup {} {} already contains policy {} {}", group.getName(), subgroup.getPdpType(), desnm,