ae0b8594fc889078094150716462d9750d99f384
[appc.git] / appc-adapters / appc-ansible-adapter / appc-ansible-adapter-bundle / src / main / java / org / onap / appc / adapter / ansible / impl / AnsibleAdapterImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017-2018 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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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.
20  *
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.appc.adapter.ansible.impl;
25
26 import java.util.Map;
27 import java.util.Properties;
28
29 import org.apache.commons.lang.StringUtils;
30 import org.json.JSONException;
31 import org.json.JSONObject;
32 import org.onap.appc.adapter.ansible.AnsibleAdapter;
33 import org.onap.appc.adapter.ansible.model.AnsibleMessageParser;
34 import org.onap.appc.adapter.ansible.model.AnsibleResult;
35 import org.onap.appc.adapter.ansible.model.AnsibleResultCodes;
36 import org.onap.appc.adapter.ansible.model.AnsibleServerEmulator;
37 import org.onap.appc.configuration.Configuration;
38 import org.onap.appc.configuration.ConfigurationFactory;
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;
42
43 import com.att.eelf.configuration.EELFLogger;
44 import com.att.eelf.configuration.EELFManager;
45
46 /**
47  * This class implements the {@link AnsibleAdapter} interface. This interface defines the behaviors
48  * that our service provides.
49  */
50 public class AnsibleAdapterImpl implements AnsibleAdapter {
51
52
53     /**
54      * The constant used to define the service name in the mapped diagnostic context
55      */
56     @SuppressWarnings("nls")
57     public static final String MDC_SERVICE = "service";
58
59     /**
60      * The constant for the status code for a failed outcome
61      */
62     @SuppressWarnings("nls")
63     public static final String OUTCOME_FAILURE = "failure";
64
65     /**
66      * The constant for the status code for a successful outcome
67      */
68     @SuppressWarnings("nls")
69     public static final String OUTCOME_SUCCESS = "success";
70
71     /**
72      * Adapter Name
73      */
74     private static final String ADAPTER_NAME = "Ansible Adapter";
75     private static final String APPC_EXCEPTION_CAUGHT = "APPCException caught";
76
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";
83
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
88     private static final String PASSWORD = "Password";
89
90     /**
91      * The logger to be used
92      */
93     private static final EELFLogger logger = EELFManager.getInstance().getLogger(AnsibleAdapterImpl.class);
94
95     /**
96      * A reference to the adapter configuration object.
97      */
98     private Configuration configuration;
99
100     /**
101      * Connection object
102      **/
103     private ConnectionBuilder httpClient;
104
105     /**
106      * Ansible API Message Handlers
107      **/
108     private AnsibleMessageParser messageProcessor;
109
110     /**
111      * indicator whether in test mode
112      **/
113     private boolean testMode = false;
114
115     /**
116      * server emulator object to be used if in test mode
117      **/
118     private AnsibleServerEmulator testServer;
119
120     /**
121      * This default constructor is used as a work around because the activator wasn't getting called
122      */
123     public AnsibleAdapterImpl() {
124         initialize();
125     }
126
127     /**
128      * Used for jUnit test and testing interface
129      */
130     public AnsibleAdapterImpl(boolean mode) {
131         testMode = mode;
132         testServer = new AnsibleServerEmulator();
133         messageProcessor = new AnsibleMessageParser();
134     }
135
136     /**
137      * Returns the symbolic name of the adapter
138      *
139      * @return The adapter name
140      * @see org.onap.appc.adapter.rest.AnsibleAdapter#getAdapterName()
141      */
142     @Override
143     public String getAdapterName() {
144         return ADAPTER_NAME;
145     }
146
147     /**
148      * @param rc Method posts info to Context memory in case of an error and throws a
149      *        SvcLogicException causing SLI to register this as a failure
150      */
151     @SuppressWarnings("static-method")
152     private void doFailure(SvcLogicContext svcLogic, int code, String message) throws SvcLogicException {
153
154         svcLogic.setStatus(OUTCOME_FAILURE);
155         svcLogic.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
156         svcLogic.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
157
158         throw new SvcLogicException("Ansible Adapter Error = " + message);
159     }
160
161     /**
162      * initialize the Ansible adapter based on default and over-ride configuration data
163      */
164     private void initialize() {
165
166         configuration = ConfigurationFactory.getConfiguration();
167         Properties props = configuration.getProperties();
168
169         // Create the message processor instance
170         messageProcessor = new AnsibleMessageParser();
171
172         // Create the http client instance
173         // type of client is extracted from the property file parameter
174         // org.onap.appc.adapter.ansible.clientType
175         // It can be :
176         // 1. TRUST_ALL (trust all SSL certs). To be used ONLY in dev
177         // 2. TRUST_CERT (trust only those whose certificates have been stored in the trustStore file)
178         // 3. DEFAULT (trust only well known certificates). This is standard behavior to which it will
179         // revert. To be used in PROD
180
181         try {
182             String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
183             logger.info("Ansible http client type set to " + clientType);
184
185             if ("TRUST_ALL".equals(clientType)) {
186                 logger.info(
187                         "Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
188                 httpClient = new ConnectionBuilder(1);
189             } else if ("TRUST_CERT".equals(clientType)) {
190                 // set path to keystore file
191                 String trustStoreFile = props.getProperty(TRUSTSTORE_PROPERTY_NAME);
192                 String key = props.getProperty(TRUSTPASSWD_PROPERTY_NAME);
193                 char[] trustStorePasswd = key.toCharArray();
194                 logger.info("Creating http client with trustmanager from " + trustStoreFile);
195                 httpClient = new ConnectionBuilder(trustStoreFile, trustStorePasswd);
196             } else {
197                 logger.info("Creating http client with default behaviour");
198                 httpClient = new ConnectionBuilder(0);
199             }
200         } catch (Exception e) {
201             logger.error("Error Initializing Ansible Adapter due to Unknown Exception", e);
202         }
203
204         logger.info("Initialized Ansible Adapter");
205     }
206
207     // Public Method to post request to execute playbook. Posts the following back
208     // to Svc context memory
209     //  org.onap.appc.adapter.ansible.req.code : 100 if successful
210     //  org.onap.appc.adapter.ansible.req.messge : any message
211     //  org.onap.appc.adapter.ansible.req.Id : a unique uuid to reference the request
212     @Override
213     public void reqExec(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
214
215         String playbookName = StringUtils.EMPTY;
216         String payload = StringUtils.EMPTY;
217         String agentUrl = StringUtils.EMPTY;
218         String user = StringUtils.EMPTY;
219         String password = StringUtils.EMPTY;
220         String id = StringUtils.EMPTY;
221
222         JSONObject jsonPayload;
223
224         try {
225             // create json object to send request
226             jsonPayload = messageProcessor.reqMessage(params);
227
228             agentUrl = (String) jsonPayload.remove("AgentUrl");
229             user = (String) jsonPayload.remove("User");
230             password = (String) jsonPayload.remove(PASSWORD);
231             id = jsonPayload.getString("Id");
232             payload = jsonPayload.toString();
233             logger.info("Updated Payload  = " + payload);
234         } catch (APPCException e) {
235             logger.error(APPC_EXCEPTION_CAUGHT, e);
236             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
237                     "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = "
238                             + e.getMessage());
239         } catch (JSONException e) {
240             logger.error("JSONException caught", e);
241             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
242                     "Error constructing request for execution of playbook due to invalid JSON block. Reason = "
243                             + e.getMessage());
244         } catch (NumberFormatException e) {
245             logger.error("NumberFormatException caught", e);
246             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
247                     "Error constructing request for execution of playbook due to invalid parameter values. Reason = "
248                             + e.getMessage());
249         }
250
251         int code = -1;
252         String message = StringUtils.EMPTY;
253
254         try {
255             // post the test request
256             logger.info("Posting request = " + payload + " to url = " + agentUrl);
257             AnsibleResult testResult = postExecRequest(agentUrl, payload, user, password);
258
259             // Process if HTTP was successful
260             if (testResult.getStatusCode() == 200) {
261                 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
262             } else {
263                 doFailure(ctx, testResult.getStatusCode(),
264                         "Error posting request. Reason = " + testResult.getStatusMessage());
265             }
266
267             code = testResult.getStatusCode();
268             message = testResult.getStatusMessage();
269
270             // Check status of test request returned by Agent
271             if (code == AnsibleResultCodes.PENDING.getValue()) {
272                 logger.info(String.format("Submission of Test %s successful.", playbookName));
273                 // test request accepted. We are in asynchronous case
274             } else {
275                 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
276             }
277         } catch (APPCException e) {
278             logger.error(APPC_EXCEPTION_CAUGHT, e);
279             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
280                     "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
281         }
282
283         ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
284         ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
285         ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
286     }
287
288     /**
289      * Public method to query status of a specific request It blocks till the Ansible Server
290      * responds or the session times out (non-Javadoc)
291      *
292      * @see org.onap.appc.adapter.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
293      *      org.onap.ccsdk.sli.core.sli.SvcLogicContext)
294      */
295     @Override
296     public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
297
298         // Get URI
299         String reqUri = StringUtils.EMPTY;
300
301         try {
302             reqUri = messageProcessor.reqUriResult(params);
303             logger.info("Got uri ", reqUri );
304         } catch (APPCException e) {
305             logger.error(APPC_EXCEPTION_CAUGHT, e);
306             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
307                     "Error constructing request to retrieve result due to missing parameters. Reason = "
308                             + e.getMessage());
309             return;
310         } catch (NumberFormatException e) {
311             logger.error("NumberFormatException caught", e);
312             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
313                     "Error constructing request to retrieve result due to invalid parameters value. Reason = "
314                             + e.getMessage());
315             return;
316         }
317
318         int code = -1;
319         String message = StringUtils.EMPTY;
320         String results = StringUtils.EMPTY;
321         String output = StringUtils.EMPTY;
322
323         try {
324             // Try to retrieve the test results (modify the URL for that)
325             AnsibleResult testResult = queryServer(reqUri, params.get("User"), params.get(PASSWORD));
326             code = testResult.getStatusCode();
327             message = testResult.getStatusMessage();
328
329             if (code == 200 || code == 400 || "FINISHED".equalsIgnoreCase(message)) {
330                 logger.info("Parsing response from Server = " + message);
331                 // Valid HTTP. process the Ansible message
332                 testResult = messageProcessor.parseGetResponse(message);
333                 code = testResult.getStatusCode();
334                 message = testResult.getStatusMessage();
335                 results = testResult.getResults();
336                 output = testResult.getOutput();
337             }
338
339             logger.info("Request response = " + message);
340         } catch (APPCException e) {
341             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
342                     "Exception encountered retrieving result : " + e.getMessage());
343             return;
344         }
345
346         // We were able to get and process the results. Determine if playbook succeeded
347
348         if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()) {
349             message = String.format("Ansible Request  %s finished with Result = %s, Message = %s", params.get("Id"),
350                     OUTCOME_SUCCESS, message);
351             logger.info(message);
352         } else {
353             logger.info(String.format("Ansible Request  %s finished with Result %s, Message = %s", params.get("Id"),
354                     OUTCOME_FAILURE, message));
355             ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
356             ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
357             doFailure(ctx, code, message);
358             return;
359         }
360
361         // In case of 200,400,FINISHED return 400
362         ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, "400");
363         ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
364         ctx.setAttribute(RESULTS_ATTRIBUTE_NAME, results);
365         ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, output);
366         ctx.setStatus(OUTCOME_SUCCESS);
367     }
368
369     /**
370      * Public method to get logs from playbook execution for a specific request
371      *
372      * It blocks till the Ansible Server responds or the session times out very similar to
373      * reqExecResult logs are returned in the DG context variable org.onap.appc.adapter.ansible.log
374      */
375     @Override
376     public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
377
378         String reqUri = StringUtils.EMPTY;
379         try {
380             reqUri = messageProcessor.reqUriLog(params);
381             logger.info("Retrieving results from " + reqUri);
382         } catch (Exception e) {
383             logger.error("Exception caught", e);
384             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
385         }
386
387         String message = StringUtils.EMPTY;
388         try {
389             // Try to retrieve the test results (modify the url for that)
390             AnsibleResult testResult = queryServer(reqUri, params.get("User"), params.get(PASSWORD));
391             message = testResult.getStatusMessage();
392             logger.info("Request output = " + message);
393             ctx.setAttribute(LOG_ATTRIBUTE_NAME, message);
394             ctx.setStatus(OUTCOME_SUCCESS);
395         } catch (Exception e) {
396             logger.error("Exception caught", e);
397             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
398                     "Exception encountered retreiving output : " + e.getMessage());
399         }
400     }
401
402     /**
403      * Method that posts the request
404      */
405     private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password) {
406
407         AnsibleResult testResult;
408
409         if (!testMode) {
410             httpClient.setHttpContext(user, password);
411             testResult = httpClient.post(agentUrl, payload);
412         } else {
413             testResult = testServer.Post(agentUrl, payload);
414         }
415         return testResult;
416     }
417
418     /**
419      * Method to query Ansible server
420      */
421     private AnsibleResult queryServer(String agentUrl, String user, String password) {
422
423         AnsibleResult testResult;
424
425         logger.info("Querying url = " + agentUrl);
426
427         if (!testMode) {
428             testResult = httpClient.get(agentUrl);
429         } else {
430             testResult = testServer.Get(agentUrl);
431         }
432
433         return testResult;
434     }
435 }