2361feebdbf2e74e43cc01ba439779d88d2555e5
[ccsdk/sli.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     private static final String OUTPUT_ATTRIBUTE_NAME = "org.onap.appc.adapter.ansible.output";
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 TRUSTPASSD_PROPERTY_NAME = "org.onap.appc.adapter.ansible.trustStore.trustPasswd";
84
85     private static final String PASSD = "Password";
86
87     /**
88      * The logger to be used
89      */
90     private static final EELFLogger logger = EELFManager.getInstance().getLogger(AnsibleAdapterImpl.class);
91
92
93     /**
94      * Connection object
95      **/
96     private ConnectionBuilder httpClient;
97
98     /**
99      * Ansible API Message Handlers
100      **/
101     private AnsibleMessageParser messageProcessor;
102
103     /**
104      * indicator whether in test mode
105      **/
106     private boolean testMode = false;
107
108     /**
109      * server emulator object to be used if in test mode
110      **/
111     private AnsibleServerEmulator testServer;
112
113     /**
114      * This default constructor is used as a work around because the activator wasn't getting called
115      */
116     public AnsibleAdapterImpl() {
117         initialize(new AnsibleAdapterPropertiesProviderImpl());
118     }
119     public AnsibleAdapterImpl(AnsibleAdapterPropertiesProvider propProvider) {
120         initialize(propProvider);
121     }
122
123     /**
124      * Used for jUnit test and testing interface
125      */
126     public AnsibleAdapterImpl(boolean mode) {
127         testMode = mode;
128         testServer = new AnsibleServerEmulator();
129         messageProcessor = new AnsibleMessageParser();
130     }
131
132     /**
133      * Returns the symbolic name of the adapter
134      *
135      * @return The adapter name
136      * @see org.onap.appc.adapter.rest.AnsibleAdapter#getAdapterName()
137      */
138     @Override
139     public String getAdapterName() {
140         return ADAPTER_NAME;
141     }
142
143     /**
144      * @param rc Method posts info to Context memory in case of an error and throws a
145      *        SvcLogicException causing SLI to register this as a failure
146      */
147     @SuppressWarnings("static-method")
148     private void doFailure(SvcLogicContext svcLogic, int code, String message) throws SvcLogicException {
149
150         svcLogic.setStatus(OUTCOME_FAILURE);
151         svcLogic.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
152         svcLogic.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
153
154         throw new SvcLogicException("Ansible Adapter Error = " + message);
155     }
156
157     /**
158      * initialize the Ansible adapter based on default and over-ride configuration data
159      */
160     private void initialize(AnsibleAdapterPropertiesProvider propProvider) {
161
162
163         Properties props = propProvider.getProperties();
164
165         // Create the message processor instance
166         messageProcessor = new AnsibleMessageParser();
167
168         // Create the http client instance
169         // type of client is extracted from the property file parameter
170         // org.onap.appc.adapter.ansible.clientType
171         // It can be :
172         // 1. TRUST_ALL (trust all SSL certs). To be used ONLY in dev
173         // 2. TRUST_CERT (trust only those whose certificates have been stored in the trustStore file)
174         // 3. DEFAULT (trust only well known certificates). This is standard behavior to which it will
175         // revert. To be used in PROD
176
177         try {
178             String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
179             logger.info("Ansible http client type set to " + clientType);
180
181             if ("TRUST_ALL".equals(clientType)) {
182                 logger.info(
183                         "Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
184                 httpClient = new ConnectionBuilder(1);
185             } else if ("TRUST_CERT".equals(clientType)) {
186                 // set path to keystore file
187                 String trustStoreFile = props.getProperty(TRUSTSTORE_PROPERTY_NAME);
188                 String key = props.getProperty(TRUSTPASSD_PROPERTY_NAME);
189                 char[] trustStorePasswd = key.toCharArray();
190                 logger.info("Creating http client with trustmanager from " + trustStoreFile);
191                 httpClient = new ConnectionBuilder(trustStoreFile, trustStorePasswd);
192             } else {
193                 logger.info("Creating http client with default behaviour");
194                 httpClient = new ConnectionBuilder(0);
195             }
196         } catch (Exception e) {
197             logger.error("Error Initializing Ansible Adapter due to Unknown Exception", e);
198         }
199
200         logger.info("Initialized Ansible Adapter");
201     }
202
203     // Public Method to post request to execute playbook. Posts the following back
204     // to Svc context memory
205     //  org.onap.appc.adapter.ansible.req.code : 100 if successful
206     //  org.onap.appc.adapter.ansible.req.messge : any message
207     //  org.onap.appc.adapter.ansible.req.Id : a unique uuid to reference the request
208     @Override
209     public void reqExec(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
210
211         String playbookName = StringUtils.EMPTY;
212         String payload = StringUtils.EMPTY;
213         String agentUrl = StringUtils.EMPTY;
214         String user = StringUtils.EMPTY;
215         String password = StringUtils.EMPTY;
216         String id = StringUtils.EMPTY;
217
218         JSONObject jsonPayload;
219
220         try {
221             // create json object to send request
222             jsonPayload = messageProcessor.reqMessage(params);
223
224             agentUrl = (String) jsonPayload.remove("AgentUrl");
225             user = (String) jsonPayload.remove("User");
226             password = (String) jsonPayload.remove(PASSD);
227             id = jsonPayload.getString("Id");
228             payload = jsonPayload.toString();
229             logger.info("Updated Payload  = " + payload);
230         } catch (SvcLogicException e) {
231             logger.error(APPC_EXCEPTION_CAUGHT, e);
232             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
233                     "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = "
234                             + e.getMessage());
235         } catch (JSONException e) {
236             logger.error("JSONException caught", e);
237             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
238                     "Error constructing request for execution of playbook due to invalid JSON block. Reason = "
239                             + e.getMessage());
240         } catch (NumberFormatException e) {
241             logger.error("NumberFormatException caught", e);
242             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
243                     "Error constructing request for execution of playbook due to invalid parameter values. Reason = "
244                             + e.getMessage());
245         }
246
247         int code = -1;
248         String message = StringUtils.EMPTY;
249
250         try {
251             // post the test request
252             logger.info("Posting request = " + payload + " to url = " + agentUrl);
253             AnsibleResult testResult = postExecRequest(agentUrl, payload, user, password);
254
255             // Process if HTTP was successful
256             if (testResult.getStatusCode() == 200) {
257                 testResult = messageProcessor.parsePostResponse(testResult.getStatusMessage());
258             } else {
259                 doFailure(ctx, testResult.getStatusCode(),
260                         "Error posting request. Reason = " + testResult.getStatusMessage());
261             }
262
263             code = testResult.getStatusCode();
264             message = testResult.getStatusMessage();
265
266             // Check status of test request returned by Agent
267             if (code == AnsibleResultCodes.PENDING.getValue()) {
268                 logger.info(String.format("Submission of Test %s successful.", playbookName));
269                 // test request accepted. We are in asynchronous case
270             } else {
271                 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
272             }
273         } catch (SvcLogicException e) {
274             logger.error(APPC_EXCEPTION_CAUGHT, e);
275             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
276                     "Exception encountered when posting request for execution of playbook. Reason = " + e.getMessage());
277         }
278
279         ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(code));
280         ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, message);
281         ctx.setAttribute(ID_ATTRIBUTE_NAME, id);
282     }
283
284     /**
285      * Public method to query status of a specific request It blocks till the Ansible Server
286      * responds or the session times out (non-Javadoc)
287      *
288      * @see org.onap.ccsdk.sli.adaptors.ansible.AnsibleAdapter#reqExecResult(java.util.Map,
289      *      org.onap.ccsdk.sli.core.sli.SvcLogicContext)
290      */
291     @Override
292     public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
293
294         // Get URI
295         String reqUri = StringUtils.EMPTY;
296
297         try {
298             reqUri = messageProcessor.reqUriResult(params);
299             logger.info("Got uri ", reqUri );
300         } catch (SvcLogicException e) {
301             logger.error(APPC_EXCEPTION_CAUGHT, e);
302             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
303                     "Error constructing request to retrieve result due to missing parameters. Reason = "
304                             + e.getMessage());
305             return;
306         } catch (NumberFormatException e) {
307             logger.error("NumberFormatException caught", e);
308             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
309                     "Error constructing request to retrieve result due to invalid parameters value. Reason = "
310                             + e.getMessage());
311             return;
312         }
313
314         int code = -1;
315         String message = StringUtils.EMPTY;
316         String results = StringUtils.EMPTY;
317
318         try {
319             // Try to retrieve the test results (modify the URL for that)
320             AnsibleResult testResult = queryServer(reqUri, params.get("User"), params.get(PASSD));
321             code = testResult.getStatusCode();
322             message = testResult.getStatusMessage();
323
324             if (code == 200) {
325                 logger.info("Parsing response from Server = " + message);
326                 // Valid HTTP. process the Ansible message
327                 testResult = messageProcessor.parseGetResponse(message);
328                 code = testResult.getStatusCode();
329                 message = testResult.getStatusMessage();
330                 results = testResult.getResults();
331             }
332
333             logger.info("Request response = " + message);
334         } catch (SvcLogicException e) {
335             logger.error(APPC_EXCEPTION_CAUGHT, 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(PASSD));
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      * Public method to get output from playbook execution for a specific request
396      *
397      * It blocks till the Ansible Server responds or the session times out very similar to
398      * reqExecResult and output is returned in the DG context variable org.onap.appc.adapter.ansible.output
399      */
400     @Override
401     public void reqExecOutput(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
402
403         String reqUri = StringUtils.EMPTY;
404         try {
405             reqUri = messageProcessor.reqUriOutput(params);
406             logger.info("Retrieving results from " + reqUri);
407         } catch (Exception e) {
408             logger.error("Exception caught", e);
409             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
410         }
411
412         String message = StringUtils.EMPTY;
413         try {
414             // Try to retrieve the test results (modify the url for that)
415             AnsibleResult testResult = queryServer(reqUri, params.get("User"), params.get(PASSD));
416             message = testResult.getStatusMessage();
417             logger.info("Request output = " + message);
418             ctx.setAttribute(OUTPUT_ATTRIBUTE_NAME, message);
419             ctx.setStatus(OUTCOME_SUCCESS);
420         } catch (Exception e) {
421             logger.error("Exception caught", e);
422             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(),
423                     "Exception encountered retreiving output : " + e.getMessage());
424         }
425     }
426
427     /**
428      * Method that posts the request
429      */
430     private AnsibleResult postExecRequest(String agentUrl, String payload, String user, String password) {
431
432         AnsibleResult testResult;
433
434         if (!testMode) {
435             httpClient.setHttpContext(user, password);
436             testResult = httpClient.post(agentUrl, payload);
437         } else {
438             testResult = testServer.Post(agentUrl, payload);
439         }
440         return testResult;
441     }
442
443     /**
444      * Method to query Ansible server
445      */
446     private AnsibleResult queryServer(String agentUrl, String user, String password) {
447
448         AnsibleResult testResult;
449
450         logger.info("Querying url = " + agentUrl);
451
452         if (!testMode) {
453             testResult = httpClient.get(agentUrl);
454         } else {
455             testResult = testServer.Get(agentUrl);
456         }
457
458         return testResult;
459     }
460 }