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