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