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