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 getCoder() {
 
 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);