2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.controlloop.actor.so;
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;
30 import java.util.Optional;
31 import java.util.concurrent.CompletableFuture;
32 import java.util.concurrent.TimeUnit;
33 import java.util.function.Function;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.Response;
37 import org.onap.aai.domain.yang.CloudRegion;
38 import org.onap.aai.domain.yang.GenericVnf;
39 import org.onap.aai.domain.yang.ServiceInstance;
40 import org.onap.aai.domain.yang.Tenant;
41 import org.onap.policy.aai.AaiConstants;
42 import org.onap.policy.aai.AaiCqResponse;
43 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
44 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
45 import org.onap.policy.common.gson.GsonMessageBodyHandler;
46 import org.onap.policy.common.utils.coder.Coder;
47 import org.onap.policy.common.utils.coder.CoderException;
48 import org.onap.policy.common.utils.coder.StandardCoder;
49 import org.onap.policy.common.utils.coder.StandardCoderObject;
50 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
51 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
52 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
53 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpConfig;
54 import org.onap.policy.controlloop.policy.PolicyResult;
55 import org.onap.policy.controlloop.policy.Target;
56 import org.onap.policy.so.SoCloudConfiguration;
57 import org.onap.policy.so.SoModelInfo;
58 import org.onap.policy.so.SoRequest;
59 import org.onap.policy.so.SoRequestInfo;
60 import org.onap.policy.so.SoRequestParameters;
61 import org.onap.policy.so.SoRequestStatus;
62 import org.onap.policy.so.SoResponse;
63 import org.onap.policy.so.util.SoLocalDateTimeTypeAdapter;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
68 * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetGetCount()}
69 * each time they issue an HTTP request.
71 public abstract class SoOperation extends HttpOperation<SoResponse> {
72 private static final Logger logger = LoggerFactory.getLogger(SoOperation.class);
73 private static final Coder coder = new SoCoder();
75 public static final String PAYLOAD_KEY_VF_COUNT = "vfCount";
76 public static final String FAILED = "FAILED";
77 public static final String COMPLETE = "COMPLETE";
78 public static final int SO_RESPONSE_CODE = 999;
80 // fields within the policy payload
81 public static final String REQ_PARAM_NM = "requestParameters";
82 public static final String CONFIG_PARAM_NM = "configurationParameters";
84 private final SoConfig config;
86 // values extracted from the parameter Target
87 private final String modelCustomizationId;
88 private final String modelInvariantId;
89 private final String modelVersionId;
91 private final String vfCountKey;
94 * Number of "get" requests issued so far, on the current operation attempt.
101 * Constructs the object.
103 * @param params operation parameters
104 * @param config configuration for this operation
106 public SoOperation(ControlLoopOperationParams params, HttpConfig config) {
107 super(params, config, SoResponse.class);
108 this.config = (SoConfig) config;
110 verifyNotNull("Target information", params.getTarget());
112 this.modelCustomizationId = params.getTarget().getModelCustomizationId();
113 this.modelInvariantId = params.getTarget().getModelInvariantId();
114 this.modelVersionId = params.getTarget().getModelVersionId();
116 vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
117 + modelVersionId + "]";
121 * Subclasses should invoke this before issuing their first HTTP request.
123 protected void resetGetCount() {
125 setSubRequestId(null);
129 * Validates that the parameters contain the required target information to extract
130 * the VF count from the custom query.
132 protected void validateTarget() {
133 verifyNotNull("modelCustomizationId", modelCustomizationId);
134 verifyNotNull("modelInvariantId", modelInvariantId);
135 verifyNotNull("modelVersionId", modelVersionId);
138 private void verifyNotNull(String type, Object value) {
140 throw new IllegalArgumentException("missing " + type + " for guard payload");
148 protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
149 return startGuardAsync();
155 * @return a future to cancel or await the VF Count
157 @SuppressWarnings("unchecked")
158 protected CompletableFuture<OperationOutcome> obtainVfCount() {
159 if (params.getContext().contains(vfCountKey)) {
160 // already have the VF count
164 // need custom query from which to extract the VF count
165 ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
166 .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
168 // run Custom Query and then extract the VF count
169 return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
173 * Stores the VF count.
175 * @return {@code null}
177 private CompletableFuture<OperationOutcome> storeVfCount() {
178 if (!params.getContext().contains(vfCountKey)) {
179 AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
180 int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
182 params.getContext().setProperty(vfCountKey, vfcount);
188 protected int getVfCount() {
189 return params.getContext().getProperty(vfCountKey);
192 protected void setVfCount(int vfCount) {
193 params.getContext().setProperty(vfCountKey, vfCount);
197 * If the response does not indicate that the request has been completed, then sleep a
198 * bit and issue a "get".
201 protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
202 Response rawResponse, SoResponse response) {
204 // see if the request has "completed", whether or not it was successful
205 if (rawResponse.getStatus() == 200) {
206 String requestState = getRequestState(response);
207 if (COMPLETE.equalsIgnoreCase(requestState)) {
208 extractSubRequestId(response);
209 successfulCompletion();
210 return CompletableFuture
211 .completedFuture(setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
214 if (FAILED.equalsIgnoreCase(requestState)) {
215 extractSubRequestId(response);
216 return CompletableFuture
217 .completedFuture(setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
223 // need a request ID with which to query
224 if (getSubRequestId() == null && !extractSubRequestId(response)) {
225 throw new IllegalArgumentException("missing request ID in response");
228 // see if the limit for the number of "gets" has been reached
229 if (getCount++ >= getMaxGets()) {
230 logger.warn("{}: execeeded 'get' limit {} for {}", getFullName(), getMaxGets(), params.getRequestId());
231 setOutcome(outcome, PolicyResult.FAILURE_TIMEOUT);
232 outcome.setResponse(response);
233 outcome.setMessage(SO_RESPONSE_CODE + " " + outcome.getMessage());
234 return CompletableFuture.completedFuture(outcome);
237 // sleep and then perform a "get" operation
238 Function<Void, CompletableFuture<OperationOutcome>> doGet = unused -> issueGet(outcome);
239 return sleep(getWaitMsGet(), TimeUnit.MILLISECONDS).thenComposeAsync(doGet);
243 public void generateSubRequestId(int attempt) {
244 setSubRequestId(null);
247 private boolean extractSubRequestId(SoResponse response) {
248 if (response == null || response.getRequestReferences() == null
249 || response.getRequestReferences().getRequestId() == null) {
253 setSubRequestId(response.getRequestReferences().getRequestId());
258 * Invoked when a request completes successfully.
260 protected void successfulCompletion() {
265 * Issues a "get" request to see if the original request is complete yet.
267 * @param outcome outcome to be populated with the response
268 * @return a future that can be used to cancel the "get" request or await its response
270 private CompletableFuture<OperationOutcome> issueGet(OperationOutcome outcome) {
271 String path = getPathGet() + getSubRequestId();
272 String url = getClient().getBaseUrl() + path;
274 logger.debug("{}: 'get' count {} for {}", getFullName(), getCount, params.getRequestId());
276 logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
278 return handleResponse(outcome, url, callback -> getClient().get(callback, path, null));
282 * Gets the request state of a response.
284 * @param response response from which to get the state
285 * @return the request state of the response, or {@code null} if it does not exist
287 protected String getRequestState(SoResponse response) {
288 SoRequest request = response.getRequest();
289 if (request == null) {
293 SoRequestStatus status = request.getRequestStatus();
294 if (status == null) {
298 return status.getRequestState();
302 * Treats everything as a success, so we always go into
303 * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
306 protected boolean isSuccess(Response rawResponse, SoResponse response) {
311 * Prepends the message with the http status code.
314 public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
315 SoResponse response) {
317 // set default result and message
318 setOutcome(outcome, result);
320 outcome.setResponse(response);
321 outcome.setMessage(rawResponse.getStatus() + " " + outcome.getMessage());
325 protected SoModelInfo prepareSoModelInfo() {
326 Target target = params.getTarget();
327 if (target == null) {
328 throw new IllegalArgumentException("missing Target");
331 if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
332 || target.getModelName() == null || target.getModelVersion() == null
333 || target.getModelVersionId() == null) {
334 throw new IllegalArgumentException("missing VF Module model");
337 SoModelInfo soModelInfo = new SoModelInfo();
338 soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
339 soModelInfo.setModelInvariantId(target.getModelInvariantId());
340 soModelInfo.setModelName(target.getModelName());
341 soModelInfo.setModelVersion(target.getModelVersion());
342 soModelInfo.setModelVersionId(target.getModelVersionId());
343 soModelInfo.setModelType("vfModule");
348 * Construct requestInfo for the SO requestDetails.
350 * @return SO request information
352 protected SoRequestInfo constructRequestInfo() {
353 SoRequestInfo soRequestInfo = new SoRequestInfo();
354 soRequestInfo.setSource("POLICY");
355 soRequestInfo.setSuppressRollback(false);
356 soRequestInfo.setRequestorId("policy");
357 return soRequestInfo;
361 * Builds the request parameters from the policy payload.
363 protected Optional<SoRequestParameters> buildRequestParameters() {
364 if (params.getPayload() == null) {
365 return Optional.empty();
368 Object data = params.getPayload().get(REQ_PARAM_NM);
370 return Optional.empty();
374 return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
375 } catch (CoderException e) {
376 throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
381 * Builds the configuration parameters from the policy payload.
383 protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
384 if (params.getPayload() == null) {
385 return Optional.empty();
388 Object data = params.getPayload().get(CONFIG_PARAM_NM);
390 return Optional.empty();
394 @SuppressWarnings("unchecked")
395 List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
396 return Optional.of(result);
397 } catch (CoderException | RuntimeException e) {
398 throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
403 * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
406 * @param tenantItem tenant item from A&AI named-query response
407 * @return SO cloud configuration
409 protected SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
410 SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
411 cloudConfiguration.setTenantId(tenantItem.getTenantId());
412 cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
413 return cloudConfiguration;
417 * Create simple HTTP headers for unauthenticated requests to SO.
419 * @return the HTTP headers
421 protected Map<String, Object> createSimpleHeaders() {
422 Map<String, Object> headers = new HashMap<>();
423 headers.put("Accept", MediaType.APPLICATION_JSON);
428 * These methods extract data from the Custom Query and throw an
429 * IllegalArgumentException if the desired data item is not found.
432 protected GenericVnf getVnfItem(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
433 GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
435 throw new IllegalArgumentException("missing generic VNF");
441 protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
442 ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
443 if (vnfService == null) {
444 throw new IllegalArgumentException("missing VNF Service Item");
450 protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
451 Tenant tenant = aaiCqResponse.getDefaultTenant();
452 if (tenant == null) {
453 throw new IllegalArgumentException("missing Tenant Item");
459 protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
460 CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
461 if (cloudRegion == null) {
462 throw new IllegalArgumentException("missing Cloud Region");
468 // these may be overridden by junit tests
471 * Gets the wait time, in milliseconds, between "get" requests.
473 * @return the wait time, in milliseconds, between "get" requests
475 public long getWaitMsGet() {
476 return TimeUnit.MILLISECONDS.convert(getWaitSecGet(), TimeUnit.SECONDS);
479 public int getMaxGets() {
480 return config.getMaxGets();
483 public String getPathGet() {
484 return config.getPathGet();
487 public int getWaitSecGet() {
488 return config.getWaitSecGet();
492 protected Coder makeCoder() {
496 private static class SoCoder extends StandardCoder {
499 * Gson object used to encode and decode messages.
501 private static final Gson SO_GSON;
504 * Gson object used to encode messages in "pretty" format.
506 private static final Gson SO_GSON_PRETTY;
509 GsonBuilder builder = GsonMessageBodyHandler
510 .configBuilder(new GsonBuilder().registerTypeAdapter(StandardCoderObject.class,
511 new StandardTypeAdapter()))
512 .registerTypeAdapter(LocalDateTime.class, new SoLocalDateTimeTypeAdapter());
514 SO_GSON = builder.create();
515 SO_GSON_PRETTY = builder.setPrettyPrinting().create();
519 super(SO_GSON, SO_GSON_PRETTY);