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