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;
192 //continuing for checking timeout
194 String timeoutStr = props.getProperty(SOCKET_TIMEOUT_PROPERTY_NAME);
195 defaultSocketTimeout = Integer.parseInt(timeoutStr) * 1000;
197 } catch (Exception e) {
198 defaultSocketTimeout = 60 * 1000;
200 //continuing for checking timeout
202 String timeoutStr = props.getProperty(POLL_INTERVAL_PROPERTY_NAME);
203 defaultPollInterval = Integer.parseInt(timeoutStr) * 1000;
205 } catch (Exception e) {
206 defaultPollInterval = 60 * 1000;
208 logger.info("Initialized Ansible Adapter");
211 private ConnectionBuilder getHttpConn(int timeout, String serverIP) {
213 String path = propDir + APPC_PROPS;
214 File propFile = new File(path);
215 props = new Properties();
218 input = new FileInputStream(propFile);
220 } catch (Exception ex) {
221 // TODO Auto-generated catch block
222 logger.error("Error while reading appc.properties file" + ex.getMessage());
224 // Create the http client instance
225 // type of client is extracted from the property file parameter
226 // org.onap.appc.adapter.ansible.clientType
228 // 1. TRUST_ALL (trust all SSL certs). To be used ONLY in dev
229 // 2. TRUST_CERT (trust only those whose certificates have been stored in the
231 // 3. DEFAULT (trust only well known certificates). This is standard behavior to
233 // revert. To be used in PROD
234 ConnectionBuilder httpClient = null;
236 String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
237 logger.info("Ansible http client type set to " + clientType);
239 if ("TRUST_ALL".equals(clientType)) {
241 "Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
242 httpClient = new ConnectionBuilder(1, timeout);
243 } else if ("TRUST_CERT".equals(clientType)) {
244 // set path to keystore file
245 String trustStoreFile = props.getProperty(TRUSTSTORE_PROPERTY_NAME);
246 String key = props.getProperty(TRUSTPASSWD_PROPERTY_NAME);
247 char[] trustStorePasswd = EncryptionTool.getInstance().decrypt(key).toCharArray();
248 logger.info("Creating http client with trustmanager from " + trustStoreFile);
249 httpClient = new ConnectionBuilder(trustStoreFile, trustStorePasswd, timeout, serverIP);
251 logger.info("Creating http client with default behaviour");
252 httpClient = new ConnectionBuilder(0, timeout);
254 } catch (Exception e) {
255 logger.error("Error Getting HTTP Connection Builder due to Unknown Exception", e);
258 logger.info("Got HTTP Connection Builder");
262 // Public Method to post request to execute playbook. Posts the following back
263 // to Svc context memory
264 // org.onap.appc.adapter.ansible.req.code : 100 if successful
265 // org.onap.appc.adapter.ansible.req.messge : any message
266 // org.onap.appc.adapter.ansible.req.Id : a unique uuid to reference the request
268 public void reqExec(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
270 String playbookName = StringUtils.EMPTY;
271 String payload = StringUtils.EMPTY;
272 String agentUrl = StringUtils.EMPTY;
273 String user = StringUtils.EMPTY;
274 String password = StringUtils.EMPTY;
275 String id = StringUtils.EMPTY;
276 String timeout = StringUtils.EMPTY;
277 JSONObject jsonPayload;
280 // create json object to send request
281 jsonPayload = messageProcessor.reqMessage(params);
283 agentUrl = (String) jsonPayload.remove("AgentUrl");
284 user = (String) jsonPayload.remove("User");
285 password = (String)jsonPayload.remove(PASSWORD);
286 if(StringUtils.isNotBlank(password))
287 password = EncryptionTool.getInstance().decrypt(password);
288 id = jsonPayload.getString("Id");
289 timeout = jsonPayload.getString("Timeout");
290 if (StringUtils.isBlank(timeout))
292 payload = jsonPayload.toString();
293 ctx.setAttribute("AnsibleTimeout", timeout);
294 logger.info("Updated Payload = " + payload + " timeout = " + timeout);
295 } catch (APPCException e) {
296 logger.error(APPC_EXCEPTION_CAUGHT, e);
297 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
298 "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = "
300 } catch (JSONException e) {
301 logger.error("JSONException caught", e);
302 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
303 "Error constructing request for execution of playbook due to invalid JSON block. Reason = "
305 } catch (NumberFormatException e) {
306 logger.error("NumberFormatException caught", e);
307 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
308 "Error constructing request for execution of playbook due to invalid parameter values. Reason = "
313 String message = StringUtils.EMPTY;
316 // post the test request
317 logger.info("Posting ansible request = " + payload + " to url = " + agentUrl);
318 AnsibleResult testResult = postExecRequest(agentUrl, payload, user, password,ctx);
319 logger.info("Received response on ansible post request " + testResult.getStatusMessage());
320 // Process if HTTP was successful
321 if (testResult.getStatusCode() == 200) {
322 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
324 doFailure(ctx, testResult.getStatusCode(),
325 "Error posting request. Reason = " + testResult.getStatusMessage());
327 String output = StringUtils.EMPTY;
328 code = testResult.getStatusCode();
329 message = testResult.getStatusMessage();
330 output = testResult.getOutput();
331 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
332 String serverIp = testResult.getServerIp();
333 if (StringUtils.isBlank(serverIp))
334 ctx.setAttribute("ServerIP", serverIp);
336 ctx.setAttribute("ServerIP", "");
337 // Check status of test request returned by Agent
338 if (code == AnsibleResultCodes.PENDING.getValue()) {
339 logger.info(String.format("Submission of Test %s successful.", playbookName));
340 // test request accepted. We are in asynchronous case
342 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
344 } catch (APPCException e) {
345 logger.error(APPC_EXCEPTION_CAUGHT, e);
346 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
347 "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
350 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
351 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
352 ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
356 * Public method to query status of a specific request It blocks till the
357 * Ansible Server responds or the session times out (non-Javadoc)
359 * @see org.onap.appc.adapter.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
360 * org.onap.ccsdk.sli.core.sli.SvcLogicContext)
363 public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
366 String reqUri = StringUtils.EMPTY;
369 String serverIp = ctx.getAttribute("ServerIP");
370 if (StringUtils.isNotBlank(serverIp))
371 reqUri = messageProcessor.reqUriResultWithIP(params, serverIp);
373 reqUri = messageProcessor.reqUriResult(params);
374 logger.info("Got uri " + reqUri);
375 } catch (APPCException e) {
376 logger.error(APPC_EXCEPTION_CAUGHT, e);
377 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
378 "Error constructing request to retrieve result due to missing parameters. Reason = "
381 } catch (NumberFormatException e) {
382 logger.error("NumberFormatException caught", e);
383 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
384 "Error constructing request to retrieve result due to invalid parameters value. Reason = "
390 String message = StringUtils.EMPTY;
391 String results = StringUtils.EMPTY;
392 String output = StringUtils.EMPTY;
394 // Try to retrieve the test results (modify the URL for that)
395 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
396 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
397 code = testResult.getStatusCode();
398 message = testResult.getStatusMessage();
400 if (code == 200 || code == 400 || "FINISHED".equalsIgnoreCase(message)) {
401 logger.info("Parsing response from ansible Server = " + message);
402 // Valid HTTP. process the Ansible message
403 testResult = messageProcessor.parseGetResponse(message);
404 code = testResult.getStatusCode();
405 message = testResult.getStatusMessage();
406 results = testResult.getResults();
407 output = testResult.getOutput();
408 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
410 logger.info("Request response = " + message);
411 } catch (APPCException e) {
412 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
413 "Exception encountered retrieving result : " + e.getMessage());
417 // We were able to get and process the results. Determine if playbook succeeded
419 if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()) {
420 message = String.format("Ansible Request %s finished with Result = %s, Message = %s", params.get("Id"),
421 OUTCOME_SUCCESS, message);
422 logger.info(message);
424 logger.info(String.format("Ansible Request %s finished with Result %s, Message = %s", params.get("Id"),
425 OUTCOME_FAILURE, message));
426 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
427 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
428 doFailure(ctx, code, message);
432 // In case of 200,400,FINISHED return 400
433 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, "400");
434 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
435 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
436 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
437 ctx.setStatus(OUTCOME_SUCCESS);
441 * Public method to get logs from playbook execution for a specific request
443 * It blocks till the Ansible Server responds or the session times out very
444 * similar to reqExecResult logs are returned in the DG context variable
445 * org.onap.appc.adapter.ansible.log
448 public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
450 String reqUri = StringUtils.EMPTY;
452 reqUri = messageProcessor.reqUriLog(params);
453 logger.info("Retrieving results from " + reqUri);
454 } catch (Exception e) {
455 logger.error("Exception caught", e);
456 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
459 String message = StringUtils.EMPTY;
461 // Try to retrieve the test results (modify the url for that)
462 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
463 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
464 message = testResult.getStatusMessage();
465 logger.info("Request output = " + message);
466 ctx.setAttribute(LOG_ATTRIBUTE_NAME, message);
467 ctx.setStatus(OUTCOME_SUCCESS);
468 } catch (Exception e) {
469 logger.error("Exception caught", e);
470 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
471 "Exception encountered retreiving output : " + e.getMessage());
476 * Method that posts the request
478 private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password,
479 SvcLogicContext ctx) {
481 AnsibleResult testResult = null;
482 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, "");
484 if(httpClient!=null) {
485 httpClient.setHttpContext(user, password);
486 testResult = httpClient.post(agentUrl, payload);
490 testResult = testServer.Post(agentUrl, payload);
496 * Method to query Ansible server
498 private AnsibleResult queryServer(String agentUrl, String user, String password, SvcLogicContext ctx) {
500 AnsibleResult testResult = new AnsibleResult();
501 int timeout = 600 * 1000;
503 timeout = Integer.parseInt(ctx.getAttribute("AnsibleTimeout")) * 1000;
505 } catch (Exception e) {
506 timeout = defaultTimeout;
508 long endTime = System.currentTimeMillis() + timeout;
510 while (System.currentTimeMillis() < endTime) {
511 String serverIP = ctx.getAttribute("ServerIP");
512 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, serverIP);
513 logger.info("Querying ansible GetResult URL = " + agentUrl);
516 if(httpClient!=null) {
517 httpClient.setHttpContext(user, password);
518 testResult = httpClient.get(agentUrl);
522 testResult = testServer.Get(agentUrl);
524 if (testResult.getStatusCode() != AnsibleResultCodes.IO_EXCEPTION.getValue()
525 && testResult.getStatusCode() != AnsibleResultCodes.PENDING.getValue()) {
530 Thread.sleep(defaultPollInterval);
531 } catch (InterruptedException ex) {
536 if (testResult.getStatusCode() == AnsibleResultCodes.PENDING.getValue()) {
537 testResult.setStatusCode(AnsibleResultCodes.IO_EXCEPTION.getValue());
538 testResult.setStatusMessage("Request timed out");