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