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.BiFunction;
32 import java.util.function.Consumer;
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
273 private boolean notifyPdpsDelSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group) {
274 boolean updated = false;
276 // subgroups, as they appear within the updated group
277 Set<String> subgroups = new HashSet<>();
278 group.getPdpSubgroups().forEach(subgrp -> subgroups.add(subgrp.getPdpType()));
280 // loop through subgroups as they appear within the DB
281 for (PdpSubGroup subgrp : dbgroup.getPdpSubgroups()) {
283 if (!subgroups.contains(subgrp.getPdpType())) {
284 // this subgroup no longer appears - notify its PDPs
286 notifyPdpsDelSubGroup(data, subgrp);
294 * Notifies the PDPs that their subgroup is being removed.
296 * @param data session data
297 * @param subgrp subgroup that is being removed
299 private void notifyPdpsDelSubGroup(SessionData data, PdpSubGroup subgrp) {
300 for (Pdp pdp : subgrp.getPdpInstances()) {
301 String name = pdp.getInstanceId();
304 PdpStateChange change = new PdpStateChange();
305 change.setName(name);
306 change.setState(PdpState.PASSIVE);
308 // remove it from subgroup and undeploy all policies
309 PdpUpdate update = new PdpUpdate();
310 update.setName(name);
312 data.addRequests(update, change);
317 * Adds a new subgroup.
319 * @param data session data
320 * @param subgrp the subgroup to be added
321 * @return the validation result
322 * @throws PfModelException if an error occurred
324 private ValidationResult addSubGroup(SessionData data, PdpSubGroup subgrp) throws PfModelException {
325 subgrp.setCurrentInstanceCount(0);
326 subgrp.setPdpInstances(Collections.emptyList());
328 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
330 result.addResult(validateSupportedTypes(data, subgrp));
331 result.addResult(validatePolicies(data, null, subgrp));
337 * Updates an existing subgroup.
339 * @param data session data
340 * @param dbgroup the group, from the DB, containing the subgroup
341 * @param dbsub the subgroup, from the DB
342 * @param subgrp the subgroup to be updated
343 * @param container container for additional validation results
344 * @return {@code true} if the subgroup content was changed, {@code false} if there
346 * @throws PfModelException if an error occurred
348 private boolean updateSubGroup(SessionData data, PdpGroup dbgroup, PdpSubGroup dbsub, PdpSubGroup subgrp,
349 BeanValidationResult container) throws PfModelException {
351 // perform additional validations first
352 if (!validateSubGroup(data, dbsub, subgrp, container)) {
357 * first, apply the changes about which the PDPs care
359 boolean updated = updateList(dbsub.getPolicies(), subgrp.getPolicies(), dbsub::setPolicies);
361 // publish any changes to the PDPs
363 makeUpdates(data, dbgroup, dbsub);
367 * now make any remaining changes
369 updated = updateList(dbsub.getSupportedPolicyTypes(), subgrp.getSupportedPolicyTypes(),
370 dbsub::setSupportedPolicyTypes) || updated;
372 return updateField(dbsub.getDesiredInstanceCount(), subgrp.getDesiredInstanceCount(),
373 dbsub::setDesiredInstanceCount) || updated;
377 * Performs additional validations of a subgroup.
379 * @param data session data
380 * @param dbsub the subgroup, from the DB
381 * @param subgrp the subgroup to be validated
382 * @param container container for additional validation results
383 * @return {@code true} if the subgroup is valid, {@code false} otherwise
384 * @throws PfModelException if an error occurred
386 private boolean validateSubGroup(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp,
387 BeanValidationResult container) throws PfModelException {
389 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
391 if (!ObjectUtils.equals(dbsub.getProperties(), subgrp.getProperties())) {
392 result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
393 "cannot change properties"));
396 result.addResult(validatePolicies(data, dbsub, subgrp));
397 container.addResult(result);
399 return result.isValid();
403 * Updates a DB list with items from a new list.
405 * @param dblist the list from the DB
406 * @param newList the new list
407 * @param setter function to set the new list
408 * @return {@code true} if the list changed, {@code false} if the lists were the same
410 private <T> boolean updateList(List<T> dblist, List<T> newList, Consumer<List<T>> setter) {
412 Set<T> dbTypes = new HashSet<>(dblist);
413 Set<T> newTypes = new HashSet<>(newList);
415 if (dbTypes.equals(newTypes)) {
419 setter.accept(new ArrayList<>(newTypes));
425 * Performs additional validations of the supported policy types within a subgroup.
427 * @param data session data
428 * @param subgrp the subgroup to be validated
429 * @param result the validation result
430 * @throws PfModelException if an error occurred
432 private ValidationResult validateSupportedTypes(SessionData data, PdpSubGroup subgrp) throws PfModelException {
433 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
435 for (ToscaPolicyTypeIdentifier type : subgrp.getSupportedPolicyTypes()) {
436 if (data.getPolicyType(type) == null) {
437 result.addResult(new ObjectValidationResult("policy type", type, ValidationStatus.INVALID,
438 "unknown policy type"));
446 * Performs additional validations of the policies within a subgroup.
448 * @param data session data
449 * @param dbsub subgroup from the DB, or {@code null} if this is a new subgroup
450 * @param subgrp the subgroup to be validated
451 * @param result the validation result
452 * @throws PfModelException if an error occurred
454 private ValidationResult validatePolicies(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp)
455 throws PfModelException {
457 // build a map of the DB data, from policy name to policy version
458 Map<String, String> dbname2vers = new HashMap<>();
460 dbsub.getPolicies().forEach(ident -> dbname2vers.put(ident.getName(), ident.getVersion()));
463 BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
465 for (ToscaPolicyIdentifier ident : subgrp.getPolicies()) {
466 String actualVersion;
468 ToscaPolicy policy = data.getPolicy(new ToscaPolicyIdentifierOptVersion(ident));
469 if (policy == null) {
470 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
473 } else if (!subgrp.getSupportedPolicyTypes().contains(policy.getTypeIdentifier())) {
474 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
475 "not a supported policy for the subgroup"));
477 } else if ((actualVersion = dbname2vers.get(ident.getName())) != null
478 && !actualVersion.equals(ident.getVersion())) {
479 // policy exists in the DB subgroup, but has the wrong version
480 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
481 "different version already deployed: " + actualVersion));
489 * Deploys or updates PDP policies using the simple API.
491 * @param policies PDP policies
492 * @throws PfModelException if an error occurred
494 public void deployPolicies(PdpDeployPolicies policies) throws PfModelException {
495 process(policies, this::deploySimplePolicies);
499 * Deploys or updates PDP policies using the simple API. This is the method that does
502 * @param data session data
503 * @param extPolicies external PDP policies
504 * @return a list of requests that should be sent to configure the PDPs
505 * @throws PfModelException if an error occurred
507 private void deploySimplePolicies(SessionData data, PdpDeployPolicies policies) throws PfModelException {
509 for (ToscaPolicyIdentifierOptVersion desiredPolicy : policies.getPolicies()) {
512 processPolicy(data, desiredPolicy);
514 } catch (PfModelException | RuntimeException e) {
515 // no need to log the error here, as it will be logged by the invoker
516 logger.warn("failed to deploy policy: {}", desiredPolicy);
523 * Adds a policy to a subgroup, if it isn't there already.
526 protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy) {
527 ToscaPolicyIdentifier desiredIdent = policy.getIdentifier();
528 ToscaPolicyTypeIdentifier desiredType = policy.getTypeIdentifier();
530 return (group, subgroup) -> {
532 if (!subgroup.getSupportedPolicyTypes().contains(desiredType)) {
533 // doesn't support the desired policy type
537 if (containsPolicy(group, subgroup, desiredIdent)) {
541 if (subgroup.getPdpInstances().isEmpty()) {
542 throw new PfModelRuntimeException(Status.BAD_REQUEST, "group " + group.getName() + " subgroup "
543 + subgroup.getPdpType() + " has no active PDPs");
547 // add the policy to the subgroup
548 subgroup.getPolicies().add(desiredIdent);
550 logger.info("add policy {} {} to subgroup {} {} count={}", desiredIdent.getName(),
551 desiredIdent.getVersion(), group.getName(), subgroup.getPdpType(),
552 subgroup.getPolicies().size());
558 * Determines if a subgroup already contains the desired policy.
560 * @param group group that contains the subgroup
561 * @param subgroup subgroup of interest
562 * @param desiredIdent identifier of the desired policy
563 * @return {@code true} if the subgroup contains the desired policy, {@code false}
565 * @throws PfModelRuntimeException if the subgroup contains a different version of the
568 private boolean containsPolicy(PdpGroup group, PdpSubGroup subgroup, ToscaPolicyIdentifier desiredIdent) {
569 String desnm = desiredIdent.getName();
570 String desvers = desiredIdent.getVersion();
572 for (ToscaPolicyIdentifier actualIdent : subgroup.getPolicies()) {
573 if (!actualIdent.getName().equals(desnm)) {
577 // found the policy - ensure the version matches
578 if (!actualIdent.getVersion().equals(desvers)) {
579 throw new PfModelRuntimeException(Status.BAD_REQUEST,
580 "group " + group.getName() + " subgroup " + subgroup.getPdpType() + " policy " + desnm
581 + " " + desvers + " different version already deployed: "
582 + actualIdent.getVersion());
585 // already has the desired policy & version
586 logger.info("subgroup {} {} already contains policy {} {}", group.getName(), subgroup.getPdpType(), desnm,