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 java.util.ArrayList;
24 import java.util.List;
26 import java.util.Optional;
27 import java.util.concurrent.CompletableFuture;
28 import java.util.concurrent.TimeUnit;
29 import java.util.function.Function;
30 import javax.ws.rs.core.Response;
32 import org.onap.aai.domain.yang.CloudRegion;
33 import org.onap.aai.domain.yang.GenericVnf;
34 import org.onap.aai.domain.yang.ServiceInstance;
35 import org.onap.aai.domain.yang.Tenant;
36 import org.onap.policy.aai.AaiConstants;
37 import org.onap.policy.aai.AaiCqResponse;
38 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
39 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
40 import org.onap.policy.common.utils.coder.Coder;
41 import org.onap.policy.common.utils.coder.CoderException;
42 import org.onap.policy.common.utils.coder.StandardCoder;
43 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
44 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
45 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
46 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpConfig;
47 import org.onap.policy.controlloop.policy.PolicyResult;
48 import org.onap.policy.controlloop.policy.Target;
49 import org.onap.policy.so.SoModelInfo;
50 import org.onap.policy.so.SoRequest;
51 import org.onap.policy.so.SoRequestInfo;
52 import org.onap.policy.so.SoRequestParameters;
53 import org.onap.policy.so.SoRequestStatus;
54 import org.onap.policy.so.SoResponse;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * Superclass for SDNC Operators. Note: subclasses should invoke {@link #resetGetCount()}
60 * each time they issue an HTTP request.
62 public abstract class SoOperation extends HttpOperation<SoResponse> {
63 private static final Logger logger = LoggerFactory.getLogger(SoOperation.class);
64 private static final Coder coder = new StandardCoder();
66 public static final String FAILED = "FAILED";
67 public static final String COMPLETE = "COMPLETE";
68 public static final int SO_RESPONSE_CODE = 999;
70 // fields within the policy payload
71 public static final String REQ_PARAM_NM = "requestParameters";
72 public static final String CONFIG_PARAM_NM = "configurationParameters";
74 private final SoConfig config;
76 // values extracted from the parameter Target
77 private final String modelCustomizationId;
78 private final String modelInvariantId;
79 private final String modelVersionId;
81 private final String vfCountKey;
84 * Number of "get" requests issued so far, on the current operation attempt.
91 * Constructs the object.
93 * @param params operation parameters
94 * @param config configuration for this operation
96 public SoOperation(ControlLoopOperationParams params, HttpConfig config) {
97 super(params, config, SoResponse.class);
98 this.config = (SoConfig) config;
100 verifyNotNull("Target information", params.getTarget());
102 this.modelCustomizationId = params.getTarget().getModelCustomizationId();
103 this.modelInvariantId = params.getTarget().getModelInvariantId();
104 this.modelVersionId = params.getTarget().getModelVersionId();
106 vfCountKey = SoConstants.VF_COUNT_PREFIX + "[" + modelCustomizationId + "][" + modelInvariantId + "]["
107 + modelVersionId + "]";
111 * Subclasses should invoke this before issuing their first HTTP request.
113 protected void resetGetCount() {
118 * Validates that the parameters contain the required target information to extract
119 * the VF count from the custom query.
121 protected void validateTarget() {
122 verifyNotNull("model-customization-id", modelCustomizationId);
123 verifyNotNull("model-invariant-id", modelInvariantId);
124 verifyNotNull("model-version-id", modelVersionId);
127 private void verifyNotNull(String type, Object value) {
129 throw new IllegalArgumentException("missing " + type + " for guard payload");
137 protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
138 return startGuardAsync();
144 * @return a future to cancel or await the VF Count
146 @SuppressWarnings("unchecked")
147 protected CompletableFuture<OperationOutcome> obtainVfCount() {
148 if (params.getContext().contains(vfCountKey)) {
149 // already have the VF count
153 // need custom query from which to extract the VF count
154 ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
155 .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
157 // run Custom Query and then extract the VF count
158 return sequence(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::storeVfCount);
162 * Stores the VF count.
164 * @return {@code null}
166 private CompletableFuture<OperationOutcome> storeVfCount() {
167 if (!params.getContext().contains(vfCountKey)) {
168 AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
169 int vfcount = cq.getVfModuleCount(modelCustomizationId, modelInvariantId, modelVersionId);
171 params.getContext().setProperty(vfCountKey, vfcount);
177 protected int getVfCount() {
178 return params.getContext().getProperty(vfCountKey);
181 protected void setVfCount(int vfCount) {
182 params.getContext().setProperty(vfCountKey, vfCount);
186 * If the response does not indicate that the request has been completed, then sleep a
187 * bit and issue a "get".
190 protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
191 Response rawResponse, SoResponse response) {
193 // see if the request has "completed", whether or not it was successful
194 if (rawResponse.getStatus() == 200) {
195 String requestState = getRequestState(response);
196 if (COMPLETE.equalsIgnoreCase(requestState)) {
197 successfulCompletion();
198 return CompletableFuture
199 .completedFuture(setOutcome(outcome, PolicyResult.SUCCESS, rawResponse, response));
202 if (FAILED.equalsIgnoreCase(requestState)) {
203 return CompletableFuture
204 .completedFuture(setOutcome(outcome, PolicyResult.FAILURE, rawResponse, response));
210 // need a request ID with which to query
211 if (response.getRequestReferences() == null || response.getRequestReferences().getRequestId() == null) {
212 throw new IllegalArgumentException("missing request ID in response");
215 // see if the limit for the number of "gets" has been reached
216 if (getCount++ >= getMaxGets()) {
217 logger.warn("{}: execeeded 'get' limit {} for {}", getFullName(), getMaxGets(), params.getRequestId());
218 setOutcome(outcome, PolicyResult.FAILURE_TIMEOUT);
219 outcome.setMessage(SO_RESPONSE_CODE + " " + outcome.getMessage());
220 return CompletableFuture.completedFuture(outcome);
223 // sleep and then perform a "get" operation
224 Function<Void, CompletableFuture<OperationOutcome>> doGet = unused -> issueGet(outcome, response);
225 return sleep(getWaitMsGet(), TimeUnit.MILLISECONDS).thenComposeAsync(doGet);
229 * Invoked when a request completes successfully.
231 protected void successfulCompletion() {
236 * Issues a "get" request to see if the original request is complete yet.
238 * @param outcome outcome to be populated with the response
239 * @param response previous response
240 * @return a future that can be used to cancel the "get" request or await its response
242 private CompletableFuture<OperationOutcome> issueGet(OperationOutcome outcome, SoResponse response) {
243 String path = getPathGet() + response.getRequestReferences().getRequestId();
244 String url = getClient().getBaseUrl() + path;
246 logger.debug("{}: 'get' count {} for {}", getFullName(), getCount, params.getRequestId());
248 logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
250 // TODO should this use "path" or the full "url"?
251 return handleResponse(outcome, url, callback -> getClient().get(callback, path, null));
255 * Gets the request state of a response.
257 * @param response response from which to get the state
258 * @return the request state of the response, or {@code null} if it does not exist
260 protected String getRequestState(SoResponse response) {
261 SoRequest request = response.getRequest();
262 if (request == null) {
266 SoRequestStatus status = request.getRequestStatus();
267 if (status == null) {
271 return status.getRequestState();
275 * Treats everything as a success, so we always go into
276 * {@link #postProcessResponse(OperationOutcome, String, Response, SoResponse)}.
279 protected boolean isSuccess(Response rawResponse, SoResponse response) {
284 * Prepends the message with the http status code.
287 public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, Response rawResponse,
288 SoResponse response) {
290 // set default result and message
291 setOutcome(outcome, result);
293 outcome.setMessage(rawResponse.getStatus() + " " + outcome.getMessage());
297 protected SoModelInfo prepareSoModelInfo() {
298 Target target = params.getTarget();
299 if (target == null) {
300 throw new IllegalArgumentException("missing Target");
303 if (target.getModelCustomizationId() == null || target.getModelInvariantId() == null
304 || target.getModelName() == null || target.getModelVersion() == null
305 || target.getModelVersionId() == null) {
306 throw new IllegalArgumentException("missing VF Module model");
309 SoModelInfo soModelInfo = new SoModelInfo();
310 soModelInfo.setModelCustomizationId(target.getModelCustomizationId());
311 soModelInfo.setModelInvariantId(target.getModelInvariantId());
312 soModelInfo.setModelName(target.getModelName());
313 soModelInfo.setModelVersion(target.getModelVersion());
314 soModelInfo.setModelVersionId(target.getModelVersionId());
315 soModelInfo.setModelType("vfModule");
320 * Construct requestInfo for the SO requestDetails.
322 * @return SO request information
324 protected SoRequestInfo constructRequestInfo() {
325 SoRequestInfo soRequestInfo = new SoRequestInfo();
326 soRequestInfo.setSource("POLICY");
327 soRequestInfo.setSuppressRollback(false);
328 soRequestInfo.setRequestorId("policy");
329 return soRequestInfo;
333 * Builds the request parameters from the policy payload.
335 protected Optional<SoRequestParameters> buildRequestParameters() {
336 if (params.getPayload() == null) {
337 return Optional.empty();
340 Object data = params.getPayload().get(REQ_PARAM_NM);
342 return Optional.empty();
346 return Optional.of(coder.decode(data.toString(), SoRequestParameters.class));
347 } catch (CoderException e) {
348 throw new IllegalArgumentException("invalid payload value: " + REQ_PARAM_NM);
353 * Builds the configuration parameters from the policy payload.
355 protected Optional<List<Map<String, String>>> buildConfigurationParameters() {
356 if (params.getPayload() == null) {
357 return Optional.empty();
360 Object data = params.getPayload().get(CONFIG_PARAM_NM);
362 return Optional.empty();
366 @SuppressWarnings("unchecked")
367 List<Map<String, String>> result = coder.decode(data.toString(), ArrayList.class);
368 return Optional.of(result);
369 } catch (CoderException | RuntimeException e) {
370 throw new IllegalArgumentException("invalid payload value: " + CONFIG_PARAM_NM);
375 * These methods extract data from the Custom Query and throw an
376 * IllegalArgumentException if the desired data item is not found.
379 protected GenericVnf getVnfItem(AaiCqResponse aaiCqResponse, SoModelInfo soModelInfo) {
380 GenericVnf vnf = aaiCqResponse.getGenericVnfByVfModuleModelInvariantId(soModelInfo.getModelInvariantId());
382 throw new IllegalArgumentException("missing generic VNF");
388 protected ServiceInstance getServiceInstance(AaiCqResponse aaiCqResponse) {
389 ServiceInstance vnfService = aaiCqResponse.getServiceInstance();
390 if (vnfService == null) {
391 throw new IllegalArgumentException("missing VNF Service Item");
397 protected Tenant getDefaultTenant(AaiCqResponse aaiCqResponse) {
398 Tenant tenant = aaiCqResponse.getDefaultTenant();
399 if (tenant == null) {
400 throw new IllegalArgumentException("missing Tenant Item");
406 protected CloudRegion getDefaultCloudRegion(AaiCqResponse aaiCqResponse) {
407 CloudRegion cloudRegion = aaiCqResponse.getDefaultCloudRegion();
408 if (cloudRegion == null) {
409 throw new IllegalArgumentException("missing Cloud Region");
415 // these may be overridden by junit tests
418 * Gets the wait time, in milliseconds, between "get" requests.
420 * @return the wait time, in milliseconds, between "get" requests
422 public long getWaitMsGet() {
423 return TimeUnit.MILLISECONDS.convert(getWaitSecGet(), TimeUnit.SECONDS);
426 public int getMaxGets() {
427 return config.getMaxGets();
430 public String getPathGet() {
431 return config.getPathGet();
434 public int getWaitSecGet() {
435 return config.getWaitSecGet();