Reject policy deployment with version mismatch
[policy/pap.git] / main / src / main / java / org / onap / policy / pap / main / rest / depundep / PdpGroupDeployProvider.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP PAP
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.policy.pap.main.rest.depundep;
22
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;
29 import java.util.Map;
30 import java.util.Set;
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;
55
56 /**
57  * Provider for PAP component to deploy PDP groups. The following items must be in the
58  * {@link Registry}:
59  * <ul>
60  * <li>PDP Modification Lock</li>
61  * <li>PDP Modify Request Map</li>
62  * <li>PAP DAO Factory</li>
63  * </ul>
64  */
65 public class PdpGroupDeployProvider extends ProviderBase {
66     private static final Logger logger = LoggerFactory.getLogger(PdpGroupDeployProvider.class);
67
68     private static final String POLICY_RESULT_NAME = "policy";
69
70
71     /**
72      * Constructs the object.
73      */
74     public PdpGroupDeployProvider() {
75         super();
76     }
77
78     /**
79      * Creates or updates PDP groups.
80      *
81      * @param groups PDP group configurations to be created or updated
82      * @throws PfModelException if an error occurred
83      */
84     public void createOrUpdateGroups(PdpGroups groups) throws PfModelException {
85         ValidationResult result = groups.validatePapRest();
86
87         if (!result.isValid()) {
88             String msg = result.getResult().trim();
89             logger.warn(msg);
90             throw new PfModelException(Status.BAD_REQUEST, msg);
91         }
92
93         process(groups, this::createOrUpdate);
94     }
95
96     /**
97      * Creates or updates PDP groups. This is the method that does the actual work.
98      *
99      * @param data session data
100      * @param groups PDP group configurations
101      * @throws PfModelException if an error occurred
102      */
103     private void createOrUpdate(SessionData data, PdpGroups groups) throws PfModelException {
104         BeanValidationResult result = new BeanValidationResult("groups", groups);
105
106         for (PdpGroup group : groups.getGroups()) {
107             PdpGroup dbgroup = data.getGroup(group.getName());
108
109             if (dbgroup == null) {
110                 result.addResult(addGroup(data, group));
111
112             } else {
113                 result.addResult(updateGroup(data, dbgroup, group));
114             }
115         }
116
117         if (!result.isValid()) {
118             throw new PfModelException(Status.BAD_REQUEST, result.getResult().trim());
119         }
120     }
121
122     /**
123      * Adds a new group.
124      *
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
129      */
130     private ValidationResult addGroup(SessionData data, PdpGroup group) throws PfModelException {
131         BeanValidationResult result = new BeanValidationResult(group.getName(), group);
132
133         validateGroupOnly(group, result);
134         if (!result.isValid()) {
135             return result;
136         }
137
138         // default to active
139         if (group.getPdpGroupState() == null) {
140             group.setPdpGroupState(PdpState.ACTIVE);
141         }
142
143         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
144             result.addResult(addSubGroup(data, subgrp));
145         }
146
147         if (result.isValid()) {
148             data.create(group);
149         }
150
151         return result;
152     }
153
154     /**
155      * Performs additional validations of a group, but does not examine the subgroups.
156      *
157      * @param group the group to be validated
158      * @param result the validation result
159      */
160     private void validateGroupOnly(PdpGroup group, BeanValidationResult result) {
161         if (group.getPdpGroupState() == null) {
162             return;
163         }
164
165         switch (group.getPdpGroupState()) {
166             case ACTIVE:
167             case PASSIVE:
168                 break;
169
170             default:
171                 result.addResult(new ObjectValidationResult("pdpGroupState", group.getPdpGroupState(),
172                                 ValidationStatus.INVALID, "must be null, ACTIVE, or PASSIVE"));
173                 break;
174         }
175     }
176
177     /**
178      * Updates an existing group.
179      *
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
185      */
186     private ValidationResult updateGroup(SessionData data, PdpGroup dbgroup, PdpGroup group) throws PfModelException {
187         BeanValidationResult result = new BeanValidationResult(group.getName(), group);
188
189         if (!ObjectUtils.equals(dbgroup.getProperties(), group.getProperties())) {
190             result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
191                             "cannot change properties"));
192         }
193
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;
197
198         if (result.isValid() && updated) {
199             data.update(group);
200         }
201
202         return result;
203     }
204
205     /**
206      * Updates a field, if the new value is different than the old value.
207      *
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
212      *         the new value
213      */
214     private <T> boolean updateField(T oldValue, T newValue, Consumer<T> setter) {
215         if (oldValue == newValue) {
216             return false;
217         }
218
219         if (oldValue != null && oldValue.equals(newValue)) {
220             return false;
221         }
222
223         setter.accept(newValue);
224         return true;
225     }
226
227     /**
228      * Adds or updates subgroups within the group.
229      *
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
236      */
237     private boolean addOrUpdateSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group,
238                     BeanValidationResult result) throws PfModelException {
239
240         // create a map of existing subgroups
241         Map<String, PdpSubGroup> type2sub = new HashMap<>();
242         dbgroup.getPdpSubgroups().forEach(subgrp -> type2sub.put(subgrp.getPdpType(), subgrp));
243
244         boolean updated = false;
245
246         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
247             PdpSubGroup dbsub = type2sub.get(subgrp.getPdpType());
248             BeanValidationResult subResult = new BeanValidationResult(subgrp.getPdpType(), subgrp);
249
250             if (dbsub == null) {
251                 updated = true;
252                 subResult.addResult(addSubGroup(data, subgrp));
253                 dbgroup.getPdpSubgroups().add(subgrp);
254
255             } else {
256                 updated = updateSubGroup(data, group, dbsub, subgrp, subResult) || updated;
257             }
258
259             result.addResult(subResult);
260         }
261
262         return updated;
263     }
264
265     /**
266      * Notifies any PDPs whose subgroups are being removed.
267      *
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      */
273     private boolean notifyPdpsDelSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group) {
274         boolean updated = false;
275
276         // subgroups, as they appear within the updated group
277         Set<String> subgroups = new HashSet<>();
278         group.getPdpSubgroups().forEach(subgrp -> subgroups.add(subgrp.getPdpType()));
279
280         // loop through subgroups as they appear within the DB
281         for (PdpSubGroup subgrp : dbgroup.getPdpSubgroups()) {
282
283             if (!subgroups.contains(subgrp.getPdpType())) {
284                 // this subgroup no longer appears - notify its PDPs
285                 updated = true;
286                 notifyPdpsDelSubGroup(data, subgrp);
287             }
288         }
289
290         return updated;
291     }
292
293     /**
294      * Notifies the PDPs that their subgroup is being removed.
295      *
296      * @param data session data
297      * @param subgrp subgroup that is being removed
298      */
299     private void notifyPdpsDelSubGroup(SessionData data, PdpSubGroup subgrp) {
300         for (Pdp pdp : subgrp.getPdpInstances()) {
301             String name = pdp.getInstanceId();
302
303             // make it passive
304             PdpStateChange change = new PdpStateChange();
305             change.setName(name);
306             change.setState(PdpState.PASSIVE);
307
308             // remove it from subgroup and undeploy all policies
309             PdpUpdate update = new PdpUpdate();
310             update.setName(name);
311
312             data.addRequests(update, change);
313         }
314     }
315
316     /**
317      * Adds a new subgroup.
318      *
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
323      */
324     private ValidationResult addSubGroup(SessionData data, PdpSubGroup subgrp) throws PfModelException {
325         subgrp.setCurrentInstanceCount(0);
326         subgrp.setPdpInstances(Collections.emptyList());
327
328         BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
329
330         result.addResult(validateSupportedTypes(data, subgrp));
331         result.addResult(validatePolicies(data, null, subgrp));
332
333         return result;
334     }
335
336     /**
337      * Updates an existing subgroup.
338      *
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
345      *         were no changes
346      * @throws PfModelException if an error occurred
347      */
348     private boolean updateSubGroup(SessionData data, PdpGroup dbgroup, PdpSubGroup dbsub, PdpSubGroup subgrp,
349                     BeanValidationResult container) throws PfModelException {
350
351         // perform additional validations first
352         if (!validateSubGroup(data, dbsub, subgrp, container)) {
353             return false;
354         }
355
356         /*
357          * first, apply the changes about which the PDPs care
358          */
359         boolean updated = updateList(dbsub.getPolicies(), subgrp.getPolicies(), dbsub::setPolicies);
360
361         // publish any changes to the PDPs
362         if (updated) {
363             makeUpdates(data, dbgroup, dbsub);
364         }
365
366         /*
367          * now make any remaining changes
368          */
369         updated = updateList(dbsub.getSupportedPolicyTypes(), subgrp.getSupportedPolicyTypes(),
370                         dbsub::setSupportedPolicyTypes) || updated;
371
372         return updateField(dbsub.getDesiredInstanceCount(), subgrp.getDesiredInstanceCount(),
373                         dbsub::setDesiredInstanceCount) || updated;
374     }
375
376     /**
377      * Performs additional validations of a subgroup.
378      *
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
385      */
386     private boolean validateSubGroup(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp,
387                     BeanValidationResult container) throws PfModelException {
388
389         BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
390
391         if (!ObjectUtils.equals(dbsub.getProperties(), subgrp.getProperties())) {
392             result.addResult(new ObjectValidationResult("properties", "", ValidationStatus.INVALID,
393                             "cannot change properties"));
394         }
395
396         result.addResult(validatePolicies(data, dbsub, subgrp));
397         container.addResult(result);
398
399         return result.isValid();
400     }
401
402     /**
403      * Updates a DB list with items from a new list.
404      *
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
409      */
410     private <T> boolean updateList(List<T> dblist, List<T> newList, Consumer<List<T>> setter) {
411
412         Set<T> dbTypes = new HashSet<>(dblist);
413         Set<T> newTypes = new HashSet<>(newList);
414
415         if (dbTypes.equals(newTypes)) {
416             return false;
417         }
418
419         setter.accept(new ArrayList<>(newTypes));
420
421         return true;
422     }
423
424     /**
425      * Performs additional validations of the supported policy types within a subgroup.
426      *
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
431      */
432     private ValidationResult validateSupportedTypes(SessionData data, PdpSubGroup subgrp) throws PfModelException {
433         BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
434
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"));
439             }
440         }
441
442         return result;
443     }
444
445     /**
446      * Performs additional validations of the policies within a subgroup.
447      *
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
453      */
454     private ValidationResult validatePolicies(SessionData data, PdpSubGroup dbsub, PdpSubGroup subgrp)
455                     throws PfModelException {
456
457         // build a map of the DB data, from policy name to policy version
458         Map<String, String> dbname2vers = new HashMap<>();
459         if (dbsub != null) {
460             dbsub.getPolicies().forEach(ident -> dbname2vers.put(ident.getName(), ident.getVersion()));
461         }
462
463         BeanValidationResult result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
464
465         for (ToscaPolicyIdentifier ident : subgrp.getPolicies()) {
466             String actualVersion;
467
468             ToscaPolicy policy = data.getPolicy(new ToscaPolicyIdentifierOptVersion(ident));
469             if (policy == null) {
470                 result.addResult(new ObjectValidationResult(POLICY_RESULT_NAME, ident, ValidationStatus.INVALID,
471                                 "unknown policy"));
472
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"));
476
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));
482             }
483         }
484
485         return result;
486     }
487
488     /**
489      * Deploys or updates PDP policies using the simple API.
490      *
491      * @param policies PDP policies
492      * @throws PfModelException if an error occurred
493      */
494     public void deployPolicies(PdpDeployPolicies policies) throws PfModelException {
495         process(policies, this::deploySimplePolicies);
496     }
497
498     /**
499      * Deploys or updates PDP policies using the simple API. This is the method that does
500      * the actual work.
501      *
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
506      */
507     private void deploySimplePolicies(SessionData data, PdpDeployPolicies policies) throws PfModelException {
508
509         for (ToscaPolicyIdentifierOptVersion desiredPolicy : policies.getPolicies()) {
510
511             try {
512                 processPolicy(data, desiredPolicy);
513
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);
517                 throw e;
518             }
519         }
520     }
521
522     /**
523      * Adds a policy to a subgroup, if it isn't there already.
524      */
525     @Override
526     protected BiFunction<PdpGroup, PdpSubGroup, Boolean> makeUpdater(ToscaPolicy policy) {
527         ToscaPolicyIdentifier desiredIdent = policy.getIdentifier();
528         ToscaPolicyTypeIdentifier desiredType = policy.getTypeIdentifier();
529
530         return (group, subgroup) -> {
531
532             if (!subgroup.getSupportedPolicyTypes().contains(desiredType)) {
533                 // doesn't support the desired policy type
534                 return false;
535             }
536
537             if (containsPolicy(group, subgroup, desiredIdent)) {
538                 return false;
539             }
540
541             if (subgroup.getPdpInstances().isEmpty()) {
542                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "group " + group.getName() + " subgroup "
543                                 + subgroup.getPdpType() + " has no active PDPs");
544             }
545
546
547             // add the policy to the subgroup
548             subgroup.getPolicies().add(desiredIdent);
549
550             logger.info("add policy {} {} to subgroup {} {} count={}", desiredIdent.getName(),
551                             desiredIdent.getVersion(), group.getName(), subgroup.getPdpType(),
552                             subgroup.getPolicies().size());
553             return true;
554         };
555     }
556
557     /**
558      * Determines if a subgroup already contains the desired policy.
559      *
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}
564      *         otherwise
565      * @throws PfModelRuntimeException if the subgroup contains a different version of the
566      *         desired policy
567      */
568     private boolean containsPolicy(PdpGroup group, PdpSubGroup subgroup, ToscaPolicyIdentifier desiredIdent) {
569         String desnm = desiredIdent.getName();
570         String desvers = desiredIdent.getVersion();
571
572         for (ToscaPolicyIdentifier actualIdent : subgroup.getPolicies()) {
573             if (!actualIdent.getName().equals(desnm)) {
574                 continue;
575             }
576
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());
583             }
584
585             // already has the desired policy & version
586             logger.info("subgroup {} {} already contains policy {} {}", group.getName(), subgroup.getPdpType(), desnm,
587                             desvers);
588             return true;
589         }
590
591         return false;
592     }
593 }