Merge "Minor fix to avoid ConcurrentModificationException in policy-models"
[policy/models.git] / models-interactions / model-actors / actor.so / src / main / java / org / onap / policy / controlloop / actor / so / SoOperation.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
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.controlloop.actor.so;
22
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.CompletableFuture;
27 import java.util.concurrent.TimeUnit;
28 import java.util.function.Function;
29 import javax.ws.rs.core.Response;
30 import lombok.Getter;
31 import org.onap.aai.domain.yang.CloudRegion;
32 import org.onap.aai.domain.yang.GenericVnf;
33 import org.onap.aai.domain.yang.ServiceInstance;
34 import org.onap.aai.domain.yang.Tenant;
35 import org.onap.policy.aai.AaiCqResponse;
36 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
37 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
38 import org.onap.policy.common.utils.coder.Coder;
39 import org.onap.policy.common.utils.coder.CoderException;
40 import org.onap.policy.common.utils.coder.StandardCoder;
41 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
42 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
43 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
44 import org.onap.policy.controlloop.policy.PolicyResult;
45 import org.onap.policy.controlloop.policy.Target;
46 import org.onap.policy.so.SoModelInfo;
47 import org.onap.policy.so.SoRequest;
48 import org.onap.policy.so.SoRequestInfo;
49 import org.onap.policy.so.SoRequestParameters;
50 import org.onap.policy.so.SoRequestStatus;
51 import org.onap.policy.so.SoResponse;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetGetCount()}
57  * each time they issue an HTTP request.
58  */
59 public abstract class SoOperation extends HttpOperation<SoResponse> {
60     private static final Logger logger = LoggerFactory.getLogger(SoOperation.class);
61     private static final Coder coder = new StandardCoder();
62
63     public static final String FAILED = "FAILED";
64     public static final String COMPLETE = "COMPLETE";
65     public static final int SO_RESPONSE_CODE = 999;
66
67     // fields within the policy payload
68     public static final String REQ_PARAM_NM = "requestParameters";
69     public static final String CONFIG_PARAM_NM = "configurationParameters";
70
71     @Getter
72     private final SoOperator operator;
73
74     /**
75      * Number of "get" requests issued so far, on the current operation attempt.
76      */
77     @Getter
78     private int getCount;
79
80
81     /**
82      * Constructs the object.
83      *
84      * @param params operation parameters
85      * @param operator operator that created this operation
86      */
87     public SoOperation(ControlLoopOperationParams params, SoOperator operator) {
88         super(params, operator, SoResponse.class);
89         this.operator = operator;
90     }
91
92     /**
93      * Subclasses should invoke this before issuing their first HTTP request.
94      */
95     protected void resetGetCount() {
96         getCount = 0;
97     }
98
99     /**
100      * Starts the GUARD.
101      */
102     @Override
103     protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
104         return startGuardAsync();
105     }
106
107     /**
108      * If the response does not indicate that the request has been completed, then sleep a
109      * bit and issue a "get".
110      */
111     @Override
112     protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
113                     Response rawResponse, SoResponse response) {
114
115         // see if the request has "completed", whether or not it was successful
116         if (rawResponse.getStatus() == 200) {
117             String requestState = getRequestState(response);
118             if (COMPLETE.equalsIgnoreCase(requestState)) {
119                 return CompletableFuture
120                                 .completedFuture(setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
121             }
122
123             if (FAILED.equalsIgnoreCase(requestState)) {
124                 return CompletableFuture
125                                 .completedFuture(setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
126             }
127         }
128
129         // still incomplete
130
131         // need a request ID with which to query
132         if (response.getRequestReferences() == null || response.getRequestReferences().getRequestId() == null) {
133             throw new IllegalArgumentException("missing request ID in response");
134         }
135
136         // see if the limit for the number of "gets" has been reached
137         if (getCount++ >= operator.getMaxGets()) {
138             logger.warn("{}: execeeded 'get' limit {} for {}", getFullName(), operator.getMaxGets(),
139                             params.getRequestId());
140             setOutcome(outcome, PolicyResult.FAILURE_TIMEOUT);
141             outcome.setMessage(SO_RESPONSE_CODE + " " + outcome.getMessage());
142             return CompletableFuture.completedFuture(outcome);
143         }
144
145         // sleep and then perform a "get" operation
146         Function<Void, CompletableFuture<OperationOutcome>> doGet = unused -> issueGet(outcome, response);
147         return sleep(getWaitMsGet(), TimeUnit.MILLISECONDS).thenComposeAsync(doGet);
148     }
149
150     /**
151      * Issues a "get" request to see if the original request is complete yet.
152      *
153      * @param outcome outcome to be populated with the response
154      * @param response previous response
155      * @return a future that can be used to cancel the "get" request or await its response
156      */
157     private CompletableFuture<OperationOutcome> issueGet(OperationOutcome outcome, SoResponse response) {
158         String path = operator.getPathGet() + response.getRequestReferences().getRequestId();
159         String url = operator.getClient().getBaseUrl() + path;
160
161         logger.debug("{}: 'get' count {} for {}", getFullName(), getCount, params.getRequestId());
162
163         logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
164
165         // TODO should this use "path" or the full "url"?
166         return handleResponse(outcome, url, callback -> operator.getClient().get(callback, path, null));
167     }
168
169     /**
170      * Gets the request state of a response.
171      *
172      * @param response response from which to get the state
173      * @return the request state of the response, or {@code null} if it does not exist
174      */
175     protected String getRequestState(SoResponse response) {
176         SoRequest request = response.getRequest();
177         if (request == null) {
178             return null;
179         }
180
181         SoRequestStatus status = request.getRequestStatus();
182         if (status == null) {
183             return null;
184         }
185
186         return status.getRequestState();
187     }
188
189     /**
190      * Treats everything as a success, so we always go into
191      * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
192      */
193     @Override
194     protected boolean isSuccess(Response rawResponse, SoResponse response) {
195         return true;
196     }
197
198     /**
199      * Prepends the message with the http status code.
200      */
201     @Override
202     public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
203                     SoResponse response) {
204
205         // set default result and message
206         setOutcome(outcome, result);
207
208         outcome.setMessage(rawResponse.getStatus() + " " + outcome.getMessage());
209         return outcome;
210     }
211
212     protected SoModelInfo prepareSoModelInfo() {
213         Target target = params.getTarget();
214         if (target == null) {
215             throw new IllegalArgumentException("missing Target");
216         }
217
218         if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
219                         || target.getModelName() == null || target.getModelVersion() == null
220                         || target.getModelVersionId() == null) {
221             throw new IllegalArgumentException("missing VF Module model");
222         }
223
224         SoModelInfo soModelInfo = new SoModelInfo();
225         soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
226         soModelInfo.setModelInvariantId(target.getModelInvariantId());
227         soModelInfo.setModelName(target.getModelName());
228         soModelInfo.setModelVersion(target.getModelVersion());
229         soModelInfo.setModelVersionId(target.getModelVersionId());
230         soModelInfo.setModelType("vfModule");
231         return soModelInfo;
232     }
233
234     /**
235      * Construct requestInfo for the SO requestDetails.
236      *
237      * @return SO request information
238      */
239     protected SoRequestInfo constructRequestInfo() {
240         SoRequestInfo soRequestInfo = new SoRequestInfo();
241         soRequestInfo.setSource("POLICY");
242         soRequestInfo.setSuppressRollback(false);
243         soRequestInfo.setRequestorId("policy");
244         return soRequestInfo;
245     }
246
247     /**
248      * Builds the request parameters from the policy payload.
249      */
250     protected SoRequestParameters buildRequestParameters() {
251         if (params.getPayload() == null) {
252             return null;
253         }
254
255         String json = params.getPayload().get(REQ_PARAM_NM);
256         if (json == null) {
257             return null;
258         }
259
260         try {
261             return coder.decode(json, SoRequestParameters.class);
262         } catch (CoderException e) {
263             throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
264         }
265     }
266
267     /**
268      * Builds the configuration parameters from the policy payload.
269      */
270     protected List<Map<String, String>> buildConfigurationParameters() {
271         if (params.getPayload() == null) {
272             return null;
273         }
274
275         String json = params.getPayload().get(CONFIG_PARAM_NM);
276         if (json == null) {
277             return null;
278         }
279
280         try {
281             @SuppressWarnings("unchecked")
282             List<Map<String, String>> result = coder.decode(json, ArrayList.class);
283             return result;
284         } catch (CoderException | RuntimeException e) {
285             throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
286         }
287     }
288
289     /*
290      * These methods extract data from the Custom Query and throw an
291      * IllegalArgumentException if the desired data item is not found.
292      */
293
294     protected GenericVnf getVnfItem(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
295         GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
296         if (vnf == null) {
297             throw new IllegalArgumentException("missing generic VNF");
298         }
299
300         return vnf;
301     }
302
303     protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
304         ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
305         if (vnfService == null) {
306             throw new IllegalArgumentException("missing VNF Service Item");
307         }
308
309         return vnfService;
310     }
311
312     protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
313         Tenant tenant = aaiCqResponse.getDefaultTenant();
314         if (tenant == null) {
315             throw new IllegalArgumentException("missing Tenant Item");
316         }
317
318         return tenant;
319     }
320
321     protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
322         CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
323         if (cloudRegion == null) {
324             throw new IllegalArgumentException("missing Cloud Region");
325         }
326
327         return cloudRegion;
328     }
329
330     // these may be overridden by junit tests
331
332     /**
333      * Gets the wait time, in milliseconds, between "get" requests.
334      *
335      * @return the wait time, in milliseconds, between "get" requests
336      */
337     public long getWaitMsGet() {
338         return TimeUnit.MILLISECONDS.convert(operator.getWaitSecGet(), TimeUnit.SECONDS);
339     }
340 }