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