Code clean-up in ChefAdapterImpl.java
[appc.git] / appc-adapters / appc-ansible-adapter / appc-ansible-adapter-bundle / src / main / java / org / openecomp / 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.openecomp.appc.adapter.ansible.impl;
26
27 import java.net.URI;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.Set;
34 import java.util.regex.Pattern;
35 import java.lang.*;
36     
37 import org.openecomp.appc.Constants;
38 import org.openecomp.appc.exceptions.APPCException;
39
40 import org.openecomp.appc.configuration.Configuration;
41 import org.openecomp.appc.configuration.ConfigurationFactory;
42 import org.openecomp.appc.exceptions.APPCException;
43 import org.openecomp.appc.i18n.Msg;
44 import org.openecomp.appc.pool.Pool;
45 import org.openecomp.appc.pool.PoolExtensionException;
46 import org.openecomp.appc.util.StructuredPropertyHelper;
47 import org.openecomp.appc.util.StructuredPropertyHelper.Node;
48
49 import org.openecomp.sdnc.sli.SvcLogicContext;
50 import org.openecomp.sdnc.sli.SvcLogicException;
51
52
53 import org.slf4j.MDC;
54
55 import org.json.JSONObject;
56 import org.json.JSONArray;
57 import org.json.JSONException;
58
59
60 import com.google.common.base.Strings;
61 //import com.google.gson.Gson;
62 //import com.google.gson.GsonBuilder;
63
64 import org.openecomp.appc.adapter.ansible.AnsibleAdapter;
65
66 import org.openecomp.appc.adapter.ansible.model.AnsibleResult;
67 import org.openecomp.appc.adapter.ansible.model.AnsibleMessageParser;
68 import org.openecomp.appc.adapter.ansible.model.AnsibleResultCodes;
69 import org.openecomp.appc.adapter.ansible.model.AnsibleServerEmulator;
70
71 import com.att.eelf.configuration.EELFLogger;
72 import com.att.eelf.configuration.EELFManager;
73 import com.att.eelf.i18n.EELFResourceManager;
74 import static com.att.eelf.configuration.Configuration.*;
75
76
77 /**
78  * This class implements the {@link AnsibleAdapter} interface. This interface
79  * defines the behaviors that our service provides.
80  *
81  */
82 public class AnsibleAdapterImpl implements AnsibleAdapter {
83
84     /**
85      * The constant used to define the adapter name in the mapped diagnostic
86      * context
87      */
88         
89
90     @SuppressWarnings("nls")
91     public static final String MDC_ADAPTER = "Ansible Adapter";
92
93     /**
94      * The constant used to define the service name in the mapped diagnostic
95      * context
96      */
97     @SuppressWarnings("nls")
98     public static final String MDC_SERVICE = "service";
99
100     /**
101      * The constant for the status code for a failed outcome
102      */
103     @SuppressWarnings("nls")
104     public static final String OUTCOME_FAILURE = "failure";
105
106     /**
107      * The constant for the status code for a successful outcome
108      */
109     @SuppressWarnings("nls")
110     public static final String OUTCOME_SUCCESS = "success";
111
112     /**
113       Adapter Name 
114     **/
115     private static final String ADAPTER_NAME = "Ansible Adapter";
116
117    
118     /**
119      * The logger to be used
120      */
121     private static final EELFLogger logger = EELFManager.getInstance().getLogger(AnsibleAdapterImpl.class);
122     
123     /**
124       * A reference to the adapter configuration object.
125     */
126     private Configuration configuration;;
127
128     /** can Specify a X509 certificate file for use if required ... 
129     Must be initialized with setCertFile 
130     **/
131     private String certFile = "";
132
133
134     /**
135      * Connection object 
136      **/
137     ConnectionBuilder  http_client ;
138     
139     /** 
140      * Ansible API Message Handlers
141      **/
142     private AnsibleMessageParser messageProcessor;
143
144     /**
145        indicator whether in test mode
146     **/
147     private boolean  testMode = false;
148
149     /**
150        server emulator object to be used if in test mode 
151     **/
152     private AnsibleServerEmulator testServer;
153     
154     /**
155      * This default constructor is used as a work around because the activator
156      * wasnt getting called
157      */
158     public AnsibleAdapterImpl() {
159         initialize();
160     }
161
162
163     /**
164      * @param props
165      *            not used
166      */
167     public AnsibleAdapterImpl(Properties props) {
168         initialize();
169     }
170
171
172
173     /** 
174         Used for jUnit test and testing interface 
175     **/
176     public AnsibleAdapterImpl(boolean Mode){
177         testMode = Mode;
178         testServer = new AnsibleServerEmulator();
179         messageProcessor = new AnsibleMessageParser();
180     }
181     
182     /**
183      * Returns the symbolic name of the adapter
184      * 
185      * @return The adapter name
186      * @see org.openecomp.appc.adapter.rest.AnsibleAdapter#getAdapterName()
187      */
188     @Override
189     public String getAdapterName() {
190         return ADAPTER_NAME;
191     }
192
193
194     
195     /**
196      * @param rc
197      *  Method posts info to Context memory in case of an error
198      *  and throws a SvcLogicException causing SLI to register this as a failure
199      */
200     @SuppressWarnings("static-method")
201     private void doFailure(SvcLogicContext svcLogic,  int code, String message)  throws SvcLogicException {
202
203         svcLogic.setStatus(OUTCOME_FAILURE);
204         svcLogic.setAttribute("org.openecomp.appc.adapter.ansible.result.code",Integer.toString(code));
205         svcLogic.setAttribute("org.openecomp.appc.adapter.ansible.message",message);
206         
207         throw new SvcLogicException("Ansible Adapter Error = " + message );
208     }
209         
210
211     /**
212      * initialize the  Ansible adapter based on default and over-ride configuration data  
213      */
214     private void initialize()  {
215
216         configuration = ConfigurationFactory.getConfiguration();
217         Properties props = configuration.getProperties();
218         
219         // Create the message processor instance 
220         messageProcessor = new AnsibleMessageParser();
221
222         // Create the http client instance
223         // type of client is extracted from the property file parameter
224         // org.openecomp.appc.adapter.ansible.clientType
225         // It can be :
226         //     1. TRUST_ALL  (trust all SSL certs). To be used ONLY in dev
227         //     2. TRUST_CERT (trust only those whose certificates have been stored in the trustStore file)
228         //     3. DEFAULT    (trust only well known certificates). This is standard behaviour to which it will
229         //     revert. To be used in PROD
230
231         try{
232             String clientType = props.getProperty("org.openecomp.appc.adapter.ansible.clientType");
233             logger.info("Ansible http client type set to " + clientType);
234
235             if (clientType.equals("TRUST_ALL")){
236                 logger.info("Creating http client to trust ALL ssl certificates. WARNING. This should be done only in dev environments");
237                 http_client = new ConnectionBuilder(1);
238             }
239             else if (clientType.equals("TRUST_CERT")){
240                 // set path to keystore file
241                 String trustStoreFile = props.getProperty("org.openecomp.appc.adapter.ansible.trustStore");
242                 String key  = props.getProperty("org.openecomp.appc.adapter.ansible.trustStore.trustPasswd");
243                 char [] trustStorePasswd = key.toCharArray();
244                 String trustStoreType = "JKS";
245                 logger.info("Creating http client with trustmanager from " + trustStoreFile);
246                 http_client = new ConnectionBuilder(trustStoreFile, trustStorePasswd);
247             }
248             else{
249                 logger.info("Creating http client with default behaviour");
250                 http_client = new ConnectionBuilder(0);
251             }
252         }
253         catch (Exception e){
254             logger.error("Error Initializing Ansible Adapter due to Unknown Exception: reason = " + e.getMessage());
255         }
256
257         logger.info("Intitialized Ansible Adapter");
258         
259     }
260
261
262     /** set the certificate file if not a trusted/known CA **/
263     private void setCertFile(String CertFile){
264         this.certFile = CertFile;
265     }
266     
267
268
269     // Public Method to post request to execute playbook. Posts the following back
270     // to Svc context memory
271     //  org.openecomp.appc.adapter.ansible.req.code : 100 if successful
272     //  org.openecomp.appc.adapter.ansible.req.messge : any message
273     //  org.openecomp.appc.adapter.ansible.req.Id : a unique uuid to reference the request
274
275     public void reqExec(Map <String, String> params, SvcLogicContext ctx) throws SvcLogicException {
276
277         String PlaybookName = "";
278         String payload = "";
279         String AgentUrl = "";
280         String User = "";
281         String Password = "";
282         String Id = "";
283         
284         JSONObject JsonPayload;
285         
286         try{
287             // create json object to send request
288             JsonPayload = messageProcessor.ReqMessage(params);
289             
290             AgentUrl = (String) JsonPayload.remove("AgentUrl");
291             User =  (String) JsonPayload.remove("User");
292             Password = (String) JsonPayload.remove("Password");
293             Id = (String)JsonPayload.getString("Id");
294             payload = JsonPayload.toString();
295             logger.info("Updated Payload  = "  + payload);
296         }
297         catch(APPCException e){
298             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), "Error constructing request for execution of playbook due to missing mandatory parameters. Reason = " + e.getMessage());
299         }
300         catch(JSONException e){
301             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), "Error constructing request for execution of playbook due to invalid JSON block. Reason = " + e.getMessage());
302         }
303         catch(NumberFormatException e){
304             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), "Error constructing request for execution of playbook due to invalid parameter values. Reason = " + e.getMessage());
305         }
306         
307     
308  
309         int code = -1;
310         String message = "";
311         
312         try{
313             
314             // post the test request
315             //---------------------------------------
316             logger.info("Posting request = " + payload + " to url = " + AgentUrl );
317             AnsibleResult testresult = postExecRequest(AgentUrl, payload, User, Password);
318
319     
320             // Process if HTTP was successfull
321             if(testresult.getStatusCode() == 200){
322                 testresult = messageProcessor.parsePostResponse(testresult.getStatusMessage());
323             }
324             else{
325                 doFailure(ctx, testresult.getStatusCode(), "Error posting request. Reason = " + testresult.getStatusMessage());
326             }
327
328     
329             code = testresult.getStatusCode();
330             message = testresult.getStatusMessage();
331
332                     
333             // Check status of test request returned by Agent
334             //-----------------------------------------------
335             if (code == AnsibleResultCodes.PENDING.getValue()){
336                 logger.info(String.format("Submission of Test %s successful.", PlaybookName));
337                 // test request accepted. We are in asynchronous case
338             }
339             else{
340                 doFailure(ctx, code, "Request for execution of playbook rejected. Reason = " + message);
341             }
342         }
343         
344         catch(APPCException e){
345             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(), "Exception encountered when posting request for execution of playbook. Reason = "  + e.getMessage());
346         }
347
348
349         ctx.setAttribute("org.openecomp.appc.adapter.ansible.result.code", Integer.toString(code));
350         ctx.setAttribute("org.openecomp.appc.adapter.ansible.message", message );
351         ctx.setAttribute("org.openecomp.appc.adapter.ansible.Id", Id);
352         
353     }
354
355
356     // Public method to query status of a specific request
357     // It blocks till the Ansible Server responds or the session times out
358     
359     public void reqExecResult(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
360
361             
362         // Get uri
363         String ReqUri = "";
364         
365         try{
366             ReqUri = messageProcessor.ReqUri_Result(params);
367             System.out.println("Got uri = " + ReqUri);
368         }
369         catch(APPCException e){
370             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), "Error constructing request to retreive result due to missing parameters. Reason = " + e.getMessage());
371             return;
372         }
373         catch(NumberFormatException e){
374             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), "Error constructing request to retreive result due to invalid parameters value. Reason = " + e.getMessage());
375             return;
376         }
377
378         int code = -1;
379         String message = "";
380         String results = "";
381         
382         try{
383             // Try to  retreive the test results (modify the url for that)
384             AnsibleResult testresult = queryServer(ReqUri, params.get("User"), params.get("Password"));
385             code = testresult.getStatusCode();
386             message = testresult.getStatusMessage();
387
388             if(code == 200){
389                 logger.info("Parsing response from Server = " + message);
390                 // Valid HTTP. process the Ansible message
391                 testresult = messageProcessor.parseGetResponse(message);
392                 code = testresult.getStatusCode();
393                 message = testresult.getStatusMessage();
394                 results = testresult.getResults();
395                 
396             }
397             
398             logger.info("Request response = " + message);
399
400         }
401         catch (APPCException e){
402             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(), "Exception encountered retreiving result : " + e.getMessage());
403             return;
404         }
405
406         // We were able to get and process the results. Determine if playbook succeeded
407         
408         if (code == AnsibleResultCodes.FINAL_SUCCESS.getValue()){
409             message = String.format("Ansible Request  %s finished with Result = %s, Message = %s", params.get("Id"), OUTCOME_SUCCESS, message);
410             logger.info(message);
411         }
412         else {
413             logger.info(String.format("Ansible Request  %s finished with Result %s, Message = %s", params.get("Id"), OUTCOME_FAILURE, message));
414             ctx.setAttribute("org.openecomp.appc.adapter.ansible.results", results);
415             doFailure(ctx, code, message );
416             return;         
417         }
418         
419       
420         ctx.setAttribute("org.openecomp.appc.adapter.ansible.result.code", Integer.toString(400));
421         ctx.setAttribute("org.openecomp.appc.adapter.ansible.message",message);
422         ctx.setAttribute("org.openecomp.appc.adapter.ansible.results", results);
423         ctx.setStatus(OUTCOME_SUCCESS);
424     }
425     
426
427     // Public method to get logs  from plyabook execution for a  specifcic request
428     // It blocks till the Ansible Server responds or the session times out
429     // very similar to reqExecResult
430     // logs are returned in the DG context variable org.openecomp.appc.adapter.ansible.log
431     
432     public void reqExecLog(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException{
433
434
435         // Get uri
436         String ReqUri = "";
437         try{
438             ReqUri = messageProcessor.ReqUri_Log(params);
439             logger.info("Retreiving results from " + ReqUri); 
440         }
441         catch(Exception e){
442             doFailure(ctx, AnsibleResultCodes.INVALID_PAYLOAD.getValue(), e.getMessage());
443         }
444
445         int code = -1;
446         String message = "";
447         float Duration = -1;
448         
449         try{
450             // Try to  retreive the test results (modify the url for that)
451             AnsibleResult testresult = queryServer(ReqUri, params.get("User"), params.get("Password"));
452             code = testresult.getStatusCode();
453             message = testresult.getStatusMessage();
454
455             logger.info("Request output = " + message);
456
457         }
458         catch (Exception e){
459             doFailure(ctx, AnsibleResultCodes.UNKNOWN_EXCEPTION.getValue(), "Exception encountered retreiving output : " + e.getMessage());
460         }
461         
462         ctx.setAttribute("org.openecomp.appc.adapter.ansible.log",message);
463         ctx.setStatus(OUTCOME_SUCCESS);
464     }
465     
466
467     
468
469         
470     /**
471      * Method that posts the request
472      **/
473     
474     private AnsibleResult  postExecRequest(String AgentUrl, String Payload, String User, String Password)  {
475         
476         String reqOutput = "UNKNOWN";
477         int    reqStatus = -1;
478
479         AnsibleResult testresult;
480         
481         if (!testMode){
482             http_client.setHttpContext(User, Password);
483             testresult  = http_client.Post(AgentUrl, Payload);
484         }
485         else{
486             testresult = testServer.Post(AgentUrl, Payload);
487         }
488            
489         return testresult;
490     }
491     
492
493     /* 
494        Method to query Ansible server
495
496     */
497     private AnsibleResult queryServer(String AgentUrl, String User, String Password) {
498
499         String testOutput = "UNKNOWN";
500         int    testStatus = -1;
501         AnsibleResult testresult;
502         
503         logger.info("Querying url = " + AgentUrl);
504
505         if (!testMode){
506             testresult = http_client.Get(AgentUrl);
507         }
508         else{
509             testresult = testServer.Get(AgentUrl);
510         }
511         
512         return testresult;
513         
514     }
515
516
517
518 }