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