Moving common polling code into HttpOperation
[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 com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25 import java.time.LocalDateTime;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.concurrent.CompletableFuture;
32 import javax.ws.rs.core.MediaType;
33 import javax.ws.rs.core.Response;
34 import org.onap.aai.domain.yang.CloudRegion;
35 import org.onap.aai.domain.yang.GenericVnf;
36 import org.onap.aai.domain.yang.ServiceInstance;
37 import org.onap.aai.domain.yang.Tenant;
38 import org.onap.policy.aai.AaiConstants;
39 import org.onap.policy.aai.AaiCqResponse;
40 import org.onap.policy.common.gson.GsonMessageBodyHandler;
41 import org.onap.policy.common.utils.coder.Coder;
42 import org.onap.policy.common.utils.coder.CoderException;
43 import org.onap.policy.common.utils.coder.StandardCoder;
44 import org.onap.policy.common.utils.coder.StandardCoderObject;
45 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
46 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
47 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
48 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpPollingConfig;
49 import org.onap.policy.controlloop.policy.PolicyResult;
50 import org.onap.policy.controlloop.policy.Target;
51 import org.onap.policy.so.SoCloudConfiguration;
52 import org.onap.policy.so.SoModelInfo;
53 import org.onap.policy.so.SoRequest;
54 import org.onap.policy.so.SoRequestInfo;
55 import org.onap.policy.so.SoRequestParameters;
56 import org.onap.policy.so.SoRequestStatus;
57 import org.onap.policy.so.SoResponse;
58 import org.onap.policy.so.util.SoLocalDateTimeTypeAdapter;
59
60 /**
61  * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetPollCount()}
62  * each time they issue an HTTP request.
63  */
64 public abstract class SoOperation extends HttpOperation<SoResponse> {
65     private static final Coder coder = new SoCoder();
66
67     public static final String PAYLOAD_KEY_VF_COUNT = "vfCount";
68     public static final String FAILED = "FAILED";
69     public static final String COMPLETE = "COMPLETE";
70     public static final int SO_RESPONSE_CODE = 999;
71
72     // fields within the policy payload
73     public static final String REQ_PARAM_NM = "requestParameters";
74     public static final String CONFIG_PARAM_NM = "configurationParameters";
75
76     // values extracted from the parameter Target
77     private final String modelCustomizationId;
78     private final String modelInvariantId;
79     private final String modelVersionId;
80
81     private final String vfCountKey;
82
83
84     /**
85      * Constructs the object.
86      *
87      * @param params operation parameters
88      * @param config configuration for this operation
89      */
90     public SoOperation(ControlLoopOperationParams params, HttpPollingConfig config) {
91         super(params, config, SoResponse.class);
92
93         setUsePolling();
94
95         verifyNotNull("Target information", params.getTarget());
96
97         this.modelCustomizationId = params.getTarget().getModelCustomizationId();
98         this.modelInvariantId = params.getTarget().getModelInvariantId();
99         this.modelVersionId = params.getTarget().getModelVersionId();
100
101         vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
102                         + modelVersionId + "]";
103     }
104
105     @Override
106     protected void resetPollCount() {
107         super.resetPollCount();
108         setSubRequestId(null);
109     }
110
111     /**
112      * Validates that the parameters contain the required target information to extract
113      * the VF count from the custom query.
114      */
115     protected void validateTarget() {
116         verifyNotNull("modelCustomizationId", modelCustomizationId);
117         verifyNotNull("modelInvariantId", modelInvariantId);
118         verifyNotNull("modelVersionId", modelVersionId);
119     }
120
121     private void verifyNotNull(String type, Object value) {
122         if (value == null) {
123             throw new IllegalArgumentException("missing " + type + " for guard payload");
124         }
125     }
126
127     /**
128      * Starts the GUARD.
129      */
130     @Override
131     protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
132         return startGuardAsync();
133     }
134
135     /**
136      * Gets the VF Count.
137      *
138      * @return a future to cancel or await the VF Count
139      */
140     @SuppressWarnings("unchecked")
141     protected CompletableFuture<OperationOutcome> obtainVfCount() {
142         if (params.getContext().contains(vfCountKey)) {
143             // already have the VF count
144             return null;
145         }
146
147         // need custom query from which to extract the VF count
148         ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
149                         .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
150
151         // run Custom Query and then extract the VF count
152         return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
153     }
154
155     /**
156      * Stores the VF count.
157      *
158      * @return {@code null}
159      */
160     private CompletableFuture<OperationOutcome> storeVfCount() {
161         if (!params.getContext().contains(vfCountKey)) {
162             AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
163             int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
164
165             params.getContext().setProperty(vfCountKey, vfcount);
166         }
167
168         return null;
169     }
170
171     protected int getVfCount() {
172         return params.getContext().getProperty(vfCountKey);
173     }
174
175     protected void setVfCount(int vfCount) {
176         params.getContext().setProperty(vfCountKey, vfCount);
177     }
178
179     @Override
180     protected Status detmStatus(Response rawResponse, SoResponse response) {
181         if (rawResponse.getStatus() == 200) {
182             String requestState = getRequestState(response);
183             if (COMPLETE.equalsIgnoreCase(requestState)) {
184                 extractSubRequestId(response);
185                 return Status.SUCCESS;
186             }
187
188             if (FAILED.equalsIgnoreCase(requestState)) {
189                 extractSubRequestId(response);
190                 return Status.FAILURE;
191             }
192         }
193
194         // still incomplete
195
196         // need a request ID with which to query
197         if (getSubRequestId() == null && !extractSubRequestId(response)) {
198             throw new IllegalArgumentException("missing request ID in response");
199         }
200
201         return Status.STILL_WAITING;
202     }
203
204     @Override
205     protected String getPollingPath() {
206         return super.getPollingPath() + getSubRequestId();
207     }
208
209     @Override
210     public void generateSubRequestId(int attempt) {
211         setSubRequestId(null);
212     }
213
214     private boolean extractSubRequestId(SoResponse response) {
215         if (response == null || response.getRequestReferences() == null
216                         || response.getRequestReferences().getRequestId() == null) {
217             return false;
218         }
219
220         setSubRequestId(response.getRequestReferences().getRequestId());
221         return true;
222     }
223
224     /**
225      * Gets the request state of a response.
226      *
227      * @param response response from which to get the state
228      * @return the request state of the response, or {@code null} if it does not exist
229      */
230     protected String getRequestState(SoResponse response) {
231         SoRequest request = response.getRequest();
232         if (request == null) {
233             return null;
234         }
235
236         SoRequestStatus status = request.getRequestStatus();
237         if (status == null) {
238             return null;
239         }
240
241         return status.getRequestState();
242     }
243
244     /**
245      * Treats everything as a success, so we always go into
246      * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
247      */
248     @Override
249     protected boolean isSuccess(Response rawResponse, SoResponse response) {
250         return true;
251     }
252
253     /**
254      * Prepends the message with the http status code.
255      */
256     @Override
257     public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
258                     SoResponse response) {
259
260         // set default result and message
261         setOutcome(outcome, result);
262
263         int code = (result == PolicyResult.FAILURE_TIMEOUT ? SO_RESPONSE_CODE : rawResponse.getStatus());
264
265         outcome.setResponse(response);
266         outcome.setMessage(code + " " + outcome.getMessage());
267         return outcome;
268     }
269
270     protected SoModelInfo prepareSoModelInfo() {
271         Target target = params.getTarget();
272         if (target == null) {
273             throw new IllegalArgumentException("missing Target");
274         }
275
276         if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
277                         || target.getModelName() == null || target.getModelVersion() == null
278                         || target.getModelVersionId() == null) {
279             throw new IllegalArgumentException("missing VF Module model");
280         }
281
282         SoModelInfo soModelInfo = new SoModelInfo();
283         soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
284         soModelInfo.setModelInvariantId(target.getModelInvariantId());
285         soModelInfo.setModelName(target.getModelName());
286         soModelInfo.setModelVersion(target.getModelVersion());
287         soModelInfo.setModelVersionId(target.getModelVersionId());
288         soModelInfo.setModelType("vfModule");
289         return soModelInfo;
290     }
291
292     /**
293      * Construct requestInfo for the SO requestDetails.
294      *
295      * @return SO request information
296      */
297     protected SoRequestInfo constructRequestInfo() {
298         SoRequestInfo soRequestInfo = new SoRequestInfo();
299         soRequestInfo.setSource("POLICY");
300         soRequestInfo.setSuppressRollback(false);
301         soRequestInfo.setRequestorId("policy");
302         return soRequestInfo;
303     }
304
305     /**
306      * Builds the request parameters from the policy payload.
307      */
308     protected Optional<SoRequestParameters> buildRequestParameters() {
309         if (params.getPayload() == null) {
310             return Optional.empty();
311         }
312
313         Object data = params.getPayload().get(REQ_PARAM_NM);
314         if (data == null) {
315             return Optional.empty();
316         }
317
318         try {
319             return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
320         } catch (CoderException e) {
321             throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
322         }
323     }
324
325     /**
326      * Builds the configuration parameters from the policy payload.
327      */
328     protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
329         if (params.getPayload() == null) {
330             return Optional.empty();
331         }
332
333         Object data = params.getPayload().get(CONFIG_PARAM_NM);
334         if (data == null) {
335             return Optional.empty();
336         }
337
338         try {
339             @SuppressWarnings("unchecked")
340             List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
341             return Optional.of(result);
342         } catch (CoderException | RuntimeException e) {
343             throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
344         }
345     }
346
347     /**
348      * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
349      * query.
350      *
351      * @param tenantItem tenant item from A&AI named-query response
352      * @return SO cloud configuration
353      */
354     protected SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
355         SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
356         cloudConfiguration.setTenantId(tenantItem.getTenantId());
357         cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
358         return cloudConfiguration;
359     }
360
361     /**
362      * Create simple HTTP headers for unauthenticated requests to SO.
363      *
364      * @return the HTTP headers
365      */
366     protected Map<String, Object> createSimpleHeaders() {
367         Map<String, Object> headers = new HashMap<>();
368         headers.put("Accept", MediaType.APPLICATION_JSON);
369         return headers;
370     }
371
372     /*
373      * These methods extract data from the Custom Query and throw an
374      * IllegalArgumentException if the desired data item is not found.
375      */
376
377     protected GenericVnf getVnfItem(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
378         GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
379         if (vnf == null) {
380             throw new IllegalArgumentException("missing generic VNF");
381         }
382
383         return vnf;
384     }
385
386     protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
387         ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
388         if (vnfService == null) {
389             throw new IllegalArgumentException("missing VNF Service Item");
390         }
391
392         return vnfService;
393     }
394
395     protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
396         Tenant tenant = aaiCqResponse.getDefaultTenant();
397         if (tenant == null) {
398             throw new IllegalArgumentException("missing Tenant Item");
399         }
400
401         return tenant;
402     }
403
404     protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
405         CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
406         if (cloudRegion == null) {
407             throw new IllegalArgumentException("missing Cloud Region");
408         }
409
410         return cloudRegion;
411     }
412
413     // these may be overridden by junit tests
414
415     @Override
416     protected Coder getCoder() {
417         return coder;
418     }
419
420     private static class SoCoder extends StandardCoder {
421
422         /**
423          * Gson object used to encode and decode messages.
424          */
425         private static final Gson SO_GSON;
426
427         /**
428          * Gson object used to encode messages in "pretty" format.
429          */
430         private static final Gson SO_GSON_PRETTY;
431
432         static {
433             GsonBuilder builder = GsonMessageBodyHandler
434                             .configBuilder(new GsonBuilder().registerTypeAdapter(StandardCoderObject.class,
435                                             new StandardTypeAdapter()))
436                             .registerTypeAdapter(LocalDateTime.class, new SoLocalDateTimeTypeAdapter());
437
438             SO_GSON = builder.create();
439             SO_GSON_PRETTY = builder.setPrettyPrinting().create();
440         }
441
442         public SoCoder() {
443             super(SO_GSON, SO_GSON_PRETTY);
444         }
445     }
446 }