7a1aa107b76e52a73cd5bb8905c70af127c1242e
[policy/pap.git] / main / src / main / java / org / onap / policy / pap / main / notification / DeploymentStatus.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2022 Bell Canada. All rights reserved.
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.notification;
23
24 import io.micrometer.core.instrument.MeterRegistry;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Set;
33 import java.util.function.BiPredicate;
34 import java.util.stream.Collectors;
35 import lombok.AccessLevel;
36 import lombok.Getter;
37 import org.onap.policy.common.utils.resources.PrometheusUtils;
38 import org.onap.policy.common.utils.services.Registry;
39 import org.onap.policy.models.pap.concepts.PolicyNotification;
40 import org.onap.policy.models.pdp.concepts.PdpPolicyStatus;
41 import org.onap.policy.models.pdp.concepts.PdpPolicyStatus.State;
42 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
43 import org.onap.policy.pap.main.PapConstants;
44 import org.onap.policy.pap.main.notification.StatusAction.Action;
45 import org.onap.policy.pap.main.service.PolicyStatusService;
46
47 /**
48  * Collection of Policy Deployment Status records. The sequence of method invocations
49  * should be as follows:
50  * <ol>
51  * <li>{@link #loadByGroup(String)}</li>
52  * <li>various other methods</li>
53  * <li>repeat the previous steps as appropriate</li>
54  * <li>{@link #flush(PolicyNotification)}</li>
55  * </ol>
56  */
57 public class DeploymentStatus {
58
59     /**
60      * Tracks the groups that have been loaded.
61      */
62     private final Set<String> pdpGroupLoaded = new HashSet<>();
63
64     /**
65      * Records, mapped by PDP/Policy pair.
66      */
67     @Getter(AccessLevel.PROTECTED)
68     private final Map<StatusKey, StatusAction> recordMap = new HashMap<>();
69
70     /**
71      * Records the policy status so that notifications can be generated. When
72      * {@link #loadByGroup(String)} is invoked, records are added to this. Other than
73      * that, this is not updated until {@link #addNotifications(PolicyNotification)} is
74      * invoked.
75      */
76     private DeploymentTracker tracker = new DeploymentTracker();
77
78     private PolicyStatusService policyStatusService;
79
80
81     /**
82      * Constructs the object.
83      *
84      * @param policyStatusService the policyStatusService
85      */
86     public DeploymentStatus(PolicyStatusService policyStatusService) {
87         this.policyStatusService = policyStatusService;
88     }
89
90     /**
91      * Adds new policy status to a notification.
92      *
93      * @param notif notification to which to add policy status
94      */
95     protected void addNotifications(PolicyNotification notif) {
96         var newTracker = new DeploymentTracker();
97         recordMap.values().forEach(newTracker::add);
98
99         tracker.addNotifications(notif, newTracker);
100
101         tracker = newTracker;
102     }
103
104     /**
105      * Loads policy deployment status associated with the given PDP group.
106      *
107      * @param pdpGroup group whose records are to be loaded
108      */
109     public void loadByGroup(String pdpGroup) {
110         if (pdpGroupLoaded.contains(pdpGroup)) {
111             return;
112         }
113
114         pdpGroupLoaded.add(pdpGroup);
115
116         for (PdpPolicyStatus status : policyStatusService.getGroupPolicyStatus(pdpGroup)) {
117             var status2 = new StatusAction(Action.UNCHANGED, status);
118             recordMap.put(new StatusKey(status), status2);
119             tracker.add(status2);
120         }
121     }
122
123     /**
124      * Flushes changes to the DB, adding policy status to the notification.
125      *
126      * @param notif notification to which to add policy status
127      */
128     public void flush(PolicyNotification notif) {
129         // must add notifications BEFORE deleting undeployments
130         addNotifications(notif);
131         updateMetrics();
132         deleteUndeployments();
133         flush();
134     }
135
136     /**
137      * Flushes changes to the DB.
138      */
139     protected void flush() {
140         // categorize the records
141         List<PdpPolicyStatus> created = new ArrayList<>();
142         List<PdpPolicyStatus> updated = new ArrayList<>();
143         List<PdpPolicyStatus> deleted = new ArrayList<>();
144
145         for (StatusAction status : recordMap.values()) {
146             switch (status.getAction()) {
147                 case CREATED:
148                     created.add(status.getStatus());
149                     break;
150                 case UPDATED:
151                     updated.add(status.getStatus());
152                     break;
153                 case DELETED:
154                     deleted.add(status.getStatus());
155                     break;
156                 default:
157                     break;
158             }
159         }
160
161         policyStatusService.cudPolicyStatus(created, updated, deleted);
162
163         /*
164          * update the records to indicate everything is now unchanged (i.e., matches what
165          * is in the DB)
166          */
167
168         Iterator<StatusAction> iter = recordMap.values().iterator();
169         while (iter.hasNext()) {
170             StatusAction status = iter.next();
171
172             if (status.getAction() == Action.DELETED) {
173                 iter.remove();
174             } else {
175                 status.setAction(Action.UNCHANGED);
176             }
177         }
178     }
179
180     private void updateMetrics() {
181         MeterRegistry meterRegistry = Registry.get(PapConstants.REG_METER_REGISTRY, MeterRegistry.class);
182         String counterName = "pap_" + PrometheusUtils.POLICY_DEPLOYMENTS_METRIC;
183         recordMap.forEach((key, value) -> {
184             if (value.getAction().equals(StatusAction.Action.UPDATED)) {
185                 if (value.getStatus().getState().equals(State.SUCCESS)) {
186                     meterRegistry.counter(counterName, PrometheusUtils.OPERATION_METRIC_LABEL,
187                         value.getStatus().isDeploy() ? PrometheusUtils.DEPLOY_OPERATION
188                             : PrometheusUtils.UNDEPLOY_OPERATION,
189                         PrometheusUtils.STATUS_METRIC_LABEL, State.SUCCESS.name()).increment();
190                 } else if (value.getStatus().getState().equals(State.FAILURE)) {
191                     meterRegistry.counter(counterName, PrometheusUtils.OPERATION_METRIC_LABEL,
192                         value.getStatus().isDeploy() ? PrometheusUtils.DEPLOY_OPERATION
193                             : PrometheusUtils.UNDEPLOY_OPERATION,
194                         PrometheusUtils.STATUS_METRIC_LABEL, State.FAILURE.name()).increment();
195                 }
196             }
197         });
198     }
199
200     /**
201      * Deletes records for any policies that have been completely undeployed.
202      */
203     protected void deleteUndeployments() {
204         // identify the incomplete policies
205
206         // @formatter:off
207         Set<ToscaConceptIdentifier> incomplete = recordMap.values().stream()
208             .filter(status -> status.getAction() != Action.DELETED)
209             .map(StatusAction::getStatus)
210             .filter(status -> status.getState() == State.WAITING)
211             .map(PdpPolicyStatus::getPolicy)
212             .collect(Collectors.toSet());
213         // @formatter:on
214
215         // delete if UNDEPLOYED and not incomplete
216         deleteDeployment((key, status) -> !status.getStatus().isDeploy() && !incomplete.contains(key.getPolicy()));
217     }
218
219     /**
220      * Delete deployment records for a PDP.
221      *
222      * @param pdpId PDP whose records are to be deleted
223      */
224     public void deleteDeployment(String pdpId) {
225         deleteDeployment((key, status) -> key.getPdpId().equals(pdpId));
226     }
227
228     /**
229      * Delete deployment records for a policy.
230      *
231      * @param policy policy whose records are to be deleted
232      * @param deploy {@code true} to delete deployment records, {@code false} to delete
233      *        undeployment records
234      */
235     public void deleteDeployment(ToscaConceptIdentifier policy, boolean deploy) {
236         deleteDeployment((key, status) -> status.getStatus().isDeploy() == deploy && key.getPolicy().equals(policy));
237     }
238
239     /**
240      * Delete deployment records for a policy.
241      *
242      * @param filter filter to identify records to be deleted
243      */
244     private void deleteDeployment(BiPredicate<StatusKey, StatusAction> filter) {
245         Iterator<Entry<StatusKey, StatusAction>> iter = recordMap.entrySet().iterator();
246         while (iter.hasNext()) {
247             Entry<StatusKey, StatusAction> entry = iter.next();
248             StatusKey key = entry.getKey();
249             StatusAction value = entry.getValue();
250
251             if (filter.test(key, value)) {
252                 if (value.getAction() == Action.CREATED) {
253                     // it's a new record - just remove it
254                     iter.remove();
255                 } else {
256                     // it's an existing record - mark it for deletion
257                     value.setAction(Action.DELETED);
258                 }
259             }
260         }
261     }
262
263     /**
264      * Deploys/undeploys a policy to a PDP. Assumes that
265      * {@link #deleteDeployment(ToscaConceptIdentifier, boolean)} has already been invoked
266      * to delete any records having the wrong "deploy" value.
267      *
268      * @param pdpId PDP to which the policy is to be deployed
269      * @param policy policy to be deployed
270      * @param policyType policy's type
271      * @param pdpGroup PdpGroup containing the PDP of interest
272      * @param pdpType PDP type (i.e., PdpSubGroup) containing the PDP of interest
273      * @param deploy {@code true} if the policy is being deployed, {@code false} if
274      *        undeployed
275      */
276     public void deploy(String pdpId, ToscaConceptIdentifier policy, ToscaConceptIdentifier policyType, String pdpGroup,
277                     String pdpType, boolean deploy) {
278
279         recordMap.compute(new StatusKey(pdpId, policy), (key, status) -> {
280
281             if (status == null) {
282                 // no record yet - create one
283
284                 // @formatter:off
285                 return new StatusAction(Action.CREATED, PdpPolicyStatus.builder()
286                                     .pdpGroup(pdpGroup)
287                                     .pdpId(pdpId)
288                                     .pdpType(pdpType)
289                                     .policy(policy)
290                                     .policyType(policyType)
291                                     .deploy(deploy)
292                                     .state(State.WAITING)
293                                     .build());
294                 // @formatter:on
295             }
296
297             PdpPolicyStatus status2 = status.getStatus();
298
299             // record already exists - see if the deployment flag should be changed
300
301             if (status2.isDeploy() != deploy) {
302                 // deployment flag has changed
303                 status.setChanged();
304                 status2.setDeploy(deploy);
305                 status2.setState(State.WAITING);
306
307
308             } else if (status.getAction() == Action.DELETED) {
309                 // deployment flag is unchanged
310                 status.setAction(Action.UPDATED);
311             }
312
313             return status;
314         });
315     }
316
317     /**
318      * Indicates the deployment/undeployment of a set of policies to a PDP has completed.
319      *
320      * @param pdpId PDP of interest
321      * @param expectedPolicies policies that we expected to be deployed to the PDP
322      * @param actualPolicies policies that were actually deployed to the PDP
323      */
324     public void completeDeploy(String pdpId, Set<ToscaConceptIdentifier> expectedPolicies,
325                     Set<ToscaConceptIdentifier> actualPolicies) {
326
327         for (StatusAction status : recordMap.values()) {
328             PdpPolicyStatus status2 = status.getStatus();
329
330             if (!status.getStatus().getPdpId().equals(pdpId)
331                             || expectedPolicies.contains(status2.getPolicy()) != status2.isDeploy()) {
332                 /*
333                  * The policy is "expected" to be deployed, but the record is not marked
334                  * for deployment (or vice versa), which means the expected policy is out
335                  * of date with the DB, thus we'll ignore this policy for now.
336                  */
337                 continue;
338             }
339
340             State state;
341             if (actualPolicies.contains(status2.getPolicy())) {
342                 state = (status.getStatus().isDeploy() ? State.SUCCESS : State.FAILURE);
343             } else {
344                 state = (status.getStatus().isDeploy() ? State.FAILURE : State.SUCCESS);
345             }
346
347             if (status2.getState() != state) {
348                 status.setChanged();
349                 status2.setState(state);
350             }
351         }
352     }
353 }