2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2020 Wipro Limited.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.controlloop.actor.so;
24 import com.google.gson.Gson;
25 import com.google.gson.GsonBuilder;
26 import java.time.LocalDateTime;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
31 import java.util.Optional;
32 import java.util.concurrent.CompletableFuture;
33 import java.util.function.Function;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.Response;
36 import org.onap.aai.domain.yang.CloudRegion;
37 import org.onap.aai.domain.yang.GenericVnf;
38 import org.onap.aai.domain.yang.ModelVer;
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.gson.GsonMessageBodyHandler;
44 import org.onap.policy.common.utils.coder.Coder;
45 import org.onap.policy.common.utils.coder.CoderException;
46 import org.onap.policy.common.utils.coder.StandardCoder;
47 import org.onap.policy.common.utils.coder.StandardCoderObject;
48 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
49 import org.onap.policy.controlloop.actorserviceprovider.OperationProperties;
50 import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
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.HttpPollingConfig;
54 import org.onap.policy.so.SoCloudConfiguration;
55 import org.onap.policy.so.SoModelInfo;
56 import org.onap.policy.so.SoRequest;
57 import org.onap.policy.so.SoRequestInfo;
58 import org.onap.policy.so.SoRequestParameters;
59 import org.onap.policy.so.SoRequestStatus;
60 import org.onap.policy.so.SoResponse;
61 import org.onap.policy.so.util.SoLocalDateTimeTypeAdapter;
64 * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetPollCount()}
65 * each time they issue an HTTP request.
67 public abstract class SoOperation extends HttpOperation<SoResponse> {
68 private static final Coder coder = new SoCoder();
70 public static final String PAYLOAD_KEY_VF_COUNT = "vfCount";
71 public static final String FAILED = "FAILED";
72 public static final String COMPLETE = "COMPLETE";
73 public static final int SO_RESPONSE_CODE = 999;
75 // fields within the policy payload
76 public static final String REQ_PARAM_NM = "requestParameters";
77 public static final String CONFIG_PARAM_NM = "configurationParameters";
79 /* Values extracted from the parameter Target. These fields are required by any
80 subclasses that make use of prepareSoModelInfo().
82 private final String modelCustomizationId;
83 private final String modelInvariantId;
84 private final String modelVersionId;
85 private final String modelName;
86 private final String modelVersion;
89 private final String vfCountKey;
93 * Constructs the object.
95 * @param params operation parameters
96 * @param config configuration for this operation
97 * @param propertyNames names of properties required by this operation
99 public SoOperation(ControlLoopOperationParams params, HttpPollingConfig config, List<String> propertyNames) {
100 super(params, config, SoResponse.class, propertyNames);
102 this.modelCustomizationId = null;
103 this.modelInvariantId = null;
104 this.modelVersionId = null;
105 this.modelVersion = null;
106 this.modelName = null;
107 this.vfCountKey = null;
109 verifyNotNull("Target information", params.getTargetType());
113 * Constructs the object.
115 * @param params operation parameters
116 * @param config configuration for this operation
117 * @param propertyNames names of properties required by this operation
118 * @param targetEntityIds Target Entity information
120 public SoOperation(ControlLoopOperationParams params, HttpPollingConfig config, List<String> propertyNames,
121 Map<String, String> targetEntityIds) {
122 super(params, config, SoResponse.class, propertyNames);
124 verifyNotNull("Target entity Ids information", targetEntityIds);
126 this.modelCustomizationId = targetEntityIds
127 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_CUSTOMIZATION_ID);
128 this.modelInvariantId = targetEntityIds
129 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_INVARIANT_ID);
130 this.modelVersionId = targetEntityIds
131 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_VERSION_ID);
132 this.modelVersion = targetEntityIds
133 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_VERSION);
134 this.modelName = targetEntityIds
135 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_NAME);
137 this.vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
138 + modelVersionId + "]";
140 verifyNotNull("Target information", params.getTargetType());
144 protected void resetPollCount() {
145 super.resetPollCount();
146 setSubRequestId(null);
150 * Validates that the parameters contain the required target information to extract
151 * the VF count from the custom query.
153 protected void validateTarget() {
154 verifyNotNull(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_CUSTOMIZATION_ID, modelCustomizationId);
155 verifyNotNull(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_INVARIANT_ID, modelInvariantId);
156 verifyNotNull(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_VERSION_ID, modelVersionId);
159 private void verifyNotNull(String type, Object value) {
161 throw new IllegalArgumentException("missing " + type + " for guard payload");
169 protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
170 return startGuardAsync();
176 * @return a future to cancel or await the VF Count
178 @SuppressWarnings("unchecked")
179 protected CompletableFuture<OperationOutcome> obtainVfCount() {
180 if (params.getContext().contains(vfCountKey)) {
181 // already have the VF count
185 // need custom query from which to extract the VF count
186 ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
187 .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
189 // run Custom Query and then extract the VF count
190 return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
194 * Stores the VF count.
196 * @return {@code null}
198 private CompletableFuture<OperationOutcome> storeVfCount() {
199 if (!params.getContext().contains(vfCountKey)) {
200 AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
201 int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
203 params.getContext().setProperty(vfCountKey, vfcount);
209 protected int getVfCount() {
210 if (containsProperty(OperationProperties.DATA_VF_COUNT)) {
211 return getProperty(OperationProperties.DATA_VF_COUNT);
214 return params.getContext().getProperty(vfCountKey);
217 protected void setVfCount(int vfCount) {
218 if (containsProperty(OperationProperties.DATA_VF_COUNT)) {
219 setProperty(OperationProperties.DATA_VF_COUNT, vfCount);
223 params.getContext().setProperty(vfCountKey, vfCount);
227 protected Status detmStatus(Response rawResponse, SoResponse response) {
228 if (rawResponse.getStatus() == 200) {
229 String requestState = getRequestState(response);
230 if (COMPLETE.equalsIgnoreCase(requestState)) {
231 extractSubRequestId(response);
232 return Status.SUCCESS;
235 if (FAILED.equalsIgnoreCase(requestState)) {
236 extractSubRequestId(response);
237 return Status.FAILURE;
243 // need a request ID with which to query
244 if (getSubRequestId() == null && !extractSubRequestId(response)) {
245 throw new IllegalArgumentException("missing request ID in response");
248 return Status.STILL_WAITING;
252 protected String getPollingPath() {
253 return super.getPollingPath() + getSubRequestId();
257 public void generateSubRequestId(int attempt) {
258 setSubRequestId(null);
261 private boolean extractSubRequestId(SoResponse response) {
262 if (response == null || response.getRequestReferences() == null
263 || response.getRequestReferences().getRequestId() == null) {
267 setSubRequestId(response.getRequestReferences().getRequestId());
272 * Gets the request state of a response.
274 * @param response response from which to get the state
275 * @return the request state of the response, or {@code null} if it does not exist
277 protected String getRequestState(SoResponse response) {
278 SoRequest request = response.getRequest();
279 if (request == null) {
283 SoRequestStatus status = request.getRequestStatus();
284 if (status == null) {
288 return status.getRequestState();
292 * Treats everything as a success, so we always go into
293 * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
296 protected boolean isSuccess(Response rawResponse, SoResponse response) {
301 * Prepends the message with the http status code.
304 public OperationOutcome setOutcome(OperationOutcome outcome, OperationResult result, Response rawResponse,
305 SoResponse response) {
307 // set default result and message
308 setOutcome(outcome, result);
310 int code = (result == OperationResult.FAILURE_TIMEOUT ? SO_RESPONSE_CODE : rawResponse.getStatus());
312 outcome.setResponse(response);
313 outcome.setMessage(code + " " + outcome.getMessage());
317 protected SoModelInfo prepareSoModelInfo() {
318 SoModelInfo soModelInfo = new SoModelInfo();
319 soModelInfo.setModelCustomizationId(modelCustomizationId);
320 soModelInfo.setModelInvariantId(modelInvariantId);
321 soModelInfo.setModelName(modelName);
322 soModelInfo.setModelVersion(modelVersion);
323 soModelInfo.setModelVersionId(modelVersionId);
324 soModelInfo.setModelType("vfModule");
329 * Construct requestInfo for the SO requestDetails.
331 * @return SO request information
333 protected SoRequestInfo constructRequestInfo() {
334 SoRequestInfo soRequestInfo = new SoRequestInfo();
335 soRequestInfo.setSource("POLICY");
336 soRequestInfo.setSuppressRollback(false);
337 soRequestInfo.setRequestorId("policy");
338 return soRequestInfo;
342 * Builds the request parameters from the policy payload.
344 protected Optional<SoRequestParameters> buildRequestParameters() {
345 if (params.getPayload() == null) {
346 return Optional.empty();
349 Object data = params.getPayload().get(REQ_PARAM_NM);
351 return Optional.empty();
355 return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
356 } catch (CoderException e) {
357 throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
362 * Builds the configuration parameters from the policy payload.
364 protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
365 if (params.getPayload() == null) {
366 return Optional.empty();
369 Object data = params.getPayload().get(CONFIG_PARAM_NM);
371 return Optional.empty();
375 @SuppressWarnings("unchecked")
376 List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
377 return Optional.of(result);
378 } catch (CoderException | RuntimeException e) {
379 throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
384 * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
387 * @param tenantItem tenant item from A&AI named-query response
388 * @return SO cloud configuration
390 protected SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
391 SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
392 cloudConfiguration.setTenantId(tenantItem.getTenantId());
393 cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
394 return cloudConfiguration;
398 * Create simple HTTP headers for unauthenticated requests to SO.
400 * @return the HTTP headers
402 protected Map<String, Object> createSimpleHeaders() {
403 Map<String, Object> headers = new HashMap<>();
404 headers.put("Accept", MediaType.APPLICATION_JSON);
409 * Gets an item from a property. If the property is not found, then it invokes the
410 * given function to retrieve it from the custom query data. If that fails as well,
411 * then an exception is thrown.
413 * @param propName property name
414 * @param getter method to extract the value from the custom query data
415 * @param errmsg error message to include in any exception
416 * @return the retrieved item
418 protected <T> T getItem(String propName, Function<AaiCqResponse, T> getter, String errmsg) {
419 if (containsProperty(propName)) {
420 return getProperty(propName);
423 final AaiCqResponse aaiCqResponse = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
424 T item = getter.apply(aaiCqResponse);
426 throw new IllegalArgumentException(errmsg);
433 * These methods extract data from the Custom Query and throw an
434 * IllegalArgumentException if the desired data item is not found.
437 protected GenericVnf getVnfItem(SoModelInfo soModelInfo) {
439 return getItem(OperationProperties.AAI_VNF,
440 cq -> cq.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId()),
441 "missing generic VNF");
445 protected ServiceInstance getServiceInstance() {
446 return getItem(OperationProperties.AAI_SERVICE, AaiCqResponse::getServiceInstance, "missing VNF Service Item");
449 protected Tenant getDefaultTenant() {
451 return getItem(OperationProperties.AAI_DEFAULT_TENANT,
452 AaiCqResponse::getDefaultTenant,
453 "missing Default Tenant Item");
457 protected CloudRegion getDefaultCloudRegion() {
459 return getItem(OperationProperties.AAI_DEFAULT_CLOUD_REGION,
460 AaiCqResponse::getDefaultCloudRegion,
461 "missing Default Cloud Region");
465 protected ModelVer getVnfModel(GenericVnf vnfItem) {
467 return getItem(OperationProperties.AAI_VNF_MODEL,
468 cq -> cq.getModelVerByVersionId(vnfItem.getModelVersionId()),
469 "missing generic VNF Model");
473 protected ModelVer getServiceModel(ServiceInstance vnfServiceItem) {
475 return getItem(OperationProperties.AAI_SERVICE_MODEL,
476 cq -> cq.getModelVerByVersionId(vnfServiceItem.getModelVersionId()),
477 "missing Service Model");
481 // these may be overridden by junit tests
484 protected Coder getCoder() {
488 private static class SoCoder extends StandardCoder {
491 * Gson object used to encode and decode messages.
493 private static final Gson SO_GSON;
496 * Gson object used to encode messages in "pretty" format.
498 private static final Gson SO_GSON_PRETTY;
501 GsonBuilder builder = GsonMessageBodyHandler
502 .configBuilder(new GsonBuilder().registerTypeAdapter(StandardCoderObject.class,
503 new StandardTypeAdapter()))
504 .registerTypeAdapter(LocalDateTime.class, new SoLocalDateTimeTypeAdapter());
506 SO_GSON = builder.create();
507 SO_GSON_PRETTY = builder.setPrettyPrinting().create();
511 super(SO_GSON, SO_GSON_PRETTY);