Fix new checkstyle issues in pap
[policy/pap.git] / main / src / main / java / org / onap / policy / pap / main / rest / PdpGroupDeployProvider.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP PAP
4  * ================================================================================
5  * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2020-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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  */
21
22 package org.onap.policy.pap.main.rest;
23
24 import com.google.gson.annotations.SerializedName;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33 import javax.ws.rs.core.Response.Status;
34 import lombok.Getter;
35 import org.onap.policy.common.parameters.BeanValidationResult;
36 import org.onap.policy.common.parameters.BeanValidator;
37 import org.onap.policy.common.parameters.ValidationResult;
38 import org.onap.policy.common.parameters.ValidationStatus;
39 import org.onap.policy.common.parameters.annotations.NotNull;
40 import org.onap.policy.common.parameters.annotations.Pattern;
41 import org.onap.policy.common.parameters.annotations.Valid;
42 import org.onap.policy.common.utils.coder.CoderException;
43 import org.onap.policy.common.utils.coder.StandardCoder;
44 import org.onap.policy.common.utils.services.Registry;
45 import org.onap.policy.models.base.PfKey;
46 import org.onap.policy.models.base.PfModelException;
47 import org.onap.policy.models.base.PfModelRuntimeException;
48 import org.onap.policy.models.pap.concepts.PdpDeployPolicies;
49 import org.onap.policy.models.pdp.concepts.DeploymentGroup;
50 import org.onap.policy.models.pdp.concepts.DeploymentGroups;
51 import org.onap.policy.models.pdp.concepts.DeploymentSubGroup;
52 import org.onap.policy.models.pdp.concepts.Pdp;
53 import org.onap.policy.models.pdp.concepts.PdpGroup;
54 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
55 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
56 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifierOptVersion;
57 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 /**
62  * Provider for PAP component to deploy PDP groups. The following items must be in the
63  * {@link Registry}:
64  * <ul>
65  * <li>PDP Modification Lock</li>
66  * <li>PDP Modify Request Map</li>
67  * <li>PAP DAO Factory</li>
68  * </ul>
69  */
70 public class PdpGroupDeployProvider extends ProviderBase {
71     private static final Logger logger = LoggerFactory.getLogger(PdpGroupDeployProvider.class);
72     private static final StandardCoder coder = new StandardCoder();
73
74     private static final String POLICY_RESULT_NAME = "policy";
75
76
77     /**
78      * Constructs the object.
79      */
80     public PdpGroupDeployProvider() {
81         super();
82     }
83
84     /**
85      * Updates policies in specific PDP groups.
86      *
87      * @param groups PDP group deployments to be updated
88      * @throws PfModelException if an error occurred
89      */
90     public void updateGroupPolicies(DeploymentGroups groups) throws PfModelException {
91         ValidationResult result = groups.validatePapRest();
92         if (!result.isValid()) {
93             String msg = result.getResult().trim();
94             throw new PfModelException(Status.BAD_REQUEST, msg);
95         }
96
97         process(groups, this::updateGroups);
98     }
99
100     /**
101      * Updates policies in specific PDP groups. This is the method that does the actual work.
102      *
103      * @param data session data
104      * @param groups PDP group deployments
105      * @throws PfModelException if an error occurred
106      */
107     private void updateGroups(SessionData data, DeploymentGroups groups) throws PfModelException {
108         var result = new BeanValidationResult("groups", groups);
109
110         for (DeploymentGroup group : groups.getGroups()) {
111             PdpGroup dbgroup = data.getGroup(group.getName());
112
113             if (dbgroup == null) {
114                 result.addResult(group.getName(), group, ValidationStatus.INVALID, "unknown group");
115
116             } else {
117                 result.addResult(updateGroup(data, dbgroup, group));
118             }
119         }
120
121         if (!result.isValid()) {
122             throw new PfModelException(Status.BAD_REQUEST, result.getResult().trim());
123         }
124     }
125
126     /**
127      * Updates an existing group.
128      *
129      * @param data session data
130      * @param dbgroup the group, as it appears within the DB
131      * @param group the group to be added
132      * @return the validation result
133      * @throws PfModelException if an error occurred
134      */
135     private ValidationResult updateGroup(SessionData data, PdpGroup dbgroup, DeploymentGroup group)
136                     throws PfModelException {
137
138         var result = new BeanValidationResult(group.getName(), group);
139
140         boolean updated = updateSubGroups(data, dbgroup, group, result);
141
142         if (result.isValid() && updated) {
143             data.update(dbgroup);
144         }
145
146         return result;
147     }
148
149     /**
150      * Adds or updates subgroups within the group.
151      *
152      * @param data session data
153      * @param dbgroup the group, as it appears within the DB
154      * @param group the group to be added
155      * @param result the validation result
156      * @return {@code true} if the DB group was modified, {@code false} otherwise
157      * @throws PfModelException if an error occurred
158      */
159     private boolean updateSubGroups(SessionData data, PdpGroup dbgroup, DeploymentGroup group,
160                     BeanValidationResult result) throws PfModelException {
161
162         // create a map of existing subgroups
163         Map<String, PdpSubGroup> type2sub = new HashMap<>();
164         dbgroup.getPdpSubgroups().forEach(subgrp -> type2sub.put(subgrp.getPdpType(), subgrp));
165
166         var updated = false;
167
168         for (DeploymentSubGroup subgrp : group.getDeploymentSubgroups()) {
169             PdpSubGroup dbsub = type2sub.get(subgrp.getPdpType());
170             var subResult = new BeanValidationResult(subgrp.getPdpType(), subgrp);
171
172             if (dbsub == null) {
173                 subResult.addResult(subgrp.getPdpType(), subgrp, ValidationStatus.INVALID, "unknown subgroup");
174
175             } else {
176                 updated = updateSubGroup(data, dbgroup, dbsub, subgrp, subResult) || updated;
177             }
178
179             result.addResult(subResult);
180         }
181
182         return updated;
183     }
184
185     /**
186      * Updates an existing subgroup.
187      *
188      * @param data session data
189      * @param dbgroup the group, from the DB, containing the subgroup
190      * @param dbsub the subgroup, from the DB
191      * @param subgrp the subgroup to be updated, updated to fully qualified versions upon
192      *        return
193      * @param container container for additional validation results
194      * @return {@code true} if the subgroup content was changed, {@code false} if there
195      *         were no changes
196      * @throws PfModelException if an error occurred
197      */
198     private boolean updateSubGroup(SessionData data, PdpGroup dbgroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp,
199                     BeanValidationResult container) throws PfModelException {
200
201         // perform additional validations first
202         if (!validateSubGroup(data, dbsub, subgrp, container)) {
203             return false;
204         }
205
206         var updated = false;
207
208         switch (subgrp.getAction()) {
209             case POST:
210                 updated = addPolicies(data, dbgroup.getName(), dbsub, subgrp);
211                 break;
212             case DELETE:
213                 updated = deletePolicies(data, dbgroup.getName(), dbsub, subgrp);
214                 break;
215             case PATCH:
216             default:
217                 updated = updatePolicies(data, dbgroup.getName(), dbsub, subgrp);
218                 break;
219         }
220
221         if (updated) {
222             // publish any changes to the PDPs
223             makeUpdates(data, dbgroup, dbsub);
224             return true;
225         }
226
227         return false;
228     }
229
230     private boolean addPolicies(SessionData data, String pdpGroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
231                     throws PfModelException {
232
233         Set<ToscaConceptIdentifier> policies = new LinkedHashSet<>(dbsub.getPolicies());
234         policies.addAll(subgrp.getPolicies());
235
236         var subgrp2 = new DeploymentSubGroup(subgrp);
237         subgrp2.getPolicies().clear();
238         subgrp2.getPolicies().addAll(policies);
239
240         return updatePolicies(data, pdpGroup, dbsub, subgrp2);
241     }
242
243     private boolean deletePolicies(SessionData data, String pdpGroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
244                     throws PfModelException {
245
246         Set<ToscaConceptIdentifier> policies = new LinkedHashSet<>(dbsub.getPolicies());
247         policies.removeAll(subgrp.getPolicies());
248
249         var subgrp2 = new DeploymentSubGroup(subgrp);
250         subgrp2.getPolicies().clear();
251         subgrp2.getPolicies().addAll(policies);
252
253         return updatePolicies(data, pdpGroup, dbsub, subgrp2);
254     }
255
256     private boolean updatePolicies(SessionData data, String pdpGroup, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
257                     throws PfModelException {
258
259         Set<ToscaConceptIdentifier> undeployed = new HashSet<>(dbsub.getPolicies());
260         undeployed.removeAll(subgrp.getPolicies());
261
262         Set<ToscaConceptIdentifier> deployed = new HashSet<>(subgrp.getPolicies());
263         deployed.removeAll(dbsub.getPolicies());
264
265         if (deployed.isEmpty() && undeployed.isEmpty()) {
266             // lists are identical
267             return false;
268         }
269
270         Set<String> pdps = dbsub.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
271
272         for (ToscaConceptIdentifier policyId : deployed) {
273             ToscaPolicy policyToBeDeployed = data.getPolicy(new ToscaConceptIdentifierOptVersion(policyId));
274             data.trackDeploy(policyToBeDeployed, pdps, pdpGroup, dbsub.getPdpType());
275         }
276
277         for (ToscaConceptIdentifier policyId : undeployed) {
278             data.trackUndeploy(policyId, pdps, pdpGroup, dbsub.getPdpType());
279         }
280
281         dbsub.setPolicies(new ArrayList<>(subgrp.getPolicies()));
282         return true;
283     }
284
285     /**
286      * Performs additional validations of a subgroup.
287      *
288      * @param data session data
289      * @param dbsub the subgroup, from the DB
290      * @param subgrp the subgroup to be validated, updated to fully qualified versions
291      *        upon return
292      * @param container container for additional validation results
293      * @return {@code true} if the subgroup is valid, {@code false} otherwise
294      * @throws PfModelException if an error occurred
295      */
296     private boolean validateSubGroup(SessionData data, PdpSubGroup dbsub, DeploymentSubGroup subgrp,
297                     BeanValidationResult container) throws PfModelException {
298
299         var result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
300
301         result.addResult(validatePolicies(data, dbsub, subgrp));
302         container.addResult(result);
303
304         return result.isValid();
305     }
306
307     /**
308      * Performs additional validations of the policies within a subgroup.
309      *
310      * @param data session data
311      * @param dbsub subgroup from the DB, or {@code null} if this is a new subgroup
312      * @param subgrp the subgroup whose policies are to be validated, updated to fully
313      *        qualified versions upon return
314      * @return the validation result
315      * @throws PfModelException if an error occurred
316      */
317     private ValidationResult validatePolicies(SessionData data, PdpSubGroup dbsub, DeploymentSubGroup subgrp)
318                     throws PfModelException {
319
320         // build a map of the DB data, from policy name to (fully qualified) policy
321         // version
322         Map<String, String> dbname2vers = new HashMap<>();
323         dbsub.getPolicies().forEach(ident -> dbname2vers.put(ident.getName(), ident.getVersion()));
324
325         var result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
326
327         for (ToscaConceptIdentifier ident : subgrp.getPolicies()) {
328             // note: "ident" may not have a fully qualified version
329
330             String expectedVersion = dbname2vers.get(ident.getName());
331             if (expectedVersion != null) {
332                 // policy exists in the DB list - compare the versions
333                 validateVersion(expectedVersion, ident, result);
334                 ident.setVersion(expectedVersion);
335                 continue;
336             }
337
338             // policy doesn't appear in the DB's policy list - look it up
339
340             ToscaPolicy policy = data.getPolicy(new ToscaConceptIdentifierOptVersion(ident));
341             if (policy == null) {
342                 result.addResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID, "unknown policy");
343
344             } else if (!isPolicySupported(dbsub.getSupportedPolicyTypes(), policy.getTypeIdentifier())) {
345                 result.addResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
346                                 "not a supported policy for the subgroup");
347
348             } else {
349                 // replace version with the fully qualified version from the policy
350                 ident.setVersion(policy.getVersion());
351             }
352         }
353
354         return result;
355     }
356
357     /**
358      * Determines if the new version matches the version in the DB.
359      *
360      * @param dbvers fully qualified version from the DB
361      * @param ident identifier whose version is to be validated; the version need not be
362      *        fully qualified
363      * @param result the validation result
364      */
365     private void validateVersion(String dbvers, ToscaConceptIdentifier ident, BeanValidationResult result) {
366         String idvers = ident.getVersion();
367         if (dbvers.equals(idvers)) {
368             return;
369         }
370
371         // did not match - see if it's a prefix
372
373         if (SessionData.isVersionPrefix(idvers) && dbvers.startsWith(idvers + ".")) {
374             // ident has a prefix of this version
375             return;
376         }
377
378         result.addResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
379                         "different version already deployed: " + dbvers);
380     }
381
382     /**
383      * Deploys or updates PDP policies using the simple API.
384      *
385      * @param policies PDP policies
386      * @throws PfModelException if an error occurred
387      */
388     public void deployPolicies(PdpDeployPolicies policies) throws PfModelException {
389         try {
390             MyPdpDeployPolicies checked = coder.convert(policies, MyPdpDeployPolicies.class);
391             ValidationResult result = new BeanValidator().validateTop(PdpDeployPolicies.class.getSimpleName(), checked);
392             if (!result.isValid()) {
393                 String msg = result.getResult().trim();
394                 throw new PfModelException(Status.BAD_REQUEST, msg);
395             }
396         } catch (CoderException e) {
397             throw new PfModelException(Status.INTERNAL_SERVER_ERROR, "cannot decode request", e);
398         }
399
400         process(policies, this::deploySimplePolicies);
401     }
402
403     /**
404      * Deploys or updates PDP policies using the simple API. This is the method that does
405      * the actual work.
406      *
407      * @param data session data
408      * @param policies external PDP policies
409      * @throws PfModelException if an error occurred
410      */
411     private void deploySimplePolicies(SessionData data, PdpDeployPolicies policies) throws PfModelException {
412
413         for (ToscaConceptIdentifierOptVersion desiredPolicy : policies.getPolicies()) {
414
415             try {
416                 processPolicy(data, desiredPolicy);
417
418             } catch (PfModelException | RuntimeException e) {
419                 // no need to log the error here, as it will be logged by the invoker
420                 logger.warn("failed to deploy policy: {}", desiredPolicy);
421                 throw e;
422             }
423         }
424     }
425
426     /**
427      * Adds a policy to a subgroup, if it isn't there already.
428      */
429     @Override
430     protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
431                     ToscaConceptIdentifierOptVersion requestedIdent) {
432
433         ToscaConceptIdentifier desiredIdent = policy.getIdentifier();
434         ToscaConceptIdentifier desiredType = policy.getTypeIdentifier();
435
436         return (group, subgroup) -> {
437
438             if (!isPolicySupported(subgroup.getSupportedPolicyTypes(), desiredType)) {
439                 // doesn't support the desired policy type
440                 return false;
441             }
442
443             if (containsPolicy(group, subgroup, desiredIdent)) {
444                 return false;
445             }
446
447             if (subgroup.getPdpInstances().isEmpty()) {
448                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "group " + group.getName() + " subgroup "
449                                 + subgroup.getPdpType() + " has no active PDPs");
450             }
451
452
453             // add the policy to the subgroup
454             subgroup.getPolicies().add(desiredIdent);
455
456             logger.info("add policy {} to subgroup {} {} count={}", desiredIdent, group.getName(),
457                             subgroup.getPdpType(), subgroup.getPolicies().size());
458
459             Set<String> pdps = subgroup.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
460             ToscaPolicy policyToBeDeployed = data.getPolicy(new ToscaConceptIdentifierOptVersion(desiredIdent));
461             data.trackDeploy(policyToBeDeployed, pdps, group.getName(), subgroup.getPdpType());
462
463             return true;
464         };
465     }
466
467     /**
468      * Determines if a policy type is supported.
469      *
470      * @param supportedTypes supported policy types, any of which may end with ".*"
471      * @param desiredType policy type of interest
472      * @return {@code true} if the policy type is supported, {@code false} otherwise
473      */
474     private boolean isPolicySupported(List<ToscaConceptIdentifier> supportedTypes,
475                     ToscaConceptIdentifier desiredType) {
476
477         if (supportedTypes.contains(desiredType)) {
478             return true;
479         }
480
481         String desiredTypeName = desiredType.getName();
482         for (ToscaConceptIdentifier type : supportedTypes) {
483             String supType = type.getName();
484             if (supType.endsWith(".*") && desiredTypeName.startsWith(supType.substring(0, supType.length() - 1))) {
485                 // matches everything up to, AND INCLUDING, the "."
486                 return true;
487             }
488         }
489
490         return false;
491     }
492
493     /**
494      * Determines if a subgroup already contains the desired policy.
495      *
496      * @param group group that contains the subgroup
497      * @param subgroup subgroup of interest
498      * @param desiredIdent identifier of the desired policy
499      * @return {@code true} if the subgroup contains the desired policy, {@code false}
500      *         otherwise
501      * @throws PfModelRuntimeException if the subgroup contains a different version of the
502      *         desired policy
503      */
504     private boolean containsPolicy(PdpGroup group, PdpSubGroup subgroup, ToscaConceptIdentifier desiredIdent) {
505         String desnm = desiredIdent.getName();
506         String desvers = desiredIdent.getVersion();
507
508         for (ToscaConceptIdentifier actualIdent : subgroup.getPolicies()) {
509             if (!actualIdent.getName().equals(desnm)) {
510                 continue;
511             }
512
513             // found the policy - ensure the version matches
514             if (!actualIdent.getVersion().equals(desvers)) {
515                 throw new PfModelRuntimeException(Status.BAD_REQUEST,
516                                 "group " + group.getName() + " subgroup " + subgroup.getPdpType() + " policy " + desnm
517                                                 + " " + desvers + " different version already deployed: "
518                                                 + actualIdent.getVersion());
519             }
520
521             // already has the desired policy & version
522             logger.info("subgroup {} {} already contains policy {}", group.getName(), subgroup.getPdpType(),
523                         desiredIdent);
524             return true;
525         }
526
527         return false;
528     }
529
530     /*
531      * These are only used to validate the incoming request.
532      */
533
534     @Getter
535     public static class MyPdpDeployPolicies {
536         @NotNull
537         private List<@NotNull @Valid PolicyIdent> policies;
538     }
539
540     @Getter
541     public static class PolicyIdent {
542         @SerializedName("policy-id")
543         @NotNull
544         @Pattern(regexp = PfKey.NAME_REGEXP)
545         private String name;
546
547         @SerializedName("policy-version")
548         @Pattern(regexp = "\\d+([.]\\d+[.]\\d+)?")
549         private String version;
550     }
551 }