7fa5048420164e3a9bef605fa40d8512b0bcb7b7
[policy/pap.git] / main / src / main / java / org / onap / policy / pap / main / rest / PdpGroupCreateOrUpdateProvider.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP PAP
4  * ================================================================================
5  * Copyright (C) 2019-2021 Nordix Foundation.
6  * Modifications Copyright (C) 2019, 2021 AT&T Intellectual Property.
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 java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Set;
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.ValidationResult;
36 import org.onap.policy.common.parameters.ValidationStatus;
37 import org.onap.policy.common.utils.services.Registry;
38 import org.onap.policy.models.base.PfModelException;
39 import org.onap.policy.models.pdp.concepts.Pdp;
40 import org.onap.policy.models.pdp.concepts.PdpGroup;
41 import org.onap.policy.models.pdp.concepts.PdpGroups;
42 import org.onap.policy.models.pdp.concepts.PdpStateChange;
43 import org.onap.policy.models.pdp.concepts.PdpSubGroup;
44 import org.onap.policy.models.pdp.concepts.PdpUpdate;
45 import org.onap.policy.models.pdp.enums.PdpState;
46 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
47 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifierOptVersion;
48 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * Provider for PAP component to create or update PDP groups. The following items must be in the
54  * {@link Registry}:
55  * <ul>
56  * <li>PDP Modification Lock</li>
57  * <li>PDP Modify Request Map</li>
58  * <li>PAP DAO Factory</li>
59  * </ul>
60  */
61 public class PdpGroupCreateOrUpdateProvider extends ProviderBase {
62     private static final Logger logger = LoggerFactory.getLogger(PdpGroupCreateOrUpdateProvider.class);
63
64     /**
65      * Constructs the object.
66      */
67     public PdpGroupCreateOrUpdateProvider() {
68         super();
69     }
70
71     /**
72      * Creates or updates PDP groups.
73      *
74      * @param groups PDP group configurations to be created or updated
75      * @throws PfModelException if an error occurred
76      */
77     public void createOrUpdateGroups(PdpGroups groups) throws PfModelException {
78         var result = new BeanValidationResult("groups", groups);
79         groups.checkForDuplicateGroups(result);
80         if (!result.isValid()) {
81             String msg = result.getResult().trim();
82             logger.warn(msg);
83             throw new PfModelException(Status.BAD_REQUEST, msg);
84         }
85         // During PdpGroup create/update, policies are not supposed to be deployed/undeployed into the group.
86         // There is a separate API for this.
87         List<PdpSubGroup> subGroupsWithPolicies =
88             groups.getGroups().parallelStream().flatMap(group -> group.getPdpSubgroups().parallelStream())
89                 .filter(subGroup -> null != subGroup.getPolicies() && !subGroup.getPolicies().isEmpty())
90                 .collect(Collectors.toList());
91         if (!subGroupsWithPolicies.isEmpty()) {
92             logger.warn(
93                 "Policies cannot be deployed during PdpGroup Create/Update operation. Ignoring the list of policies");
94             subGroupsWithPolicies.forEach(subGroup -> subGroup.setPolicies(Collections.emptyList()));
95         }
96         process(groups, this::createOrUpdate);
97     }
98
99     /**
100      * Creates or updates PDP groups. This is the method that does the actual work.
101      *
102      * @param data session data
103      * @param groups PDP group configurations
104      * @throws PfModelException if an error occurred
105      */
106     private void createOrUpdate(SessionData data, PdpGroups groups) throws PfModelException {
107         var result = new BeanValidationResult("groups", groups);
108
109         for (PdpGroup group : groups.getGroups()) {
110             PdpGroup dbgroup = data.getGroup(group.getName());
111             ValidationResult groupValidationResult;
112             if (dbgroup == null) {
113                 // create group flow
114                 groupValidationResult = group.validatePapRest(false);
115                 if (groupValidationResult.isValid()) {
116                     result.addResult(addGroup(data, group));
117                 } else {
118                     result.addResult(groupValidationResult);
119                 }
120
121             } else {
122                 // update group flow
123                 groupValidationResult = group.validatePapRest(true);
124                 if (groupValidationResult.isValid()) {
125                     result.addResult(updateGroup(data, dbgroup, group));
126                 } else {
127                     result.addResult(groupValidationResult);
128                 }
129             }
130         }
131
132         if (!result.isValid()) {
133             throw new PfModelException(Status.BAD_REQUEST, result.getResult().trim());
134         }
135     }
136
137     /**
138      * Adds a new group.
139      *
140      * @param data session data
141      * @param group the group to be added
142      * @return the validation result
143      * @throws PfModelException if an error occurred
144      */
145     private ValidationResult addGroup(SessionData data, PdpGroup group) throws PfModelException {
146         var result = new BeanValidationResult(group.getName(), group);
147
148         validateGroupOnly(group, result);
149         if (!result.isValid()) {
150             return result;
151         }
152
153         // default to active
154         if (group.getPdpGroupState() == null) {
155             group.setPdpGroupState(PdpState.ACTIVE);
156         }
157
158         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
159             result.addResult(addSubGroup(data, subgrp));
160         }
161
162         if (result.isValid()) {
163             data.create(group);
164         }
165
166         return result;
167     }
168
169     /**
170      * Performs additional validations of a group, but does not examine the subgroups.
171      *
172      * @param group the group to be validated
173      * @param result the validation result
174      */
175     private void validateGroupOnly(PdpGroup group, BeanValidationResult result) {
176         if (group.getPdpGroupState() == null) {
177             return;
178         }
179
180         switch (group.getPdpGroupState()) {
181             case ACTIVE:
182             case PASSIVE:
183                 break;
184
185             default:
186                 result.addResult("pdpGroupState", group.getPdpGroupState(),
187                                 ValidationStatus.INVALID, "must be null, ACTIVE, or PASSIVE");
188                 break;
189         }
190     }
191
192     /**
193      * Updates an existing group.
194      *
195      * @param data session data
196      * @param dbgroup the group, as it appears within the DB
197      * @param group the group to be added
198      * @return the validation result
199      * @throws PfModelException if an error occurred
200      */
201     private ValidationResult updateGroup(SessionData data, PdpGroup dbgroup, PdpGroup group) throws PfModelException {
202         var result = new BeanValidationResult(group.getName(), group);
203
204         if (!Objects.equals(dbgroup.getProperties(), group.getProperties())) {
205             result.addResult("properties", "", ValidationStatus.INVALID, "cannot change properties");
206         }
207
208         boolean updated = updateField(dbgroup.getDescription(), group.getDescription(), dbgroup::setDescription);
209         updated =
210             updateField(dbgroup.getPdpGroupState(), group.getPdpGroupState(), dbgroup::setPdpGroupState) || updated;
211         updated = notifyPdpsDelSubGroups(data, dbgroup, group) || updated;
212         updated = addOrUpdateSubGroups(data, dbgroup, group, result) || updated;
213
214         if (result.isValid() && updated) {
215             data.update(group);
216         }
217
218         return result;
219     }
220
221     /**
222      * Updates a field, if the new value is different than the old value.
223      *
224      * @param oldValue old value
225      * @param newValue new value
226      * @param setter function to set the field to the new value
227      * @return {@code true} if the field was updated, {@code false} if it already matched
228      *         the new value
229      */
230     private <T> boolean updateField(T oldValue, T newValue, Consumer<T> setter) {
231         if (oldValue == newValue) {
232             return false;
233         }
234
235         if (oldValue != null && oldValue.equals(newValue)) {
236             return false;
237         }
238
239         setter.accept(newValue);
240         return true;
241     }
242
243     /**
244      * Adds or updates subgroups within the group.
245      *
246      * @param data session data
247      * @param dbgroup the group, as it appears within the DB
248      * @param group the group to be added
249      * @param result the validation result
250      * @return {@code true} if the DB group was modified, {@code false} otherwise
251      * @throws PfModelException if an error occurred
252      */
253     private boolean addOrUpdateSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group,
254         BeanValidationResult result) throws PfModelException {
255
256         // create a map of existing subgroups
257         Map<String, PdpSubGroup> type2sub = new HashMap<>();
258         dbgroup.getPdpSubgroups().forEach(subgrp -> type2sub.put(subgrp.getPdpType(), subgrp));
259
260         var updated = false;
261
262         for (PdpSubGroup subgrp : group.getPdpSubgroups()) {
263             PdpSubGroup dbsub = type2sub.get(subgrp.getPdpType());
264             var subResult = new BeanValidationResult(subgrp.getPdpType(), subgrp);
265
266             if (dbsub == null) {
267                 updated = true;
268                 subResult.addResult(addSubGroup(data, subgrp));
269                 dbgroup.getPdpSubgroups().add(subgrp);
270
271             } else {
272                 updated = updateSubGroup(dbsub, subgrp, subResult) || updated;
273             }
274
275             result.addResult(subResult);
276         }
277
278         return updated;
279     }
280
281     /**
282      * Notifies any PDPs whose subgroups are being removed.
283      *
284      * @param data session data
285      * @param dbgroup the group, as it appears within the DB
286      * @param group the group being updated
287      * @return {@code true} if a subgroup was removed, {@code false} otherwise
288      * @throws PfModelException if an error occurred
289      */
290     private boolean notifyPdpsDelSubGroups(SessionData data, PdpGroup dbgroup, PdpGroup group) throws PfModelException {
291         var updated = false;
292
293         // subgroups, as they appear within the updated group
294         Set<String> subgroups = new HashSet<>();
295         group.getPdpSubgroups().forEach(subgrp -> subgroups.add(subgrp.getPdpType()));
296
297         // loop through subgroups as they appear within the DB
298         for (PdpSubGroup subgrp : dbgroup.getPdpSubgroups()) {
299
300             if (!subgroups.contains(subgrp.getPdpType())) {
301                 // this subgroup no longer appears - notify its PDPs
302                 updated = true;
303                 notifyPdpsDelSubGroup(data, subgrp);
304                 trackPdpsDelSubGroup(data, dbgroup.getName(), subgrp);
305             }
306         }
307
308         return updated;
309     }
310
311     /**
312      * Notifies the PDPs that their subgroup is being removed.
313      *
314      * @param data session data
315      * @param subgrp subgroup that is being removed
316      */
317     private void notifyPdpsDelSubGroup(SessionData data, PdpSubGroup subgrp) {
318         for (Pdp pdp : subgrp.getPdpInstances()) {
319             String name = pdp.getInstanceId();
320
321             // make it passive
322             var change = new PdpStateChange();
323             change.setName(name);
324             change.setState(PdpState.PASSIVE);
325
326             // remove it from subgroup and undeploy all policies
327             var update = new PdpUpdate();
328             update.setName(name);
329
330             data.addRequests(update, change);
331         }
332     }
333
334     /**
335      * Tracks PDP responses when their subgroup is removed.
336      *
337      * @param data session data
338      * @param pdpGroup PdpGroup name
339      * @param subgrp subgroup that is being removed
340      * @throws PfModelException if an error occurred
341      */
342     private void trackPdpsDelSubGroup(SessionData data, String pdpGroup, PdpSubGroup subgrp) throws PfModelException {
343         Set<String> pdps = subgrp.getPdpInstances().stream().map(Pdp::getInstanceId).collect(Collectors.toSet());
344
345         for (ToscaConceptIdentifier policyId : subgrp.getPolicies()) {
346             data.trackUndeploy(policyId, pdps, pdpGroup, subgrp.getPdpType());
347         }
348     }
349
350     /**
351      * Adds a new subgroup.
352      *
353      * @param data session data
354      * @param subgrp the subgroup to be added, updated to fully qualified versions upon
355      *        return
356      * @return the validation result
357      * @throws PfModelException if an error occurred
358      */
359     private ValidationResult addSubGroup(SessionData data, PdpSubGroup subgrp) throws PfModelException {
360         subgrp.setCurrentInstanceCount(0);
361         subgrp.setPdpInstances(Collections.emptyList());
362
363         var result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
364
365         result.addResult(validateSupportedTypes(data, subgrp));
366         return result;
367     }
368
369     /**
370      * Updates an existing subgroup.
371      *
372      * @param dbsub the subgroup, from the DB
373      * @param subgrp the subgroup to be updated, updated to fully qualified versions upon
374      *        return
375      * @param container container for additional validation results
376      * @return {@code true} if the subgroup content was changed, {@code false} if there
377      *         were no changes
378      */
379     private boolean updateSubGroup(PdpSubGroup dbsub, PdpSubGroup subgrp, BeanValidationResult container) {
380
381         // perform additional validations first
382         if (!validateSubGroup(dbsub, subgrp, container)) {
383             return false;
384         }
385
386         if (null != subgrp.getSupportedPolicyTypes() && !new HashSet<>(dbsub.getSupportedPolicyTypes())
387             .equals(new HashSet<>(subgrp.getSupportedPolicyTypes()))) {
388             logger.warn("Supported policy types cannot be updated while updating PdpGroup. "
389                 + "Hence, ignoring the new set of supported policy types.");
390         }
391
392         // while updating PdpGroup, list of policies (already deployed ones) and supported policies (the ones provided
393         // during PdpGroup creation) has to be retained
394         subgrp.setSupportedPolicyTypes(dbsub.getSupportedPolicyTypes());
395         subgrp.setPolicies(dbsub.getPolicies());
396         return updateField(dbsub.getDesiredInstanceCount(), subgrp.getDesiredInstanceCount(),
397             dbsub::setDesiredInstanceCount);
398     }
399
400     /**
401      * Performs additional validations of a subgroup.
402      *
403      * @param dbsub the subgroup, from the DB
404      * @param subgrp the subgroup to be validated, updated to fully qualified versions
405      *        upon return
406      * @param container container for additional validation results
407      * @return {@code true} if the subgroup is valid, {@code false} otherwise
408      */
409     private boolean validateSubGroup(PdpSubGroup dbsub, PdpSubGroup subgrp, BeanValidationResult container) {
410
411         var result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
412
413         if (!Objects.equals(dbsub.getProperties(), subgrp.getProperties())) {
414             result.addResult("properties", "", ValidationStatus.INVALID, "cannot change properties");
415         }
416
417         container.addResult(result);
418
419         return result.isValid();
420     }
421
422     /**
423      * Performs validations of the supported policy types within a subgroup.
424      *
425      * @param data session data
426      * @param subgrp the subgroup to be validated
427      * @param result the validation result
428      * @throws PfModelException if an error occurred
429      */
430     private ValidationResult validateSupportedTypes(SessionData data, PdpSubGroup subgrp) throws PfModelException {
431         var result = new BeanValidationResult(subgrp.getPdpType(), subgrp);
432         for (ToscaConceptIdentifier type : subgrp.getSupportedPolicyTypes()) {
433             if (!type.getName().endsWith(".*") && data.getPolicyType(type) == null) {
434                 result.addResult("policy type", type, ValidationStatus.INVALID, "unknown policy type");
435             }
436         }
437
438         return result;
439     }
440
441     @Override
442     protected Updater makeUpdater(SessionData data, ToscaPolicy policy,
443             ToscaConceptIdentifierOptVersion desiredPolicy) {
444         throw new UnsupportedOperationException("makeUpdater should not be invoked");
445     }
446 }