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 com.google.gson.JsonParseException;
26 import com.google.gson.TypeAdapter;
27 import com.google.gson.stream.JsonReader;
28 import com.google.gson.stream.JsonToken;
29 import com.google.gson.stream.JsonWriter;
30 import java.io.IOException;
31 import java.time.LocalDateTime;
32 import java.time.format.DateTimeFormatter;
33 import java.time.format.DateTimeParseException;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.List;
38 import java.util.Optional;
39 import java.util.concurrent.CompletableFuture;
40 import java.util.concurrent.TimeUnit;
41 import java.util.function.Function;
42 import javax.ws.rs.core.MediaType;
43 import javax.ws.rs.core.Response;
45 import org.onap.aai.domain.yang.CloudRegion;
46 import org.onap.aai.domain.yang.GenericVnf;
47 import org.onap.aai.domain.yang.ServiceInstance;
48 import org.onap.aai.domain.yang.Tenant;
49 import org.onap.policy.aai.AaiConstants;
50 import org.onap.policy.aai.AaiCqResponse;
51 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
52 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
53 import org.onap.policy.common.gson.GsonMessageBodyHandler;
54 import org.onap.policy.common.utils.coder.Coder;
55 import org.onap.policy.common.utils.coder.CoderException;
56 import org.onap.policy.common.utils.coder.StandardCoder;
57 import org.onap.policy.common.utils.coder.StandardCoderObject;
58 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
59 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
60 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
61 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpConfig;
62 import org.onap.policy.controlloop.policy.PolicyResult;
63 import org.onap.policy.controlloop.policy.Target;
64 import org.onap.policy.so.SoCloudConfiguration;
65 import org.onap.policy.so.SoModelInfo;
66 import org.onap.policy.so.SoRequest;
67 import org.onap.policy.so.SoRequestInfo;
68 import org.onap.policy.so.SoRequestParameters;
69 import org.onap.policy.so.SoRequestStatus;
70 import org.onap.policy.so.SoResponse;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
75 * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetGetCount()}
76 * each time they issue an HTTP request.
78 public abstract class SoOperation extends HttpOperation<SoResponse> {
79 private static final Logger logger = LoggerFactory.getLogger(SoOperation.class);
80 private static final Coder coder = new SoCoder();
82 public static final String PAYLOAD_KEY_VF_COUNT = "vfCount";
83 public static final String FAILED = "FAILED";
84 public static final String COMPLETE = "COMPLETE";
85 public static final int SO_RESPONSE_CODE = 999;
87 // fields within the policy payload
88 public static final String REQ_PARAM_NM = "requestParameters";
89 public static final String CONFIG_PARAM_NM = "configurationParameters";
91 private final SoConfig config;
93 // values extracted from the parameter Target
94 private final String modelCustomizationId;
95 private final String modelInvariantId;
96 private final String modelVersionId;
98 private final String vfCountKey;
101 * Number of "get" requests issued so far, on the current operation attempt.
104 private int getCount;
108 * Constructs the object.
110 * @param params operation parameters
111 * @param config configuration for this operation
113 public SoOperation(ControlLoopOperationParams params, HttpConfig config) {
114 super(params, config, SoResponse.class);
115 this.config = (SoConfig) config;
117 verifyNotNull("Target information", params.getTarget());
119 this.modelCustomizationId = params.getTarget().getModelCustomizationId();
120 this.modelInvariantId = params.getTarget().getModelInvariantId();
121 this.modelVersionId = params.getTarget().getModelVersionId();
123 vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
124 + modelVersionId + "]";
128 * Subclasses should invoke this before issuing their first HTTP request.
130 protected void resetGetCount() {
132 setSubRequestId(null);
136 * Validates that the parameters contain the required target information to extract
137 * the VF count from the custom query.
139 protected void validateTarget() {
140 verifyNotNull("modelCustomizationId", modelCustomizationId);
141 verifyNotNull("modelInvariantId", modelInvariantId);
142 verifyNotNull("modelVersionId", modelVersionId);
145 private void verifyNotNull(String type, Object value) {
147 throw new IllegalArgumentException("missing " + type + " for guard payload");
155 protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
156 return startGuardAsync();
162 * @return a future to cancel or await the VF Count
164 @SuppressWarnings("unchecked")
165 protected CompletableFuture<OperationOutcome> obtainVfCount() {
166 if (params.getContext().contains(vfCountKey)) {
167 // already have the VF count
171 // need custom query from which to extract the VF count
172 ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
173 .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
175 // run Custom Query and then extract the VF count
176 return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
180 * Stores the VF count.
182 * @return {@code null}
184 private CompletableFuture<OperationOutcome> storeVfCount() {
185 if (!params.getContext().contains(vfCountKey)) {
186 AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
187 int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
189 params.getContext().setProperty(vfCountKey, vfcount);
195 protected int getVfCount() {
196 return params.getContext().getProperty(vfCountKey);
199 protected void setVfCount(int vfCount) {
200 params.getContext().setProperty(vfCountKey, vfCount);
204 * If the response does not indicate that the request has been completed, then sleep a
205 * bit and issue a "get".
208 protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
209 Response rawResponse, SoResponse response) {
211 // see if the request has "completed", whether or not it was successful
212 if (rawResponse.getStatus() == 200) {
213 String requestState = getRequestState(response);
214 if (COMPLETE.equalsIgnoreCase(requestState)) {
215 extractSubRequestId(response);
216 successfulCompletion();
217 return CompletableFuture
218 .completedFuture(setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
221 if (FAILED.equalsIgnoreCase(requestState)) {
222 extractSubRequestId(response);
223 return CompletableFuture
224 .completedFuture(setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
230 // need a request ID with which to query
231 if (getSubRequestId() == null && !extractSubRequestId(response)) {
232 throw new IllegalArgumentException("missing request ID in response");
235 // see if the limit for the number of "gets" has been reached
236 if (getCount++ >= getMaxGets()) {
237 logger.warn("{}: execeeded 'get' limit {} for {}", getFullName(), getMaxGets(), params.getRequestId());
238 setOutcome(outcome, PolicyResult.FAILURE_TIMEOUT);
239 outcome.setMessage(SO_RESPONSE_CODE + " " + outcome.getMessage());
240 return CompletableFuture.completedFuture(outcome);
243 // sleep and then perform a "get" operation
244 Function<Void, CompletableFuture<OperationOutcome>> doGet = unused -> issueGet(outcome);
245 return sleep(getWaitMsGet(), TimeUnit.MILLISECONDS).thenComposeAsync(doGet);
249 public void generateSubRequestId(int attempt) {
250 setSubRequestId(null);
253 private boolean extractSubRequestId(SoResponse response) {
254 if (response == null || response.getRequestReferences() == null
255 || response.getRequestReferences().getRequestId() == null) {
259 setSubRequestId(response.getRequestReferences().getRequestId());
264 * Invoked when a request completes successfully.
266 protected void successfulCompletion() {
271 * Issues a "get" request to see if the original request is complete yet.
273 * @param outcome outcome to be populated with the response
274 * @return a future that can be used to cancel the "get" request or await its response
276 private CompletableFuture<OperationOutcome> issueGet(OperationOutcome outcome) {
277 String path = getPathGet() + getSubRequestId();
278 String url = getClient().getBaseUrl() + path;
280 logger.debug("{}: 'get' count {} for {}", getFullName(), getCount, params.getRequestId());
282 logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
284 return handleResponse(outcome, url, callback -> getClient().get(callback, path, null));
288 * Gets the request state of a response.
290 * @param response response from which to get the state
291 * @return the request state of the response, or {@code null} if it does not exist
293 protected String getRequestState(SoResponse response) {
294 SoRequest request = response.getRequest();
295 if (request == null) {
299 SoRequestStatus status = request.getRequestStatus();
300 if (status == null) {
304 return status.getRequestState();
308 * Treats everything as a success, so we always go into
309 * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
312 protected boolean isSuccess(Response rawResponse, SoResponse response) {
317 * Prepends the message with the http status code.
320 public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
321 SoResponse response) {
323 // set default result and message
324 setOutcome(outcome, result);
326 outcome.setMessage(rawResponse.getStatus() + " " + outcome.getMessage());
330 protected SoModelInfo prepareSoModelInfo() {
331 Target target = params.getTarget();
332 if (target == null) {
333 throw new IllegalArgumentException("missing Target");
336 if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
337 || target.getModelName() == null || target.getModelVersion() == null
338 || target.getModelVersionId() == null) {
339 throw new IllegalArgumentException("missing VF Module model");
342 SoModelInfo soModelInfo = new SoModelInfo();
343 soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
344 soModelInfo.setModelInvariantId(target.getModelInvariantId());
345 soModelInfo.setModelName(target.getModelName());
346 soModelInfo.setModelVersion(target.getModelVersion());
347 soModelInfo.setModelVersionId(target.getModelVersionId());
348 soModelInfo.setModelType("vfModule");
353 * Construct requestInfo for the SO requestDetails.
355 * @return SO request information
357 protected SoRequestInfo constructRequestInfo() {
358 SoRequestInfo soRequestInfo = new SoRequestInfo();
359 soRequestInfo.setSource("POLICY");
360 soRequestInfo.setSuppressRollback(false);
361 soRequestInfo.setRequestorId("policy");
362 return soRequestInfo;
366 * Builds the request parameters from the policy payload.
368 protected Optional<SoRequestParameters> buildRequestParameters() {
369 if (params.getPayload() == null) {
370 return Optional.empty();
373 Object data = params.getPayload().get(REQ_PARAM_NM);
375 return Optional.empty();
379 return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
380 } catch (CoderException e) {
381 throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
386 * Builds the configuration parameters from the policy payload.
388 protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
389 if (params.getPayload() == null) {
390 return Optional.empty();
393 Object data = params.getPayload().get(CONFIG_PARAM_NM);
395 return Optional.empty();
399 @SuppressWarnings("unchecked")
400 List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
401 return Optional.of(result);
402 } catch (CoderException | RuntimeException e) {
403 throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
408 * Construct cloudConfiguration for the SO requestDetails. Overridden for custom
411 * @param tenantItem tenant item from A&AI named-query response
412 * @return SO cloud configuration
414 protected SoCloudConfiguration constructCloudConfigurationCq(Tenant tenantItem, CloudRegion cloudRegionItem) {
415 SoCloudConfiguration cloudConfiguration = new SoCloudConfiguration();
416 cloudConfiguration.setTenantId(tenantItem.getTenantId());
417 cloudConfiguration.setLcpCloudRegionId(cloudRegionItem.getCloudRegionId());
418 return cloudConfiguration;
422 * Create simple HTTP headers for unauthenticated requests to SO.
424 * @return the HTTP headers
426 protected Map<String, Object> createSimpleHeaders() {
427 Map<String, Object> headers = new HashMap<>();
428 headers.put("Accept", MediaType.APPLICATION_JSON);
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(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
438 GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
440 throw new IllegalArgumentException("missing generic VNF");
446 protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
447 ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
448 if (vnfService == null) {
449 throw new IllegalArgumentException("missing VNF Service Item");
455 protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
456 Tenant tenant = aaiCqResponse.getDefaultTenant();
457 if (tenant == null) {
458 throw new IllegalArgumentException("missing Tenant Item");
464 protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
465 CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
466 if (cloudRegion == null) {
467 throw new IllegalArgumentException("missing Cloud Region");
473 // these may be overridden by junit tests
476 * Gets the wait time, in milliseconds, between "get" requests.
478 * @return the wait time, in milliseconds, between "get" requests
480 public long getWaitMsGet() {
481 return TimeUnit.MILLISECONDS.convert(getWaitSecGet(), TimeUnit.SECONDS);
484 public int getMaxGets() {
485 return config.getMaxGets();
488 public String getPathGet() {
489 return config.getPathGet();
492 public int getWaitSecGet() {
493 return config.getWaitSecGet();
497 protected Coder makeCoder() {
502 * TODO: combine this adapter with existing LocalDateTimeTypeAdapter and eliminate the
503 * following two classes.
507 * GSON Type Adapter for "LocalDateTime" fields, that uses the standard
508 * RFC_1123_DATE_TIME formatter.
510 private static class SoLocalDateTimeTypeAdapter extends TypeAdapter<LocalDateTime> {
511 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME;
514 public LocalDateTime read(JsonReader in) throws IOException {
516 if (in.peek() == JsonToken.NULL) {
520 return LocalDateTime.parse(in.nextString(), FORMATTER);
523 } catch (DateTimeParseException e) {
524 throw new JsonParseException("invalid date", e);
529 public void write(JsonWriter out, LocalDateTime value) throws IOException {
533 String text = value.format(FORMATTER);
539 private static class SoCoder extends StandardCoder {
542 * Gson object used to encode and decode messages.
544 private static final Gson SO_GSON;
547 * Gson object used to encode messages in "pretty" format.
549 private static final Gson SO_GSON_PRETTY;
552 GsonBuilder builder = GsonMessageBodyHandler
553 .configBuilder(new GsonBuilder().registerTypeAdapter(StandardCoderObject.class,
554 new StandardTypeAdapter()))
555 .registerTypeAdapter(LocalDateTime.class, new SoLocalDateTimeTypeAdapter());
557 SO_GSON = builder.create();
558 SO_GSON_PRETTY = builder.setPrettyPrinting().create();
562 super(SO_GSON, SO_GSON_PRETTY);