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