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