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