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.impl.HttpOperation;
50 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
51 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpPollingConfig;
52 import org.onap.policy.controlloop.policy.PolicyResult;
53 import org.onap.policy.controlloop.policy.Target;
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
80 private final String modelCustomizationId;
81 private final String modelInvariantId;
82 private final String modelVersionId;
84 private final String vfCountKey;
88 * Constructs the object.
90 * @param params operation parameters
91 * @param config configuration for this operation
92 * @param propertyNames names of properties required by this operation
94 public SoOperation(ControlLoopOperationParams params, HttpPollingConfig config, List<String> propertyNames) {
95 super(params, config, SoResponse.class, propertyNames);
99 verifyNotNull("Target information", params.getTarget());
101 this.modelCustomizationId = params.getTarget().getModelCustomizationId();
102 this.modelInvariantId = params.getTarget().getModelInvariantId();
103 this.modelVersionId = params.getTarget().getModelVersionId();
105 vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
106 + modelVersionId + "]";
110 protected void resetPollCount() {
111 super.resetPollCount();
112 setSubRequestId(null);
116 * Validates that the parameters contain the required target information to extract
117 * the VF count from the custom query.
119 protected void validateTarget() {
120 verifyNotNull("modelCustomizationId", modelCustomizationId);
121 verifyNotNull("modelInvariantId", modelInvariantId);
122 verifyNotNull("modelVersionId", modelVersionId);
125 private void verifyNotNull(String type, Object value) {
127 throw new IllegalArgumentException("missing " + type + " for guard payload");
135 protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
136 return startGuardAsync();
142 * @return a future to cancel or await the VF Count
144 @SuppressWarnings("unchecked")
145 protected CompletableFuture<OperationOutcome> obtainVfCount() {
146 if (params.getContext().contains(vfCountKey)) {
147 // already have the VF count
151 // need custom query from which to extract the VF count
152 ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
153 .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
155 // run Custom Query and then extract the VF count
156 return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
160 * Stores the VF count.
162 * @return {@code null}
164 private CompletableFuture<OperationOutcome> storeVfCount() {
165 if (!params.getContext().contains(vfCountKey)) {
166 AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
167 int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
169 params.getContext().setProperty(vfCountKey, vfcount);
175 protected int getVfCount() {
176 if (containsProperty(OperationProperties.DATA_VF_COUNT)) {
177 return getProperty(OperationProperties.DATA_VF_COUNT);
180 return params.getContext().getProperty(vfCountKey);
183 protected void setVfCount(int vfCount) {
184 if (containsProperty(OperationProperties.DATA_VF_COUNT)) {
185 setProperty(OperationProperties.DATA_VF_COUNT, vfCount);
189 params.getContext().setProperty(vfCountKey, vfCount);
193 protected Status detmStatus(Response rawResponse, SoResponse response) {
194 if (rawResponse.getStatus() == 200) {
195 String requestState = getRequestState(response);
196 if (COMPLETE.equalsIgnoreCase(requestState)) {
197 extractSubRequestId(response);
198 return Status.SUCCESS;
201 if (FAILED.equalsIgnoreCase(requestState)) {
202 extractSubRequestId(response);
203 return Status.FAILURE;
209 // need a request ID with which to query
210 if (getSubRequestId() == null && !extractSubRequestId(response)) {
211 throw new IllegalArgumentException("missing request ID in response");
214 return Status.STILL_WAITING;
218 protected String getPollingPath() {
219 return super.getPollingPath() + getSubRequestId();
223 public void generateSubRequestId(int attempt) {
224 setSubRequestId(null);
227 private boolean extractSubRequestId(SoResponse response) {
228 if (response == null || response.getRequestReferences() == null
229 || response.getRequestReferences().getRequestId() == null) {
233 setSubRequestId(response.getRequestReferences().getRequestId());
238 * Gets the request state of a response.
240 * @param response response from which to get the state
241 * @return the request state of the response, or {@code null} if it does not exist
243 protected String getRequestState(SoResponse response) {
244 SoRequest request = response.getRequest();
245 if (request == null) {
249 SoRequestStatus status = request.getRequestStatus();
250 if (status == null) {
254 return status.getRequestState();
258 * Treats everything as a success, so we always go into
259 * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
262 protected boolean isSuccess(Response rawResponse, SoResponse response) {
267 * Prepends the message with the http status code.
270 public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
271 SoResponse response) {
273 // set default result and message
274 setOutcome(outcome, result);
276 int code = (result == PolicyResult.FAILURE_TIMEOUT ? SO_RESPONSE_CODE : rawResponse.getStatus());
278 outcome.setResponse(response);
279 outcome.setMessage(code + " " + outcome.getMessage());
283 protected SoModelInfo prepareSoModelInfo() {
284 Target target = params.getTarget();
285 if (target == null) {
286 throw new IllegalArgumentException("missing Target");
289 if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
290 || target.getModelName() == null || target.getModelVersion() == null
291 || target.getModelVersionId() == null) {
292 throw new IllegalArgumentException("missing VF Module model");
295 SoModelInfo soModelInfo = new SoModelInfo();
296 soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
297 soModelInfo.setModelInvariantId(target.getModelInvariantId());
298 soModelInfo.setModelName(target.getModelName());
299 soModelInfo.setModelVersion(target.getModelVersion());
300 soModelInfo.setModelVersionId(target.getModelVersionId());
301 soModelInfo.setModelType("vfModule");
306 * Construct requestInfo for the SO requestDetails.
308 * @return SO request information
310 protected SoRequestInfo constructRequestInfo() {
311 SoRequestInfo soRequestInfo = new SoRequestInfo();
312 soRequestInfo.setSource("POLICY");
313 soRequestInfo.setSuppressRollback(false);
314 soRequestInfo.setRequestorId("policy");
315 return soRequestInfo;
319 * Builds the request parameters from the policy payload.
321 protected Optional<SoRequestParameters> buildRequestParameters() {
322 if (params.getPayload() == null) {
323 return Optional.empty();
326 Object data = params.getPayload().get(REQ_PARAM_NM);
328 return Optional.empty();
332 return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
333 } catch (CoderException e) {
334 throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
339 * Builds the configuration parameters from the policy payload.
341 protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
342 if (params.getPayload() == null) {
343 return Optional.empty();
346 Object data = params.getPayload().get(CONFIG_PARAM_NM);
348 return Optional.empty();
352 @SuppressWarnings("unchecked")
353 List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
354 return Optional.of(result);
355 } catch (CoderException | RuntimeException e) {
356 throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
361 * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
364 * @param tenantItem tenant item from A&AI named-query response
365 * @return SO cloud configuration
367 protected SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
368 SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
369 cloudConfiguration.setTenantId(tenantItem.getTenantId());
370 cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
371 return cloudConfiguration;
375 * Create simple HTTP headers for unauthenticated requests to SO.
377 * @return the HTTP headers
379 protected Map<String, Object> createSimpleHeaders() {
380 Map<String, Object> headers = new HashMap<>();
381 headers.put("Accept", MediaType.APPLICATION_JSON);
386 * Gets an item from a property. If the property is not found, then it invokes the
387 * given function to retrieve it from the custom query data. If that fails as well,
388 * then an exception is thrown.
390 * @param propName property name
391 * @param getter method to extract the value from the custom query data
392 * @param errmsg error message to include in any exception
393 * @return the retrieved item
395 protected <T> T getItem(String propName, Function<AaiCqResponse, T> getter, String errmsg) {
396 if (containsProperty(propName)) {
397 return getProperty(propName);
400 final AaiCqResponse aaiCqResponse = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
401 T item = getter.apply(aaiCqResponse);
403 throw new IllegalArgumentException(errmsg);
410 * These methods extract data from the Custom Query and throw an
411 * IllegalArgumentException if the desired data item is not found.
414 protected GenericVnf getVnfItem(SoModelInfo soModelInfo) {
416 return getItem(OperationProperties.AAI_VNF,
417 cq -> cq.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId()),
418 "missing generic VNF");
422 protected ServiceInstance getServiceInstance() {
423 return getItem(OperationProperties.AAI_SERVICE, AaiCqResponse::getServiceInstance, "missing VNF Service Item");
426 protected Tenant getDefaultTenant() {
428 return getItem(OperationProperties.AAI_DEFAULT_TENANT,
429 AaiCqResponse::getDefaultTenant,
430 "missing Default Tenant Item");
434 protected CloudRegion getDefaultCloudRegion() {
436 return getItem(OperationProperties.AAI_DEFAULT_CLOUD_REGION,
437 AaiCqResponse::getDefaultCloudRegion,
438 "missing Default Cloud Region");
442 protected ModelVer getVnfModel(GenericVnf vnfItem) {
444 return getItem(OperationProperties.AAI_VNF_MODEL,
445 cq -> cq.getModelVerByVersionId(vnfItem.getModelVersionId()),
446 "missing generic VNF Model");
450 protected ModelVer getServiceModel(ServiceInstance vnfServiceItem) {
452 return getItem(OperationProperties.AAI_SERVICE_MODEL,
453 cq -> cq.getModelVerByVersionId(vnfServiceItem.getModelVersionId()),
454 "missing Service Model");
458 // these may be overridden by junit tests
461 protected Coder getCoder() {
465 private static class SoCoder extends StandardCoder {
468 * Gson object used to encode and decode messages.
470 private static final Gson SO_GSON;
473 * Gson object used to encode messages in "pretty" format.
475 private static final Gson SO_GSON_PRETTY;
478 GsonBuilder builder = GsonMessageBodyHandler
479 .configBuilder(new GsonBuilder().registerTypeAdapter(StandardCoderObject.class,
480 new StandardTypeAdapter()))
481 .registerTypeAdapter(LocalDateTime.class, new SoLocalDateTimeTypeAdapter());
483 SO_GSON = builder.create();
484 SO_GSON_PRETTY = builder.setPrettyPrinting().create();
488 super(SO_GSON, SO_GSON_PRETTY);