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 if (testResult != null) {
320 logger.info("Received response on ansible post request " + testResult.getStatusMessage());
321 // Process if HTTP was successful
322 if (testResult.getStatusCode() == 200) {
323 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
325 doFailure(ctx, testResult.getStatusCode(),
326 "Error posting request. Reason = " + testResult.getStatusMessage());
328 String output = StringUtils.EMPTY;
329 code = testResult.getStatusCode();
330 message = testResult.getStatusMessage();
331 output = testResult.getOutput();
332 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
333 String serverIp = testResult.getServerIp();
334 if (StringUtils.isBlank(serverIp))
335 ctx.setAttribute("ServerIP", serverIp);
337 ctx.setAttribute("ServerIP", "");
338 // Check status of test request returned by Agent
339 if (code == AnsibleResultCodes.PENDING.getValue()) {
340 logger.info(String.format("Submission of Test %s successful.", playbookName));
341 // test request accepted. We are in asynchronous case
343 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
346 doFailure(ctx, code, "Ansible Test result is null");
348 } catch (APPCException e) {
349 logger.error(APPC_EXCEPTION_CAUGHT, e);
350 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
351 "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
354 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
355 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
356 ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
360 * Public method to query status of a specific request It blocks till the
361 * Ansible Server responds or the session times out (non-Javadoc)
363 * @see org.onap.appc.adapter.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
364 * org.onap.ccsdk.sli.core.sli.SvcLogicContext)
367 public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
370 String reqUri = StringUtils.EMPTY;
373 String serverIp = ctx.getAttribute("ServerIP");
374 if (StringUtils.isNotBlank(serverIp))
375 reqUri = messageProcessor.reqUriResultWithIP(params, serverIp);
377 reqUri = messageProcessor.reqUriResult(params);
378 logger.info("Got uri " + reqUri);
379 } catch (APPCException e) {
380 logger.error(APPC_EXCEPTION_CAUGHT, e);
381 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
382 "Error constructing request to retrieve result due to missing parameters. Reason = "
385 } catch (NumberFormatException e) {
386 logger.error("NumberFormatException caught", e);
387 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
388 "Error constructing request to retrieve result due to invalid parameters value. Reason = "
394 String message = StringUtils.EMPTY;
395 String results = StringUtils.EMPTY;
396 String output = StringUtils.EMPTY;
398 // Try to retrieve the test results (modify the URL for that)
399 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
400 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
401 code = testResult.getStatusCode();
402 message = testResult.getStatusMessage();
404 if (code == 200 || code == 400 || "FINISHED".equalsIgnoreCase(message)) {
405 logger.info("Parsing response from ansible Server = " + message);
406 // Valid HTTP. process the Ansible message
407 testResult = messageProcessor.parseGetResponse(message);
408 code = testResult.getStatusCode();
409 message = testResult.getStatusMessage();
410 results = testResult.getResults();
411 output = testResult.getOutput();
412 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
414 logger.info("Request response = " + message);
415 } catch (APPCException e) {
416 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
417 "Exception encountered retrieving result : " + e.getMessage());
421 // We were able to get and process the results. Determine if playbook succeeded
423 if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()) {
424 message = String.format("Ansible Request %s finished with Result = %s, Message = %s", params.get("Id"),
425 OUTCOME_SUCCESS, message);
426 logger.info(message);
428 logger.info(String.format("Ansible Request %s finished with Result %s, Message = %s", params.get("Id"),
429 OUTCOME_FAILURE, message));
430 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
431 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
432 doFailure(ctx, code, message);
436 // In case of 200,400,FINISHED return 400
437 ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, "400");
438 ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
439 ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
440 ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
441 ctx.setStatus(OUTCOME_SUCCESS);
445 * Public method to get logs from playbook execution for a specific request
447 * It blocks till the Ansible Server responds or the session times out very
448 * similar to reqExecResult logs are returned in the DG context variable
449 * org.onap.appc.adapter.ansible.log
452 public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
454 String reqUri = StringUtils.EMPTY;
456 reqUri = messageProcessor.reqUriLog(params);
457 logger.info("Retrieving results from " + reqUri);
458 } catch (Exception e) {
459 logger.error("Exception caught", e);
460 doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
463 String message = StringUtils.EMPTY;
465 // Try to retrieve the test results (modify the url for that)
466 AnsibleResult testResult = queryServer(reqUri, params.get("User"),
467 EncryptionTool.getInstance().decrypt(params.get(PASSWORD)), ctx);
468 message = testResult.getStatusMessage();
469 logger.info("Request output = " + message);
470 ctx.setAttribute(LOG_ATTRIBUTE_NAME, message);
471 ctx.setStatus(OUTCOME_SUCCESS);
472 } catch (Exception e) {
473 logger.error("Exception caught", e);
474 doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
475 "Exception encountered retreiving output : " + e.getMessage());
480 * Method that posts the request
482 private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password,
483 SvcLogicContext ctx) {
485 AnsibleResult testResult = null;
486 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, "");
488 if(httpClient!=null) {
489 httpClient.setHttpContext(user, password);
490 testResult = httpClient.post(agentUrl, payload);
494 testResult = testServer.Post(agentUrl, payload);
500 * Method to query Ansible server
502 private AnsibleResult queryServer(String agentUrl, String user, String password, SvcLogicContext ctx) {
504 AnsibleResult testResult = new AnsibleResult();
505 int timeout = 600 * 1000;
507 timeout = Integer.parseInt(ctx.getAttribute("AnsibleTimeout")) * 1000;
509 } catch (Exception e) {
510 timeout = defaultTimeout;
512 long endTime = System.currentTimeMillis() + timeout;
514 while (System.currentTimeMillis() < endTime) {
515 String serverIP = ctx.getAttribute("ServerIP");
516 ConnectionBuilder httpClient = getHttpConn(defaultSocketTimeout, serverIP);
517 logger.info("Querying ansible GetResult URL = " + agentUrl);
520 if(httpClient!=null) {
521 httpClient.setHttpContext(user, password);
522 testResult = httpClient.get(agentUrl);
526 testResult = testServer.Get(agentUrl);
528 if (testResult.getStatusCode() != AnsibleResultCodes.IO_EXCEPTION.getValue()
529 && testResult.getStatusCode() != AnsibleResultCodes.PENDING.getValue()) {
534 Thread.sleep(defaultPollInterval);
535 } catch (InterruptedException ex) {
540 if (testResult.getStatusCode() == AnsibleResultCodes.PENDING.getValue()) {
541 testResult.setStatusCode(AnsibleResultCodes.IO_EXCEPTION.getValue());
542 testResult.setStatusMessage("Request timed out");