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.function.Function;
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.Response;
35 import org.onap.aai.domain.yang.CloudRegion;
36 import org.onap.aai.domain.yang.GenericVnf;
37 import org.onap.aai.domain.yang.ModelVer;
38 import org.onap.aai.domain.yang.ServiceInstance;
39 import org.onap.aai.domain.yang.Tenant;
40 import org.onap.policy.aai.AaiConstants;
41 import org.onap.policy.aai.AaiCqResponse;
42 import org.onap.policy.common.gson.GsonMessageBodyHandler;
43 import org.onap.policy.common.utils.coder.Coder;
44 import org.onap.policy.common.utils.coder.CoderException;
45 import org.onap.policy.common.utils.coder.StandardCoder;
46 import org.onap.policy.common.utils.coder.StandardCoderObject;
47 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
48 import org.onap.policy.controlloop.actorserviceprovider.OperationProperties;
49 import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
50 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
51 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
52 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpPollingConfig;
53 import org.onap.policy.so.SoCloudConfiguration;
54 import org.onap.policy.so.SoModelInfo;
55 import org.onap.policy.so.SoRequest;
56 import org.onap.policy.so.SoRequestInfo;
57 import org.onap.policy.so.SoRequestParameters;
58 import org.onap.policy.so.SoRequestStatus;
59 import org.onap.policy.so.SoResponse;
60 import org.onap.policy.so.util.SoLocalDateTimeTypeAdapter;
63 * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetPollCount()}
64 * each time they issue an HTTP request.
66 public abstract class SoOperation extends HttpOperation<SoResponse> {
67 private static final Coder coder = new SoCoder();
69 public static final String PAYLOAD_KEY_VF_COUNT = "vfCount";
70 public static final String FAILED = "FAILED";
71 public static final String COMPLETE = "COMPLETE";
72 public static final int SO_RESPONSE_CODE = 999;
74 // fields within the policy payload
75 public static final String REQ_PARAM_NM = "requestParameters";
76 public static final String CONFIG_PARAM_NM = "configurationParameters";
78 // values extracted from the parameter Target
79 private final String modelCustomizationId;
80 private final String modelInvariantId;
81 private final String modelVersionId;
82 private final String modelName;
83 private final String modelVersion;
85 private final String vfCountKey;
89 * Constructs the object.
91 * @param params operation parameters
92 * @param config configuration for this operation
93 * @param propertyNames names of properties required by this operation
95 public SoOperation(ControlLoopOperationParams params, HttpPollingConfig config, List<String> propertyNames) {
96 super(params, config, SoResponse.class, propertyNames);
100 verifyNotNull("Target information", params.getTargetType());
102 verifyNotNull("Target entity Ids information", params.getTargetEntityIds());
104 this.modelCustomizationId = params.getTargetEntityIds()
105 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_CUSTOMIZATION_ID);
106 this.modelInvariantId = params.getTargetEntityIds()
107 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_INVARIANT_ID);
108 this.modelVersionId = params.getTargetEntityIds()
109 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_VERSION_ID);
110 this.modelVersion = params.getTargetEntityIds()
111 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_VERSION);
112 this.modelName = params.getTargetEntityIds()
113 .get(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_NAME);
115 vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
116 + modelVersionId + "]";
120 protected void resetPollCount() {
121 super.resetPollCount();
122 setSubRequestId(null);
126 * Validates that the parameters contain the required target information to extract
127 * the VF count from the custom query.
129 protected void validateTarget() {
130 verifyNotNull(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_CUSTOMIZATION_ID, modelCustomizationId);
131 verifyNotNull(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_INVARIANT_ID, modelInvariantId);
132 verifyNotNull(ControlLoopOperationParams.PARAMS_ENTITY_MODEL_VERSION_ID, modelVersionId);
135 private void verifyNotNull(String type, Object value) {
137 throw new IllegalArgumentException("missing " + type + " for guard payload");
145 protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
146 return startGuardAsync();
152 * @return a future to cancel or await the VF Count
154 @SuppressWarnings("unchecked")
155 protected CompletableFuture<OperationOutcome> obtainVfCount() {
156 if (params.getContext().contains(vfCountKey)) {
157 // already have the VF count
161 // need custom query from which to extract the VF count
162 ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
163 .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
165 // run Custom Query and then extract the VF count
166 return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
170 * Stores the VF count.
172 * @return {@code null}
174 private CompletableFuture<OperationOutcome> storeVfCount() {
175 if (!params.getContext().contains(vfCountKey)) {
176 AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
177 int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
179 params.getContext().setProperty(vfCountKey, vfcount);
185 protected int getVfCount() {
186 if (containsProperty(OperationProperties.DATA_VF_COUNT)) {
187 return getProperty(OperationProperties.DATA_VF_COUNT);
190 return params.getContext().getProperty(vfCountKey);
193 protected void setVfCount(int vfCount) {
194 if (containsProperty(OperationProperties.DATA_VF_COUNT)) {
195 setProperty(OperationProperties.DATA_VF_COUNT, vfCount);
199 params.getContext().setProperty(vfCountKey, vfCount);
203 protected Status detmStatus(Response rawResponse, SoResponse response) {
204 if (rawResponse.getStatus() == 200) {
205 String requestState = getRequestState(response);
206 if (COMPLETE.equalsIgnoreCase(requestState)) {
207 extractSubRequestId(response);
208 return Status.SUCCESS;
211 if (FAILED.equalsIgnoreCase(requestState)) {
212 extractSubRequestId(response);
213 return Status.FAILURE;
219 // need a request ID with which to query
220 if (getSubRequestId() == null && !extractSubRequestId(response)) {
221 throw new IllegalArgumentException("missing request ID in response");
224 return Status.STILL_WAITING;
228 protected String getPollingPath() {
229 return super.getPollingPath() + getSubRequestId();
233 public void generateSubRequestId(int attempt) {
234 setSubRequestId(null);
237 private boolean extractSubRequestId(SoResponse response) {
238 if (response == null || response.getRequestReferences() == null
239 || response.getRequestReferences().getRequestId() == null) {
243 setSubRequestId(response.getRequestReferences().getRequestId());
248 * Gets the request state of a response.
250 * @param response response from which to get the state
251 * @return the request state of the response, or {@code null} if it does not exist
253 protected String getRequestState(SoResponse response) {
254 SoRequest request = response.getRequest();
255 if (request == null) {
259 SoRequestStatus status = request.getRequestStatus();
260 if (status == null) {
264 return status.getRequestState();
268 * Treats everything as a success, so we always go into
269 * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
272 protected boolean isSuccess(Response rawResponse, SoResponse response) {
277 * Prepends the message with the http status code.
280 public OperationOutcome setOutcome(OperationOutcome outcome, OperationResult result, Response rawResponse,
281 SoResponse response) {
283 // set default result and message
284 setOutcome(outcome, result);
286 int code = (result == OperationResult.FAILURE_TIMEOUT ? SO_RESPONSE_CODE : rawResponse.getStatus());
288 outcome.setResponse(response);
289 outcome.setMessage(code + " " + outcome.getMessage());
293 protected SoModelInfo prepareSoModelInfo() {
294 SoModelInfo soModelInfo = new SoModelInfo();
295 soModelInfo.setModelCustomizationId(modelCustomizationId);
296 soModelInfo.setModelInvariantId(modelInvariantId);
297 soModelInfo.setModelName(modelName);
298 soModelInfo.setModelVersion(modelVersion);
299 soModelInfo.setModelVersionId(modelVersionId);
300 soModelInfo.setModelType("vfModule");
305 * Construct requestInfo for the SO requestDetails.
307 * @return SO request information
309 protected SoRequestInfo constructRequestInfo() {
310 SoRequestInfo soRequestInfo = new SoRequestInfo();
311 soRequestInfo.setSource("POLICY");
312 soRequestInfo.setSuppressRollback(false);
313 soRequestInfo.setRequestorId("policy");
314 return soRequestInfo;
318 * Builds the request parameters from the policy payload.
320 protected Optional<SoRequestParameters> buildRequestParameters() {
321 if (params.getPayload() == null) {
322 return Optional.empty();
325 Object data = params.getPayload().get(REQ_PARAM_NM);
327 return Optional.empty();
331 return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
332 } catch (CoderException e) {
333 throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
338 * Builds the configuration parameters from the policy payload.
340 protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
341 if (params.getPayload() == null) {
342 return Optional.empty();
345 Object data = params.getPayload().get(CONFIG_PARAM_NM);
347 return Optional.empty();
351 @SuppressWarnings("unchecked")
352 List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
353 return Optional.of(result);
354 } catch (CoderException | RuntimeException e) {
355 throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
360 * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
363 * @param tenantItem tenant item from A&AI named-query response
364 * @return SO cloud configuration
366 protected SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
367 SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
368 cloudConfiguration.setTenantId(tenantItem.getTenantId());
369 cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
370 return cloudConfiguration;
374 * Create simple HTTP headers for unauthenticated requests to SO.
376 * @return the HTTP headers
378 protected Map<String, Object> createSimpleHeaders() {
379 Map<String, Object> headers = new HashMap<>();
380 headers.put("Accept", MediaType.APPLICATION_JSON);
385 * Gets an item from a property. If the property is not found, then it invokes the
386 * given function to retrieve it from the custom query data. If that fails as well,
387 * then an exception is thrown.
389 * @param propName property name
390 * @param getter method to extract the value from the custom query data
391 * @param errmsg error message to include in any exception
392 * @return the retrieved item
394 protected <T> T getItem(String propName, Function<AaiCqResponse, T> getter, String errmsg) {
395 if (containsProperty(propName)) {
396 return getProperty(propName);
399 final AaiCqResponse aaiCqResponse = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
400 T item = getter.apply(aaiCqResponse);
402 throw new IllegalArgumentException(errmsg);
409 * These methods extract data from the Custom Query and throw an
410 * IllegalArgumentException if the desired data item is not found.
413 protected GenericVnf getVnfItem(SoModelInfo soModelInfo) {
415 return getItem(OperationProperties.AAI_VNF,
416 cq -> cq.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId()),
417 "missing generic VNF");
421 protected ServiceInstance getServiceInstance() {
422 return getItem(OperationProperties.AAI_SERVICE, AaiCqResponse::getServiceInstance, "missing VNF Service Item");
425 protected Tenant getDefaultTenant() {
427 return getItem(OperationProperties.AAI_DEFAULT_TENANT,
428 AaiCqResponse::getDefaultTenant,
429 "missing Default Tenant Item");
433 protected CloudRegion getDefaultCloudRegion() {
435 return getItem(OperationProperties.AAI_DEFAULT_CLOUD_REGION,
436 AaiCqResponse::getDefaultCloudRegion,
437 "missing Default Cloud Region");
441 protected ModelVer getVnfModel(GenericVnf vnfItem) {
443 return getItem(OperationProperties.AAI_VNF_MODEL,
444 cq -> cq.getModelVerByVersionId(vnfItem.getModelVersionId()),
445 "missing generic VNF Model");
449 protected ModelVer getServiceModel(ServiceInstance vnfServiceItem) {
451 return getItem(OperationProperties.AAI_SERVICE_MODEL,
452 cq -> cq.getModelVerByVersionId(vnfServiceItem.getModelVersionId()),
453 "missing Service Model");
457 // these may be overridden by junit tests
460 protected Coder getCoder() {
464 private static class SoCoder extends StandardCoder {
467 * Gson object used to encode and decode messages.
469 private static final Gson SO_GSON;
472 * Gson object used to encode messages in "pretty" format.
474 private static final Gson SO_GSON_PRETTY;
477 GsonBuilder builder = GsonMessageBodyHandler
478 .configBuilder(new GsonBuilder().registerTypeAdapter(StandardCoderObject.class,
479 new StandardTypeAdapter()))
480 .registerTypeAdapter(LocalDateTime.class, new SoLocalDateTimeTypeAdapter());
482 SO_GSON = builder.create();
483 SO_GSON_PRETTY = builder.setPrettyPrinting().create();
487 super(SO_GSON, SO_GSON_PRETTY);