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