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