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