Change payload to Map<String,Object> so it's more versatile
[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 java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.CompletableFuture;
27 import java.util.concurrent.TimeUnit;
28 import java.util.function.Function;
29 import javax.ws.rs.core.Response;
30 import lombok.Getter;
31 import org.onap.aai.domain.yang.CloudRegion;
32 import org.onap.aai.domain.yang.GenericVnf;
33 import org.onap.aai.domain.yang.ServiceInstance;
34 import org.onap.aai.domain.yang.Tenant;
35 import org.onap.policy.aai.AaiCqResponse;
36 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
37 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
38 import org.onap.policy.common.utils.coder.Coder;
39 import org.onap.policy.common.utils.coder.CoderException;
40 import org.onap.policy.common.utils.coder.StandardCoder;
41 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
42 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
43 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
44 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpConfig;
45 import org.onap.policy.controlloop.policy.PolicyResult;
46 import org.onap.policy.controlloop.policy.Target;
47 import org.onap.policy.so.SoModelInfo;
48 import org.onap.policy.so.SoRequest;
49 import org.onap.policy.so.SoRequestInfo;
50 import org.onap.policy.so.SoRequestParameters;
51 import org.onap.policy.so.SoRequestStatus;
52 import org.onap.policy.so.SoResponse;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetGetCount()}
58  * each time they issue an HTTP request.
59  */
60 public abstract class SoOperation extends HttpOperation<SoResponse> {
61     private static final Logger logger = LoggerFactory.getLogger(SoOperation.class);
62     private static final Coder coder = new StandardCoder();
63
64     public static final String FAILED = "FAILED";
65     public static final String COMPLETE = "COMPLETE";
66     public static final int SO_RESPONSE_CODE = 999;
67
68     // fields within the policy payload
69     public static final String REQ_PARAM_NM = "requestParameters";
70     public static final String CONFIG_PARAM_NM = "configurationParameters";
71
72     private final SoConfig config;
73
74     /**
75      * Number of "get" requests issued so far, on the current operation attempt.
76      */
77     @Getter
78     private int getCount;
79
80
81     /**
82      * Constructs the object.
83      *
84      * @param params operation parameters
85      * @param config configuration for this operation
86      */
87     public SoOperation(ControlLoopOperationParams params, HttpConfig config) {
88         super(params, config, SoResponse.class);
89         this.config = (SoConfig) config;
90     }
91
92     /**
93      * Subclasses should invoke this before issuing their first HTTP request.
94      */
95     protected void resetGetCount() {
96         getCount = 0;
97     }
98
99     /**
100      * Validates that the parameters contain the required target information to extract
101      * the VF count from the custom query.
102      */
103     protected void validateTarget() {
104         verifyNotNull("Target information", params.getTarget());
105         verifyNotNull("model-customization-id", params.getTarget().getModelCustomizationId());
106         verifyNotNull("model-invariant-id", params.getTarget().getModelInvariantId());
107         verifyNotNull("model-version-id", params.getTarget().getModelVersionId());
108     }
109
110     private void verifyNotNull(String type, Object value) {
111         if (value == null) {
112             throw new IllegalArgumentException("missing " + type + " for guard payload");
113         }
114     }
115
116     /**
117      * Starts the GUARD.
118      */
119     @Override
120     protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
121         return startGuardAsync();
122     }
123
124     /**
125      * Stores the VF count and then runs the guard.
126      *
127      * @return a future to cancel or await the guard response
128      */
129     protected CompletableFuture<OperationOutcome> storeVfCountRunGuard() {
130         String custId = params.getTarget().getModelCustomizationId();
131         String invId = params.getTarget().getModelInvariantId();
132         String verId = params.getTarget().getModelVersionId();
133
134         AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
135         int vfcount = cq.getVfModuleCount(custId, invId, verId);
136
137         params.getContext().setProperty(SoConstants.CONTEXT_KEY_VF_COUNT, vfcount);
138
139         return startGuardAsync();
140     }
141
142     /**
143      * If the response does not indicate that the request has been completed, then sleep a
144      * bit and issue a "get".
145      */
146     @Override
147     protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
148                     Response rawResponse, SoResponse response) {
149
150         // see if the request has "completed", whether or not it was successful
151         if (rawResponse.getStatus() == 200) {
152             String requestState = getRequestState(response);
153             if (COMPLETE.equalsIgnoreCase(requestState)) {
154                 successfulCompletion();
155                 return CompletableFuture
156                                 .completedFuture(setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
157             }
158
159             if (FAILED.equalsIgnoreCase(requestState)) {
160                 return CompletableFuture
161                                 .completedFuture(setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
162             }
163         }
164
165         // still incomplete
166
167         // need a request ID with which to query
168         if (response.getRequestReferences() == null || response.getRequestReferences().getRequestId() == null) {
169             throw new IllegalArgumentException("missing request ID in response");
170         }
171
172         // see if the limit for the number of "gets" has been reached
173         if (getCount++ >= getMaxGets()) {
174             logger.warn("{}: execeeded 'get' limit {} for {}", getFullName(), getMaxGets(), params.getRequestId());
175             setOutcome(outcome, PolicyResult.FAILURE_TIMEOUT);
176             outcome.setMessage(SO_RESPONSE_CODE + " " + outcome.getMessage());
177             return CompletableFuture.completedFuture(outcome);
178         }
179
180         // sleep and then perform a "get" operation
181         Function<Void, CompletableFuture<OperationOutcome>> doGet = unused -> issueGet(outcome, response);
182         return sleep(getWaitMsGet(), TimeUnit.MILLISECONDS).thenComposeAsync(doGet);
183     }
184
185     /**
186      * Invoked when a request completes successfully.
187      */
188     protected void successfulCompletion() {
189         // do nothing
190     }
191
192     /**
193      * Issues a "get" request to see if the original request is complete yet.
194      *
195      * @param outcome outcome to be populated with the response
196      * @param response previous response
197      * @return a future that can be used to cancel the "get" request or await its response
198      */
199     private CompletableFuture<OperationOutcome> issueGet(OperationOutcome outcome, SoResponse response) {
200         String path = getPathGet() + response.getRequestReferences().getRequestId();
201         String url = getClient().getBaseUrl() + path;
202
203         logger.debug("{}: 'get' count {} for {}", getFullName(), getCount, params.getRequestId());
204
205         logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
206
207         // TODO should this use "path" or the full "url"?
208         return handleResponse(outcome, url, callback -> getClient().get(callback, path, null));
209     }
210
211     /**
212      * Gets the request state of a response.
213      *
214      * @param response response from which to get the state
215      * @return the request state of the response, or {@code null} if it does not exist
216      */
217     protected String getRequestState(SoResponse response) {
218         SoRequest request = response.getRequest();
219         if (request == null) {
220             return null;
221         }
222
223         SoRequestStatus status = request.getRequestStatus();
224         if (status == null) {
225             return null;
226         }
227
228         return status.getRequestState();
229     }
230
231     /**
232      * Treats everything as a success, so we always go into
233      * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
234      */
235     @Override
236     protected boolean isSuccess(Response rawResponse, SoResponse response) {
237         return true;
238     }
239
240     /**
241      * Prepends the message with the http status code.
242      */
243     @Override
244     public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
245                     SoResponse response) {
246
247         // set default result and message
248         setOutcome(outcome, result);
249
250         outcome.setMessage(rawResponse.getStatus() + " " + outcome.getMessage());
251         return outcome;
252     }
253
254     protected SoModelInfo prepareSoModelInfo() {
255         Target target = params.getTarget();
256         if (target == null) {
257             throw new IllegalArgumentException("missing Target");
258         }
259
260         if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
261                         || target.getModelName() == null || target.getModelVersion() == null
262                         || target.getModelVersionId() == null) {
263             throw new IllegalArgumentException("missing VF Module model");
264         }
265
266         SoModelInfo soModelInfo = new SoModelInfo();
267         soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
268         soModelInfo.setModelInvariantId(target.getModelInvariantId());
269         soModelInfo.setModelName(target.getModelName());
270         soModelInfo.setModelVersion(target.getModelVersion());
271         soModelInfo.setModelVersionId(target.getModelVersionId());
272         soModelInfo.setModelType("vfModule");
273         return soModelInfo;
274     }
275
276     /**
277      * Construct requestInfo for the SO requestDetails.
278      *
279      * @return SO request information
280      */
281     protected SoRequestInfo constructRequestInfo() {
282         SoRequestInfo soRequestInfo = new SoRequestInfo();
283         soRequestInfo.setSource("POLICY");
284         soRequestInfo.setSuppressRollback(false);
285         soRequestInfo.setRequestorId("policy");
286         return soRequestInfo;
287     }
288
289     /**
290      * Builds the request parameters from the policy payload.
291      */
292     protected SoRequestParameters buildRequestParameters() {
293         if (params.getPayload() == null) {
294             return null;
295         }
296
297         Object data = params.getPayload().get(REQ_PARAM_NM);
298         if (data == null) {
299             return null;
300         }
301
302         try {
303             return coder.decode(data.toString(), SoRequestParameters.class);
304         } catch (CoderException e) {
305             throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
306         }
307     }
308
309     /**
310      * Builds the configuration parameters from the policy payload.
311      */
312     protected List<Map<String, String>> buildConfigurationParameters() {
313         if (params.getPayload() == null) {
314             return null;
315         }
316
317         Object data = params.getPayload().get(CONFIG_PARAM_NM);
318         if (data == null) {
319             return null;
320         }
321
322         try {
323             @SuppressWarnings("unchecked")
324             List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
325             return result;
326         } catch (CoderException | RuntimeException e) {
327             throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
328         }
329     }
330
331     /*
332      * These methods extract data from the Custom Query and throw an
333      * IllegalArgumentException if the desired data item is not found.
334      */
335
336     protected GenericVnf getVnfItem(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
337         GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
338         if (vnf == null) {
339             throw new IllegalArgumentException("missing generic VNF");
340         }
341
342         return vnf;
343     }
344
345     protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
346         ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
347         if (vnfService == null) {
348             throw new IllegalArgumentException("missing VNF Service Item");
349         }
350
351         return vnfService;
352     }
353
354     protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
355         Tenant tenant = aaiCqResponse.getDefaultTenant();
356         if (tenant == null) {
357             throw new IllegalArgumentException("missing Tenant Item");
358         }
359
360         return tenant;
361     }
362
363     protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
364         CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
365         if (cloudRegion == null) {
366             throw new IllegalArgumentException("missing Cloud Region");
367         }
368
369         return cloudRegion;
370     }
371
372     // these may be overridden by junit tests
373
374     /**
375      * Gets the wait time, in milliseconds, between "get" requests.
376      *
377      * @return the wait time, in milliseconds, between "get" requests
378      */
379     public long getWaitMsGet() {
380         return TimeUnit.MILLISECONDS.convert(getWaitSecGet(), TimeUnit.SECONDS);
381     }
382
383     public int getMaxGets() {
384         return config.getMaxGets();
385     }
386
387     public String getPathGet() {
388         return config.getPathGet();
389     }
390
391     public int getWaitSecGet() {
392         return config.getWaitSecGet();
393     }
394 }