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