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