2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2017 Amdocs
8 * =============================================================================
9 * Modifications Copyright (C) 2019 IBM
10 * =============================================================================
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
15 * http://www.apache.org/licenses/LICENSE-2.0
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
23 * ============LICENSE_END=========================================================
26 package org.onap.appc.adapter.ansible.impl;
29 import java.util.Properties;
31 import java.io.FileInputStream;
32 import java.io.InputStream;
33 import org.apache.commons.lang.StringUtils;
34 import org.json.JSONException;
35 import org.json.JSONObject;
36 import org.onap.appc.adapter.ansible.AnsibleAdapter;
37 import org.onap.appc.adapter.ansible.model.AnsibleMessageParser;
38 import org.onap.appc.adapter.ansible.model.AnsibleResult;
39 import org.onap.appc.adapter.ansible.model.AnsibleResultCodes;
40 import org.onap.appc.adapter.ansible.model.AnsibleServerEmulator;
41 import org.onap.appc.exceptions.APPCException;
42 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
43 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
45 import com.att.eelf.configuration.EELFLogger;
46 import com.att.eelf.configuration.EELFManager;
47 import org.onap.appc.encryption.EncryptionTool;
50 * This class implements the {@link AnsibleAdapter} interface. This interface
51 * defines the behaviors that our service provides.
53 public class AnsibleAdapterImpl implements AnsibleAdapter {
56 * The constant used to define the service name in the mapped diagnostic context
58 @SuppressWarnings("nls")
59 public static final String MDC_SERVICE = "service";
62 * The constant for the status code for a failed outcome
64 @SuppressWarnings("nls")
65 public static final String OUTCOME_FAILURE = "failure";
68 * The constant for the status code for a successful outcome
70 @SuppressWarnings("nls")
71 public static final String OUTCOME_SUCCESS = "success";
76 private static final String ADAPTER_NAME = "Ansible Adapter";
77 private static final String APPC_EXCEPTION_CAUGHT = "APPCException caught";
79 private static final String RESULT_CODE_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.result.code";
80 private static final String MESSAGE_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.message";
81 private static final String RESULTS_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.results";
82 private static final String OUTPUT_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.output";
83 private static final String ID_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.Id";
84 private static final String LOG_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.log";
86 private static final String CLIENT_TYPE_PROPERTY_NAME = "org.onap.appc.adapter.ansible.clientType";
87 private static final String TRUSTSTORE_PROPERTY_NAME = "org.onap.appc.adapter.ansible.trustStore";
88 private static final String TRUSTPASSWD_PROPERTY_NAME = "org.onap.appc.adapter.ansible.trustStore.trustPasswd";
89 private static final String TIMEOUT_PROPERTY_NAME = "org.onap.appc.adapter.ansible.timeout";
90 private static final String POLL_INTERVAL_PROPERTY_NAME = "org.onap.appc.adapter.ansible.pollInterval";
91 private static final String SOCKET_TIMEOUT_PROPERTY_NAME = "org.onap.appc.adapter.ansible.socketTimeout";
92 private static final String PASSWORD = "Password";
93 private static final String APPC_PROPS = "/appc.properties";
94 private static final String SDNC_CONFIG_DIR = "SDNC_CONFIG_DIR";
95 private static final String propDir = System.getenv(SDNC_CONFIG_DIR);
96 private static final String SERVERIP = "ServerIP";
97 private Properties props;
98 private int defaultTimeout = 600 * 1000;
99 private int defaultSocketTimeout = 60 * 1000;
100 private int defaultPollInterval = 60 * 1000;
103 * The logger to be used
105 private static final EELFLogger logger = EELFManager.getInstance().getLogger(AnsibleAdapterImpl.class);
109 private ConnectionBuilder httpClient;
112 * Ansible API Message Handlers
114 private AnsibleMessageParser messageProcessor;
117 * indicator whether in test mode
119 private boolean testMode = false;
122 * server emulator object to be used if in test mode
124 private AnsibleServerEmulator testServer;
127 * This default constructor is used as a work around because the activator
128 * wasn't getting called
130 public AnsibleAdapterImpl() {
135 * Used for jUnit test and testing interface
137 public AnsibleAdapterImpl(boolean mode) {
139 testServer = new AnsibleServerEmulator();
140 messageProcessor = new AnsibleMessageParser();
144 * Returns the symbolic name of the adapter
146 * @return The adapter name
147 * @see org.onap.appc.adapter.rest.AnsibleAdapter#getAdapterName()
150 public String getAdapterName() {
156 * Method posts info to Context memory in case of an error and throws
157 * a SvcLogicException causing SLI to register this as a failure
159 @SuppressWarnings("static-method")
160 private void doFailure(SvcLogicContext svcLogic, int code, String message) throws SvcLogicException {
162 svcLogic.setStatus(OUTCOME_FAILURE);
163 svcLogic.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
164 svcLogic.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
166 throw new SvcLogicException("Ansible Adapter Error = " + message);
170 * initialize the Ansible adapter based on default and over-ride configuration
173 private void initialize() {
174 String path = propDir + APPC_PROPS;
175 File propFile = new File(path);
176 props = new Properties();
178 InputStream input = new FileInputStream(propFile);
180 } catch (Exception ex) {
181 logger.error("Error while reading appc.properties file" + ex.getMessage());
183 // Create the message processor instance
184 messageProcessor = new AnsibleMessageParser();
185 //continuing for checking timeout
187 String timeoutStr = props.getProperty(TIMEOUT_PROPERTY_NAME);
188 defaultTimeout = Integer.parseInt(timeoutStr) * 1000;
190 } catch (Exception e) {
191 defaultTimeout = 600 * 1000;
192 logger.error("Error while reading time out property" , e);
194 //continuing for checking timeout
196 String timeoutStr = props.getProperty(SOCKET_TIMEOUT_PROPERTY_NAME);
197 defaultSocketTimeout = Integer.parseInt(timeoutStr) * 1000;
199 } catch (Exception e) {
200 defaultSocketTimeout = 60 * 1000;
201 logger.error("Error while reading socket time out property" , e);
203 //continuing for checking timeout
205 String timeoutStr = props.getProperty(POLL_INTERVAL_PROPERTY_NAME);
206 defaultPollInterval = Integer.parseInt(timeoutStr) * 1000;
208 } catch (Exception e) {
209 defaultPollInterval = 60 * 1000;
210 logger.error("Error while reading poll interval property" , e);
212 logger.info("Initialized Ansible Adapter");
215 private ConnectionBuilder getHttpConn(int timeout, String serverIP) {
217 String path = propDir + APPC_PROPS;
218 File propFile = new File(path);
219 props = new Properties();
222 input = new FileInputStream(propFile);
224 } catch (Exception ex) {
225 // TODO Auto-generated catch block
226 logger.error("Error while reading appc.properties file" + ex.getMessage());
228 // Create the http client instance
229 // type of client is extracted from the property file parameter
230 // org.onap.appc.adapter.ansible.clientType
232 // 1. TRUST_ALL (trust all SSL certs). To be used ONLY in dev
233 // 2. TRUST_CERT (trust only those whose certificates have been stored in the
235 // 3. DEFAULT (trust only well known certificates). This is standard behavior to
237 // revert. To be used in PROD
238 ConnectionBuilder httpClientLocal = null;
240 String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
241 logger.info("Ansible http client type set to " + clientType);
243 if ("TRUST_ALL".equals(clientType)) {
245 "Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
246 httpClientLocal = new ConnectionBuilder(1, timeout);
247 } else if ("TRUST_CERT".equals(clientType)) {
248 // set path to keystore file
249 String trustStoreFile = props.getProperty(TRUSTSTORE_PROPERTY_NAME);
250 String key = props.getProperty(TRUSTPASSWD_PROPERTY_NAME);
251 char[] trustStorePasswd = EncryptionTool.getInstance().decrypt(key).toCharArray();
252 logger.info("Creating http client with trustmanager from " + trustStoreFile);
253 httpClientLocal = new ConnectionBuilder(trustStoreFile, trustStorePasswd, timeout, serverIP);
255 logger.info("Creating http client with default behaviour");
256 httpClientLocal = new ConnectionBuilder(0, timeout);
258 } catch (Exception e) {
259 logger.error("Error Getting HTTP Connection Builder due to Unknown Exception", e);
262 logger.info("Got HTTP Connection Builder");
263 return httpClientLocal;
266 // Public Method to post request to execute playbook. Posts the following back
267 // to Svc context memory
268 // org.onap.appc.adapter.ansible.req.code : 100 if successful
269 // org.onap.appc.adapter.ansible.req.messge : any message
270 // org.onap.appc.adapter.ansible.req.Id : a unique uuid to reference the request
272 public void reqExec(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
274 String playbookName = StringUtils.EMPTY;
275 String payload = StringUtils.EMPTY;
276 String agentUrl = StringUtils.EMPTY;
277 String user = StringUtils.EMPTY;
278 String password = StringUtils.EMPTY;
279 String id = StringUtils.EMPTY;
280 String timeout = StringUtils.EMPTY;
281 JSONObject jsonPayload;
284 // create json object to send request
285 jsonPayload = messageProcessor.reqMessage(params);
287 agentUrl = (String) jsonPayload.remove("AgentUrl");
288 user = (String) jsonPayload.remove("User");
289 password = (String)jsonPayload.remove(PASSWORD);
290 if(StringUtils.isNotBlank(password))
291 password = EncryptionTool.getInstance().decrypt(password);
292 id = jsonPayload.getString("Id");
293 timeout = jsonPayload.getString("Timeout");
294 if (StringUtils.isBlank(timeout))
296 payload = jsonPayload.toString();
297 ctx.setAttribute("AnsibleTimeout", timeout);
298 logger.info("Updated Payload = " + payload + " timeout = " + timeout);
299 } catch (APPCException e) {
300 logger.error(APPC_EXCEPTION_CAUGHT, e);
301 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
302 "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = "
304 } catch (JSONException e) {
305 logger.error("JSONException caught", e);
306 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
307 "Error constructing request for execution of playbook due to invalid JSON block. Reason = "
309 } catch (NumberFormatException e) {
310 logger.error("NumberFormatException caught", e);
311 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
312 "Error constructing request for execution of playbook due to invalid parameter values. Reason = "
317 String message = StringUtils.EMPTY;
320 // post the test request
321 logger.info("Posting ansible request = " + payload + " to url = " + agentUrl);
322 AnsibleResult testResult = postExecRequest(agentUrl, payload, user, password,ctx);
323 if (testResult != null) {
324 logger.info("Received response on ansible post request " + testResult.getStatusMessage());
325 // Process if HTTP was successful
326 if (testResult.getStatusCode() == 200) {
327 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
329 doFailure(ctx, testResult.getStatusCode(),
330 "Error posting request. Reason = " + testResult.getStatusMessage());
332 String output = StringUtils.EMPTY;
333 code = testResult.getStatusCode();
334 message = testResult.getStatusMessage();
335 output = testResult.getOutput();
336 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
337 String serverIp = testResult.getServerIp();
338 if (StringUtils.isBlank(serverIp))
339 ctx.setAttribute(SERVERIP, serverIp);
341 ctx.setAttribute(SERVERIP, "");
342 // Check status of test request returned by Agent
343 if (code == AnsibleResultCodes.PENDING.getValue()) {
344 logger.info(String.format("Submission of Test %s successful.", playbookName));
345 // test request accepted. We are in asynchronous case
347 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
350 doFailure(ctx, code, "Ansible Test result is null");
352 } catch (APPCException e) {
353 logger.error(APPC_EXCEPTION_CAUGHT, e);
354 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
355 "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
358 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
359 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
360 ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
364 * Public method to query status of a specific request It blocks till the
365 * Ansible Server responds or the session times out (non-Javadoc)
367 * @see org.onap.appc.adapter.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
368 * org.onap.ccsdk.sli.core.sli.SvcLogicContext)
371 public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
374 String reqUri = StringUtils.EMPTY;
377 String serverIp = ctx.getAttribute(SERVERIP);
378 if (StringUtils.isNotBlank(serverIp))
379 reqUri = messageProcessor.reqUriResultWithIP(params, serverIp);
381 reqUri = messageProcessor.reqUriResult(params);
382 logger.info("Got uri " + reqUri);
383 } catch (APPCException e) {
384 logger.error(APPC_EXCEPTION_CAUGHT, e);
385 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
386 "Error constructing request to retrieve result due to missing parameters. Reason = "
389 } catch (NumberFormatException e) {
390 logger.error("NumberFormatException caught", e);
391 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
392 "Error constructing request to retrieve result due to invalid parameters value. Reason = "
398 String message = StringUtils.EMPTY;
399 String results = StringUtils.EMPTY;
400 String output = StringUtils.EMPTY;
402 // Try to retrieve the test results (modify the URL for that)
403 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
404 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
405 code = testResult.getStatusCode();
406 message = testResult.getStatusMessage();
408 if (code == 200 || code == 400 || "FINISHED".equalsIgnoreCase(message)) {
409 logger.info("Parsing response from ansible Server = " + message);
410 // Valid HTTP. process the Ansible message
411 testResult = messageProcessor.parseGetResponse(message);
412 code = testResult.getStatusCode();
413 message = testResult.getStatusMessage();
414 results = testResult.getResults();
415 output = testResult.getOutput();
416 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
418 logger.info("Request response = " + message);
419 } catch (APPCException e) {
420 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
421 "Exception encountered retrieving result : " + e.getMessage());
425 // We were able to get and process the results. Determine if playbook succeeded
427 if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()) {
428 message = String.format("Ansible Request %s finished with Result = %s, Message = %s", params.get("Id"),
429 OUTCOME_SUCCESS, message);
430 logger.info(message);
432 logger.info(String.format("Ansible Request %s finished with Result %s, Message = %s", params.get("Id"),
433 OUTCOME_FAILURE, message));
434 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
435 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
436 doFailure(ctx, code, message);
440 // In case of 200,400,FINISHED return 400
441 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, "400");
442 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
443 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
444 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
445 ctx.setStatus(OUTCOME_SUCCESS);
449 * Public method to get logs from playbook execution for a specific request
451 * It blocks till the Ansible Server responds or the session times out very
452 * similar to reqExecResult logs are returned in the DG context variable
453 * org.onap.appc.adapter.ansible.log
456 public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
458 String reqUri = StringUtils.EMPTY;
460 reqUri = messageProcessor.reqUriLog(params);
461 logger.info("Retrieving results from " + reqUri);
462 } catch (Exception e) {
463 logger.error("Exception caught", e);
464 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
467 String message = StringUtils.EMPTY;
469 // Try to retrieve the test results (modify the url for that)
470 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
471 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
472 message = testResult.getStatusMessage();
473 logger.info("Request output = " + message);
474 ctx.setAttribute(LOG_ATTRIBUTE_NAME, message);
475 ctx.setStatus(OUTCOME_SUCCESS);
476 } catch (Exception e) {
477 logger.error("Exception caught", e);
478 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
479 "Exception encountered retreiving output : " + e.getMessage());
484 * Method that posts the request
486 private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password,
487 SvcLogicContext ctx) {
489 AnsibleResult testResult = null;
490 ConnectionBuilder httpClientLocal = getHttpConn(defaultSocketTimeout, "");
492 if(httpClientLocal!=null) {
493 httpClientLocal.setHttpContext(user, password);
494 testResult = httpClientLocal.post(agentUrl, payload);
495 httpClientLocal.close();
498 testResult = testServer.post(agentUrl, payload);
504 * Method to query Ansible server
506 private AnsibleResult queryServer(String agentUrl, String user, String password, SvcLogicContext ctx) {
508 AnsibleResult testResult = new AnsibleResult();
509 int timeout = 600 * 1000;
511 timeout = Integer.parseInt(ctx.getAttribute("AnsibleTimeout")) * 1000;
513 } catch (Exception e) {
514 timeout = defaultTimeout;
516 long endTime = System.currentTimeMillis() + timeout;
518 while (System.currentTimeMillis() < endTime) {
519 String serverIP = ctx.getAttribute(SERVERIP);
520 ConnectionBuilder httpClientLocal = getHttpConn(defaultSocketTimeout, serverIP);
521 logger.info("Querying ansible GetResult URL = " + agentUrl);
524 if(httpClientLocal!=null) {
525 httpClientLocal.setHttpContext(user, password);
526 testResult = httpClientLocal.get(agentUrl);
527 httpClientLocal.close();
530 testResult = testServer.get(agentUrl);
532 if (testResult.getStatusCode() != AnsibleResultCodes.IO_EXCEPTION.getValue()
533 && testResult.getStatusCode() != AnsibleResultCodes.PENDING.getValue()) {
538 Thread.sleep(defaultPollInterval);
539 } catch (InterruptedException ex) {
540 logger.error("Thread Interrupted Exception", ex);
541 Thread.currentThread().interrupt();
545 if (testResult.getStatusCode() == AnsibleResultCodes.PENDING.getValue()) {
546 testResult.setStatusCode(AnsibleResultCodes.IO_EXCEPTION.getValue());
547 testResult.setStatusMessage("Request timed out");