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 Properties props;
97 private int defaultTimeout = 600 * 1000;
98 private int defaultSocketTimeout = 60 * 1000;
99 private int defaultPollInterval = 60 * 1000;
102 * The logger to be used
104 private static final EELFLogger logger = EELFManager.getInstance().getLogger(AnsibleAdapterImpl.class);
108 private ConnectionBuilder httpClient;
111 * Ansible API Message Handlers
113 private AnsibleMessageParser messageProcessor;
116 * indicator whether in test mode
118 private boolean testMode = false;
121 * server emulator object to be used if in test mode
123 private AnsibleServerEmulator testServer;
126 * This default constructor is used as a work around because the activator
127 * wasn't getting called
129 public AnsibleAdapterImpl() {
134 * Used for jUnit test and testing interface
136 public AnsibleAdapterImpl(boolean mode) {
138 testServer = new AnsibleServerEmulator();
139 messageProcessor = new AnsibleMessageParser();
143 * Returns the symbolic name of the adapter
145 * @return The adapter name
146 * @see org.onap.appc.adapter.rest.AnsibleAdapter#getAdapterName()
149 public String getAdapterName() {
155 * Method posts info to Context memory in case of an error and throws
156 * a SvcLogicException causing SLI to register this as a failure
158 @SuppressWarnings("static-method")
159 private void doFailure(SvcLogicContext svcLogic, int code, String message) throws SvcLogicException {
161 svcLogic.setStatus(OUTCOME_FAILURE);
162 svcLogic.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
163 svcLogic.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
165 throw new SvcLogicException("Ansible Adapter Error = " + message);
169 * initialize the Ansible adapter based on default and over-ride configuration
172 private void initialize() {
173 String path = propDir + APPC_PROPS;
174 File propFile = new File(path);
175 props = new Properties();
177 InputStream input = new FileInputStream(propFile);
179 } catch (Exception ex) {
180 logger.error("Error while reading appc.properties file" + ex.getMessage());
182 // Create the message processor instance
183 messageProcessor = new AnsibleMessageParser();
184 //continuing for checking timeout
186 String timeoutStr = props.getProperty(TIMEOUT_PROPERTY_NAME);
187 defaultTimeout = Integer.parseInt(timeoutStr) * 1000;
189 } catch (Exception e) {
190 defaultTimeout = 600 * 1000;
191 logger.error("Error while reading time out property" , e);
193 //continuing for checking timeout
195 String timeoutStr = props.getProperty(SOCKET_TIMEOUT_PROPERTY_NAME);
196 defaultSocketTimeout = Integer.parseInt(timeoutStr) * 1000;
198 } catch (Exception e) {
199 defaultSocketTimeout = 60 * 1000;
200 logger.error("Error while reading socket time out property" , e);
202 //continuing for checking timeout
204 String timeoutStr = props.getProperty(POLL_INTERVAL_PROPERTY_NAME);
205 defaultPollInterval = Integer.parseInt(timeoutStr) * 1000;
207 } catch (Exception e) {
208 defaultPollInterval = 60 * 1000;
209 logger.error("Error while reading poll interval property" , e);
211 logger.info("Initialized Ansible Adapter");
214 private ConnectionBuilder getHttpConn(int timeout, String serverIP) {
216 String path = propDir + APPC_PROPS;
217 File propFile = new File(path);
218 props = new Properties();
221 input = new FileInputStream(propFile);
223 } catch (Exception ex) {
224 // TODO Auto-generated catch block
225 logger.error("Error while reading appc.properties file" + ex.getMessage());
227 // Create the http client instance
228 // type of client is extracted from the property file parameter
229 // org.onap.appc.adapter.ansible.clientType
231 // 1. TRUST_ALL (trust all SSL certs). To be used ONLY in dev
232 // 2. TRUST_CERT (trust only those whose certificates have been stored in the
234 // 3. DEFAULT (trust only well known certificates). This is standard behavior to
236 // revert. To be used in PROD
237 ConnectionBuilder httpClient = null;
239 String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
240 logger.info("Ansible http client type set to " + clientType);
242 if ("TRUST_ALL".equals(clientType)) {
244 "Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
245 httpClient = new ConnectionBuilder(1, timeout);
246 } else if ("TRUST_CERT".equals(clientType)) {
247 // set path to keystore file
248 String trustStoreFile = props.getProperty(TRUSTSTORE_PROPERTY_NAME);
249 String key = props.getProperty(TRUSTPASSWD_PROPERTY_NAME);
250 char[] trustStorePasswd = EncryptionTool.getInstance().decrypt(key).toCharArray();
251 logger.info("Creating http client with trustmanager from " + trustStoreFile);
252 httpClient = new ConnectionBuilder(trustStoreFile, trustStorePasswd, timeout, serverIP);
254 logger.info("Creating http client with default behaviour");
255 httpClient = new ConnectionBuilder(0, timeout);
257 } catch (Exception e) {
258 logger.error("Error Getting HTTP Connection Builder due to Unknown Exception", e);
261 logger.info("Got HTTP Connection Builder");
265 // Public Method to post request to execute playbook. Posts the following back
266 // to Svc context memory
267 // org.onap.appc.adapter.ansible.req.code : 100 if successful
268 // org.onap.appc.adapter.ansible.req.messge : any message
269 // org.onap.appc.adapter.ansible.req.Id : a unique uuid to reference the request
271 public void reqExec(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
273 String playbookName = StringUtils.EMPTY;
274 String payload = StringUtils.EMPTY;
275 String agentUrl = StringUtils.EMPTY;
276 String user = StringUtils.EMPTY;
277 String password = StringUtils.EMPTY;
278 String id = StringUtils.EMPTY;
279 String timeout = StringUtils.EMPTY;
280 JSONObject jsonPayload;
283 // create json object to send request
284 jsonPayload = messageProcessor.reqMessage(params);
286 agentUrl = (String) jsonPayload.remove("AgentUrl");
287 user = (String) jsonPayload.remove("User");
288 password = (String)jsonPayload.remove(PASSWORD);
289 if(StringUtils.isNotBlank(password))
290 password = EncryptionTool.getInstance().decrypt(password);
291 id = jsonPayload.getString("Id");
292 timeout = jsonPayload.getString("Timeout");
293 if (StringUtils.isBlank(timeout))
295 payload = jsonPayload.toString();
296 ctx.setAttribute("AnsibleTimeout", timeout);
297 logger.info("Updated Payload = " + payload + " timeout = " + timeout);
298 } catch (APPCException e) {
299 logger.error(APPC_EXCEPTION_CAUGHT, e);
300 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
301 "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = "
303 } catch (JSONException e) {
304 logger.error("JSONException caught", e);
305 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
306 "Error constructing request for execution of playbook due to invalid JSON block. Reason = "
308 } catch (NumberFormatException e) {
309 logger.error("NumberFormatException caught", e);
310 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
311 "Error constructing request for execution of playbook due to invalid parameter values. Reason = "
316 String message = StringUtils.EMPTY;
319 // post the test request
320 logger.info("Posting ansible request = " + payload + " to url = " + agentUrl);
321 AnsibleResult testResult = postExecRequest(agentUrl, payload, user, password,ctx);
322 if (testResult != null) {
323 logger.info("Received response on ansible post request " + testResult.getStatusMessage());
324 // Process if HTTP was successful
325 if (testResult.getStatusCode() == 200) {
326 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
328 doFailure(ctx, testResult.getStatusCode(),
329 "Error posting request. Reason = " + testResult.getStatusMessage());
331 String output = StringUtils.EMPTY;
332 code = testResult.getStatusCode();
333 message = testResult.getStatusMessage();
334 output = testResult.getOutput();
335 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
336 String serverIp = testResult.getServerIp();
337 if (StringUtils.isBlank(serverIp))
338 ctx.setAttribute("ServerIP", serverIp);
340 ctx.setAttribute("ServerIP", "");
341 // Check status of test request returned by Agent
342 if (code == AnsibleResultCodes.PENDING.getValue()) {
343 logger.info(String.format("Submission of Test %s successful.", playbookName));
344 // test request accepted. We are in asynchronous case
346 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
349 doFailure(ctx, code, "Ansible Test result is null");
351 } catch (APPCException e) {
352 logger.error(APPC_EXCEPTION_CAUGHT, e);
353 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
354 "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
357 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
358 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
359 ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
363 * Public method to query status of a specific request It blocks till the
364 * Ansible Server responds or the session times out (non-Javadoc)
366 * @see org.onap.appc.adapter.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
367 * org.onap.ccsdk.sli.core.sli.SvcLogicContext)
370 public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
373 String reqUri = StringUtils.EMPTY;
376 String serverIp = ctx.getAttribute("ServerIP");
377 if (StringUtils.isNotBlank(serverIp))
378 reqUri = messageProcessor.reqUriResultWithIP(params, serverIp);
380 reqUri = messageProcessor.reqUriResult(params);
381 logger.info("Got uri " + reqUri);
382 } catch (APPCException e) {
383 logger.error(APPC_EXCEPTION_CAUGHT, e);
384 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
385 "Error constructing request to retrieve result due to missing parameters. Reason = "
388 } catch (NumberFormatException e) {
389 logger.error("NumberFormatException caught", e);
390 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
391 "Error constructing request to retrieve result due to invalid parameters value. Reason = "
397 String message = StringUtils.EMPTY;
398 String results = StringUtils.EMPTY;
399 String output = StringUtils.EMPTY;
401 // Try to retrieve the test results (modify the URL for that)
402 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
403 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
404 code = testResult.getStatusCode();
405 message = testResult.getStatusMessage();
407 if (code == 200 || code == 400 || "FINISHED".equalsIgnoreCase(message)) {
408 logger.info("Parsing response from ansible Server = " + message);
409 // Valid HTTP. process the Ansible message
410 testResult = messageProcessor.parseGetResponse(message);
411 code = testResult.getStatusCode();
412 message = testResult.getStatusMessage();
413 results = testResult.getResults();
414 output = testResult.getOutput();
415 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
417 logger.info("Request response = " + message);
418 } catch (APPCException e) {
419 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
420 "Exception encountered retrieving result : " + e.getMessage());
424 // We were able to get and process the results. Determine if playbook succeeded
426 if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()) {
427 message = String.format("Ansible Request %s finished with Result = %s, Message = %s", params.get("Id"),
428 OUTCOME_SUCCESS, message);
429 logger.info(message);
431 logger.info(String.format("Ansible Request %s finished with Result %s, Message = %s", params.get("Id"),
432 OUTCOME_FAILURE, message));
433 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
434 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
435 doFailure(ctx, code, message);
439 // In case of 200,400,FINISHED return 400
440 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, "400");
441 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
442 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
443 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
444 ctx.setStatus(OUTCOME_SUCCESS);
448 * Public method to get logs from playbook execution for a specific request
450 * It blocks till the Ansible Server responds or the session times out very
451 * similar to reqExecResult logs are returned in the DG context variable
452 * org.onap.appc.adapter.ansible.log
455 public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
457 String reqUri = StringUtils.EMPTY;
459 reqUri = messageProcessor.reqUriLog(params);
460 logger.info("Retrieving results from " + reqUri);
461 } catch (Exception e) {
462 logger.error("Exception caught", e);
463 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
466 String message = StringUtils.EMPTY;
468 // Try to retrieve the test results (modify the url for that)
469 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
470 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
471 message = testResult.getStatusMessage();
472 logger.info("Request output = " + message);
473 ctx.setAttribute(LOG_ATTRIBUTE_NAME, message);
474 ctx.setStatus(OUTCOME_SUCCESS);
475 } catch (Exception e) {
476 logger.error("Exception caught", e);
477 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
478 "Exception encountered retreiving output : " + e.getMessage());
483 * Method that posts the request
485 private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password,
486 SvcLogicContext ctx) {
488 AnsibleResult testResult = null;
489 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, "");
491 if(httpClient!=null) {
492 httpClient.setHttpContext(user, password);
493 testResult = httpClient.post(agentUrl, payload);
497 testResult = testServer.Post(agentUrl, payload);
503 * Method to query Ansible server
505 private AnsibleResult queryServer(String agentUrl, String user, String password, SvcLogicContext ctx) {
507 AnsibleResult testResult = new AnsibleResult();
508 int timeout = 600 * 1000;
510 timeout = Integer.parseInt(ctx.getAttribute("AnsibleTimeout")) * 1000;
512 } catch (Exception e) {
513 timeout = defaultTimeout;
515 long endTime = System.currentTimeMillis() + timeout;
517 while (System.currentTimeMillis() < endTime) {
518 String serverIP = ctx.getAttribute("ServerIP");
519 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, serverIP);
520 logger.info("Querying ansible GetResult URL = " + agentUrl);
523 if(httpClient!=null) {
524 httpClient.setHttpContext(user, password);
525 testResult = httpClient.get(agentUrl);
529 testResult = testServer.Get(agentUrl);
531 if (testResult.getStatusCode() != AnsibleResultCodes.IO_EXCEPTION.getValue()
532 && testResult.getStatusCode() != AnsibleResultCodes.PENDING.getValue()) {
537 Thread.sleep(defaultPollInterval);
538 } catch (InterruptedException ex) {
539 logger.error("Thread Interrupted Exception", ex);
540 Thread.currentThread().interrupt();
544 if (testResult.getStatusCode() == AnsibleResultCodes.PENDING.getValue()) {
545 testResult.setStatusCode(AnsibleResultCodes.IO_EXCEPTION.getValue());
546 testResult.setStatusMessage("Request timed out");