2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2017 Amdocs
8 * =============================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
21 * ============LICENSE_END=========================================================
24 package org.onap.appc.adapter.ansible.impl;
27 import java.util.Properties;
29 import java.io.FileInputStream;
30 import java.io.InputStream;
31 import org.apache.commons.lang.StringUtils;
32 import org.json.JSONException;
33 import org.json.JSONObject;
34 import org.onap.appc.adapter.ansible.AnsibleAdapter;
35 import org.onap.appc.adapter.ansible.model.AnsibleMessageParser;
36 import org.onap.appc.adapter.ansible.model.AnsibleResult;
37 import org.onap.appc.adapter.ansible.model.AnsibleResultCodes;
38 import org.onap.appc.adapter.ansible.model.AnsibleServerEmulator;
39 import org.onap.appc.exceptions.APPCException;
40 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
41 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
43 import com.att.eelf.configuration.EELFLogger;
44 import com.att.eelf.configuration.EELFManager;
45 import org.onap.appc.encryption.EncryptionTool;
48 * This class implements the {@link AnsibleAdapter} interface. This interface
49 * defines the behaviors that our service provides.
51 public class AnsibleAdapterImpl implements AnsibleAdapter {
54 * The constant used to define the service name in the mapped diagnostic context
56 @SuppressWarnings("nls")
57 public static final String MDC_SERVICE = "service";
60 * The constant for the status code for a failed outcome
62 @SuppressWarnings("nls")
63 public static final String OUTCOME_FAILURE = "failure";
66 * The constant for the status code for a successful outcome
68 @SuppressWarnings("nls")
69 public static final String OUTCOME_SUCCESS = "success";
74 private static final String ADAPTER_NAME = "Ansible Adapter";
75 private static final String APPC_EXCEPTION_CAUGHT = "APPCException caught";
77 private static final String RESULT_CODE_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.result.code";
78 private static final String MESSAGE_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.message";
79 private static final String RESULTS_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.results";
80 private static final String OUTPUT_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.output";
81 private static final String ID_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.Id";
82 private static final String LOG_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.log";
84 private static final String CLIENT_TYPE_PROPERTY_NAME = "org.onap.appc.adapter.ansible.clientType";
85 private static final String TRUSTSTORE_PROPERTY_NAME = "org.onap.appc.adapter.ansible.trustStore";
86 private static final String TRUSTPASSWD_PROPERTY_NAME = "org.onap.appc.adapter.ansible.trustStore.trustPasswd";
87 private static final String TIMEOUT_PROPERTY_NAME = "org.onap.appc.adapter.ansible.timeout";
88 private static final String POLL_INTERVAL_PROPERTY_NAME = "org.onap.appc.adapter.ansible.pollInterval";
89 private static final String SOCKET_TIMEOUT_PROPERTY_NAME = "org.onap.appc.adapter.ansible.socketTimeout";
90 private static final String PASSWORD = "Password";
91 private static final String APPC_PROPS = "/appc.properties";
92 private static final String SDNC_CONFIG_DIR = "SDNC_CONFIG_DIR";
93 private static final String propDir = System.getenv(SDNC_CONFIG_DIR);
94 private Properties props;
95 private int defaultTimeout = 600 * 1000;
96 private int defaultSocketTimeout = 60 * 1000;
97 private int defaultPollInterval = 60 * 1000;
100 * The logger to be used
102 private static final EELFLogger logger = EELFManager.getInstance().getLogger(AnsibleAdapterImpl.class);
106 private ConnectionBuilder httpClient;
109 * Ansible API Message Handlers
111 private AnsibleMessageParser messageProcessor;
114 * indicator whether in test mode
116 private boolean testMode = false;
119 * server emulator object to be used if in test mode
121 private AnsibleServerEmulator testServer;
124 * This default constructor is used as a work around because the activator
125 * wasn't getting called
127 public AnsibleAdapterImpl() {
132 * Used for jUnit test and testing interface
134 public AnsibleAdapterImpl(boolean mode) {
136 testServer = new AnsibleServerEmulator();
137 messageProcessor = new AnsibleMessageParser();
141 * Returns the symbolic name of the adapter
143 * @return The adapter name
144 * @see org.onap.appc.adapter.rest.AnsibleAdapter#getAdapterName()
147 public String getAdapterName() {
153 * Method posts info to Context memory in case of an error and throws
154 * a SvcLogicException causing SLI to register this as a failure
156 @SuppressWarnings("static-method")
157 private void doFailure(SvcLogicContext svcLogic, int code, String message) throws SvcLogicException {
159 svcLogic.setStatus(OUTCOME_FAILURE);
160 svcLogic.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
161 svcLogic.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
163 throw new SvcLogicException("Ansible Adapter Error = " + message);
167 * initialize the Ansible adapter based on default and over-ride configuration
170 private void initialize() {
171 String path = propDir + APPC_PROPS;
172 File propFile = new File(path);
173 props = new Properties();
175 InputStream input = new FileInputStream(propFile);
177 } catch (Exception ex) {
178 logger.error("Error while reading appc.properties file" + ex.getMessage());
180 // Create the message processor instance
181 messageProcessor = new AnsibleMessageParser();
182 //continuing for checking timeout
184 String timeoutStr = props.getProperty(TIMEOUT_PROPERTY_NAME);
185 defaultTimeout = Integer.parseInt(timeoutStr) * 1000;
187 } catch (Exception e) {
188 defaultTimeout = 600 * 1000;
190 //continuing for checking timeout
192 String timeoutStr = props.getProperty(SOCKET_TIMEOUT_PROPERTY_NAME);
193 defaultSocketTimeout = Integer.parseInt(timeoutStr) * 1000;
195 } catch (Exception e) {
196 defaultSocketTimeout = 60 * 1000;
198 //continuing for checking timeout
200 String timeoutStr = props.getProperty(POLL_INTERVAL_PROPERTY_NAME);
201 defaultPollInterval = Integer.parseInt(timeoutStr) * 1000;
203 } catch (Exception e) {
204 defaultPollInterval = 60 * 1000;
206 logger.info("Initialized Ansible Adapter");
209 private ConnectionBuilder getHttpConn(int timeout, String serverIP) {
211 String path = propDir + APPC_PROPS;
212 File propFile = new File(path);
213 props = new Properties();
216 input = new FileInputStream(propFile);
218 } catch (Exception ex) {
219 // TODO Auto-generated catch block
220 logger.error("Error while reading appc.properties file" + ex.getMessage());
222 // Create the http client instance
223 // type of client is extracted from the property file parameter
224 // org.onap.appc.adapter.ansible.clientType
226 // 1. TRUST_ALL (trust all SSL certs). To be used ONLY in dev
227 // 2. TRUST_CERT (trust only those whose certificates have been stored in the
229 // 3. DEFAULT (trust only well known certificates). This is standard behavior to
231 // revert. To be used in PROD
232 ConnectionBuilder httpClient = null;
234 String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
235 logger.info("Ansible http client type set to " + clientType);
237 if ("TRUST_ALL".equals(clientType)) {
239 "Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
240 httpClient = new ConnectionBuilder(1, timeout);
241 } else if ("TRUST_CERT".equals(clientType)) {
242 // set path to keystore file
243 String trustStoreFile = props.getProperty(TRUSTSTORE_PROPERTY_NAME);
244 String key = props.getProperty(TRUSTPASSWD_PROPERTY_NAME);
245 char[] trustStorePasswd = EncryptionTool.getInstance().decrypt(key).toCharArray();
246 logger.info("Creating http client with trustmanager from " + trustStoreFile);
247 httpClient = new ConnectionBuilder(trustStoreFile, trustStorePasswd, timeout, serverIP);
249 logger.info("Creating http client with default behaviour");
250 httpClient = new ConnectionBuilder(0, timeout);
252 } catch (Exception e) {
253 logger.error("Error Getting HTTP Connection Builder due to Unknown Exception", e);
256 logger.info("Got HTTP Connection Builder");
260 // Public Method to post request to execute playbook. Posts the following back
261 // to Svc context memory
262 // org.onap.appc.adapter.ansible.req.code : 100 if successful
263 // org.onap.appc.adapter.ansible.req.messge : any message
264 // org.onap.appc.adapter.ansible.req.Id : a unique uuid to reference the request
266 public void reqExec(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
268 String playbookName = StringUtils.EMPTY;
269 String payload = StringUtils.EMPTY;
270 String agentUrl = StringUtils.EMPTY;
271 String user = StringUtils.EMPTY;
272 String password = StringUtils.EMPTY;
273 String id = StringUtils.EMPTY;
274 String timeout = StringUtils.EMPTY;
275 JSONObject jsonPayload;
278 // create json object to send request
279 jsonPayload = messageProcessor.reqMessage(params);
281 agentUrl = (String) jsonPayload.remove("AgentUrl");
282 user = (String) jsonPayload.remove("User");
283 password = (String)jsonPayload.remove(PASSWORD);
284 if(StringUtils.isNotBlank(password))
285 password = EncryptionTool.getInstance().decrypt(password);
286 id = jsonPayload.getString("Id");
287 timeout = jsonPayload.getString("Timeout");
288 if (StringUtils.isBlank(timeout))
290 payload = jsonPayload.toString();
291 ctx.setAttribute("AnsibleTimeout", timeout);
292 logger.info("Updated Payload = " + payload + " timeout = " + timeout);
293 } catch (APPCException e) {
294 logger.error(APPC_EXCEPTION_CAUGHT, e);
295 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
296 "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = "
298 } catch (JSONException e) {
299 logger.error("JSONException caught", e);
300 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
301 "Error constructing request for execution of playbook due to invalid JSON block. Reason = "
303 } catch (NumberFormatException e) {
304 logger.error("NumberFormatException caught", e);
305 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
306 "Error constructing request for execution of playbook due to invalid parameter values. Reason = "
311 String message = StringUtils.EMPTY;
314 // post the test request
315 logger.info("Posting ansible request = " + payload + " to url = " + agentUrl);
316 AnsibleResult testResult = postExecRequest(agentUrl, payload, user, password,ctx);
317 logger.info("Received response on ansible post request " + testResult.getStatusMessage());
318 // Process if HTTP was successful
319 if (testResult.getStatusCode() == 200) {
320 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
322 doFailure(ctx, testResult.getStatusCode(),
323 "Error posting request. Reason = " + testResult.getStatusMessage());
325 String output = StringUtils.EMPTY;
326 code = testResult.getStatusCode();
327 message = testResult.getStatusMessage();
328 output = testResult.getOutput();
329 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
330 String serverIp = testResult.getServerIp();
331 if (StringUtils.isBlank(serverIp))
332 ctx.setAttribute("ServerIP", serverIp);
334 ctx.setAttribute("ServerIP", "");
335 // Check status of test request returned by Agent
336 if (code == AnsibleResultCodes.PENDING.getValue()) {
337 logger.info(String.format("Submission of Test %s successful.", playbookName));
338 // test request accepted. We are in asynchronous case
340 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
342 } catch (APPCException e) {
343 logger.error(APPC_EXCEPTION_CAUGHT, e);
344 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
345 "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
348 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
349 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
350 ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
354 * Public method to query status of a specific request It blocks till the
355 * Ansible Server responds or the session times out (non-Javadoc)
357 * @see org.onap.appc.adapter.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
358 * org.onap.ccsdk.sli.core.sli.SvcLogicContext)
361 public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
364 String reqUri = StringUtils.EMPTY;
367 String serverIp = ctx.getAttribute("ServerIP");
368 if (StringUtils.isNotBlank(serverIp))
369 reqUri = messageProcessor.reqUriResultWithIP(params, serverIp);
371 reqUri = messageProcessor.reqUriResult(params);
372 logger.info("Got uri " + reqUri);
373 } catch (APPCException e) {
374 logger.error(APPC_EXCEPTION_CAUGHT, e);
375 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
376 "Error constructing request to retrieve result due to missing parameters. Reason = "
379 } catch (NumberFormatException e) {
380 logger.error("NumberFormatException caught", e);
381 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
382 "Error constructing request to retrieve result due to invalid parameters value. Reason = "
388 String message = StringUtils.EMPTY;
389 String results = StringUtils.EMPTY;
390 String output = StringUtils.EMPTY;
392 // Try to retrieve the test results (modify the URL for that)
393 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
394 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
395 code = testResult.getStatusCode();
396 message = testResult.getStatusMessage();
398 if (code == 200 || code == 400 || "FINISHED".equalsIgnoreCase(message)) {
399 logger.info("Parsing response from ansible Server = " + message);
400 // Valid HTTP. process the Ansible message
401 testResult = messageProcessor.parseGetResponse(message);
402 code = testResult.getStatusCode();
403 message = testResult.getStatusMessage();
404 results = testResult.getResults();
405 output = testResult.getOutput();
406 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
408 logger.info("Request response = " + message);
409 } catch (APPCException e) {
410 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
411 "Exception encountered retrieving result : " + e.getMessage());
415 // We were able to get and process the results. Determine if playbook succeeded
417 if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()) {
418 message = String.format("Ansible Request %s finished with Result = %s, Message = %s", params.get("Id"),
419 OUTCOME_SUCCESS, message);
420 logger.info(message);
422 logger.info(String.format("Ansible Request %s finished with Result %s, Message = %s", params.get("Id"),
423 OUTCOME_FAILURE, message));
424 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
425 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
426 doFailure(ctx, code, message);
430 // In case of 200,400,FINISHED return 400
431 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, "400");
432 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
433 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
434 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
435 ctx.setStatus(OUTCOME_SUCCESS);
439 * Public method to get logs from playbook execution for a specific request
441 * It blocks till the Ansible Server responds or the session times out very
442 * similar to reqExecResult logs are returned in the DG context variable
443 * org.onap.appc.adapter.ansible.log
446 public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
448 String reqUri = StringUtils.EMPTY;
450 reqUri = messageProcessor.reqUriLog(params);
451 logger.info("Retrieving results from " + reqUri);
452 } catch (Exception e) {
453 logger.error("Exception caught", e);
454 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
457 String message = StringUtils.EMPTY;
459 // Try to retrieve the test results (modify the url for that)
460 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
461 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
462 message = testResult.getStatusMessage();
463 logger.info("Request output = " + message);
464 ctx.setAttribute(LOG_ATTRIBUTE_NAME, message);
465 ctx.setStatus(OUTCOME_SUCCESS);
466 } catch (Exception e) {
467 logger.error("Exception caught", e);
468 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
469 "Exception encountered retreiving output : " + e.getMessage());
474 * Method that posts the request
476 private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password,
477 SvcLogicContext ctx) {
479 AnsibleResult testResult;
480 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, "");
482 httpClient.setHttpContext(user, password);
483 testResult = httpClient.post(agentUrl, payload);
486 testResult = testServer.Post(agentUrl, payload);
492 * Method to query Ansible server
494 private AnsibleResult queryServer(String agentUrl, String user, String password, SvcLogicContext ctx) {
496 AnsibleResult testResult = new AnsibleResult();
497 int timeout = 600 * 1000;
499 timeout = Integer.parseInt(ctx.getAttribute("AnsibleTimeout")) * 1000;
501 } catch (Exception e) {
502 timeout = defaultTimeout;
504 long endTime = System.currentTimeMillis() + timeout;
506 while (System.currentTimeMillis() < endTime) {
507 String serverIP = ctx.getAttribute("ServerIP");
508 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, serverIP);
509 logger.info("Querying ansible GetResult URL = " + agentUrl);
512 httpClient.setHttpContext(user, password);
513 testResult = httpClient.get(agentUrl);
516 testResult = testServer.Get(agentUrl);
518 if (testResult.getStatusCode() != AnsibleResultCodes.IO_EXCEPTION.getValue()
519 && testResult.getStatusCode() != AnsibleResultCodes.PENDING.getValue()) {
524 Thread.sleep(defaultPollInterval);
525 } catch (InterruptedException ex) {
530 if (testResult.getStatusCode() == AnsibleResultCodes.PENDING.getValue()) {
531 testResult.setStatusCode(AnsibleResultCodes.IO_EXCEPTION.getValue());
532 testResult.setStatusMessage("Request timed out");