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