Fix sonar issues in AnsibleMessageParser
[appc.git] / appc-adapters / appc-ansible-adapter / appc-ansible-adapter-bundle / src / main / java / org / onap / appc / adapter / ansible / model / AnsibleMessageParser.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.onap.appc.adapter.ansible.model;
26
27 /**
28  * This module imples the APP-C/Ansible Server interface
29  * based on the REST API specifications
30  */
31
32 import java.util.*;
33
34 import com.google.common.base.Strings;
35
36 import org.json.JSONObject;
37 import org.json.JSONArray;
38 import org.json.JSONException;
39 import org.onap.appc.exceptions.APPCException;
40
41 /**
42  * Class that validates and constructs requests sent/received from
43  * Ansible Server
44  */
45 public class AnsibleMessageParser {
46
47     private static final String STATUS_MESSAGE_KEY = "StatusMessage";
48     private static final String STATUS_CODE_KEY = "StatusCode";
49
50     private static final String PLAYBOOK_NAME_KEY = "PlaybookName";
51     private static final String AGENT_URL_KEY = "AgentUrl";
52     private static final String PASS_KEY = "Password";
53     private static final String USER_KEY = "User";
54     private static final String ID_KEY = "Id";
55
56     private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
57     private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
58     private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
59     private static final String NODE_LIST_OPT_KEY = "NodeList";
60     private static final String TIMEOUT_OPT_KEY = "Timeout";
61     private static final String VERSION_OPT_KEY = "Version";
62     private static final String ACTION_OPT_KEY = "Action";
63
64     /**
65      * Accepts a map of strings and
66      * a) validates if all parameters are appropriate (else, throws an exception) and
67      * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
68      *
69      * Mandatory  parameters, that must be in the supplied information to the Ansible Adapter
70      * 1. URL to connect to
71      * 2. credentials for URL (assume username password for now)
72      * 3. Playbook name
73      *
74      */
75     public JSONObject reqMessage(Map<String, String> params) throws APPCException {
76         final String[] mandatoryTestParams = { AGENT_URL_KEY, PLAYBOOK_NAME_KEY, USER_KEY, PASS_KEY };
77         final String[] optionalTestParams = { ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
78             TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY };
79
80         JSONObject jsonPayload = new JSONObject();
81
82         for (String key : mandatoryTestParams) {
83             throwIfMissingMandatoryParam(params, key);
84             jsonPayload.put(key, params.get(key));
85         }
86
87         parseOptionalParams(params, optionalTestParams, jsonPayload);
88
89         // Generate a unique uuid for the test
90         String reqId = UUID.randomUUID().toString();
91         jsonPayload.put(ID_KEY, reqId);
92
93         return jsonPayload;
94     }
95
96     /**
97      * Method that validates that the Map has enough information
98      * to query Ansible server for a result. If so, it returns
99      * the appropriate url, else an empty string.
100      */
101     public String reqUriResult(Map<String, String> params) throws APPCException {
102
103         final String[] mandatoryTestParams = { AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY };
104
105         for (String key : mandatoryTestParams) {
106             throwIfMissingMandatoryParam(params, key);
107         }
108         return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetResult";
109     }
110
111     /**
112      * Method that validates that the Map has enough information
113      * to query Ansible server for logs. If so, it populates the
114      * appropriate returns the appropriate url, else an empty string.
115      */
116     public String reqUriOutput(Map<String, String> params) throws APPCException {
117
118         final String[] mandatoryTestParams = { AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY };
119
120         for (String mandatoryParam : mandatoryTestParams) {
121             throwIfMissingMandatoryParam(params, mandatoryParam);
122         }
123         return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetOutput";
124     }
125
126     /**
127      * Method that validates that the Map has enough information
128      * to query Ansible server for logs. If so, it populates the appropriate
129      * returns the appropriate url, else an empty string.
130      */
131     public String reqUriLog(Map<String, String> params) throws APPCException {
132
133         final String[] mandatoryTestParams = { AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY };
134
135         for (String mandatoryParam : mandatoryTestParams) {
136             throwIfMissingMandatoryParam(params, mandatoryParam);
137         }
138         return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetLog";
139     }
140
141     /**
142      * This method parses response from the Ansible Server when we do a post
143      * and returns an AnsibleResult object.
144      */
145     public AnsibleResult parsePostResponse(String input) throws APPCException {
146         AnsibleResult ansibleResult;
147         try {
148             JSONObject postResponse = new JSONObject(input);
149
150             int code = postResponse.getInt(STATUS_CODE_KEY);
151             String msg = postResponse.getString(STATUS_MESSAGE_KEY);
152
153             int initResponseValue = AnsibleResultCodes.INITRESPONSE.getValue();
154             boolean validCode = AnsibleResultCodes.CODE.checkValidCode(initResponseValue, code);
155             if (!validCode) {
156                 throw new APPCException("Invalid InitResponse code  = " + code + " received. MUST be one of " + AnsibleResultCodes.CODE.getValidCodes(initResponseValue));
157             }
158
159             ansibleResult = new AnsibleResult(code, msg);
160
161         } catch (JSONException e) {
162             ansibleResult = new AnsibleResult(600, "Error parsing response = " + input + ". Error = " + e.getMessage());
163         }
164         return ansibleResult;
165     }
166
167     /**
168      * This method parses response from an Ansible server when we do a GET for a result
169      * and returns an AnsibleResult object.
170      **/
171     public AnsibleResult  parseGetResponse(String input) throws APPCException {
172
173         AnsibleResult ansibleResult = new AnsibleResult();
174         int finalCode = AnsibleResultCodes.FINAL_SUCCESS.getValue();
175
176         try{
177             JSONObject  postResponse = new JSONObject(input);
178             
179             int codeStatus = postResponse.getInt(STATUS_CODE_KEY);
180             String messageStatus = postResponse.getString(STATUS_MESSAGE_KEY);
181             
182             boolean valCode = AnsibleResultCodes.CODE.checkValidCode(AnsibleResultCodes.FINALRESPONSE.getValue(), codeStatus);
183             
184             if(!valCode){
185                 throw new APPCException("Invalid FinalResponse code  = " + codeStatus + " received. MUST be one of " + AnsibleResultCodes.CODE.getValidCodes(AnsibleResultCodes.FINALRESPONSE.getValue()));
186             }
187
188             ansibleResult.setStatusCode(codeStatus);
189             ansibleResult.setStatusMessage(messageStatus);
190             System.out.println("Received response with code = " + Integer.toString(codeStatus) + " Message = " + messageStatus);
191
192             if(! postResponse.isNull("Results")){
193
194                 // Results are available. process them
195                 // Results is a dictionary of the form
196                 // {host :{status:s, group:g, message:m, hostname:h}, ...}
197                 System.out.println("Processing results in response");
198                 JSONObject results = postResponse.getJSONObject("Results");
199                 System.out.println("Get JSON dictionary from Results ..");
200                 Iterator<String> hosts = results.keys();
201                 System.out.println("Iterating through hosts");
202                 
203                 while(hosts.hasNext()){
204                     String host = hosts.next();
205                     System.out.println("Processing host = " + host);
206                     
207                     try{
208                         JSONObject hostResponse = results.getJSONObject(host);
209                         int subCode = hostResponse.getInt(STATUS_CODE_KEY);
210                         String message = hostResponse.getString(STATUS_MESSAGE_KEY);
211
212                         System.out.println("Code = " + Integer.toString(subCode) + " Message = " + message);
213                         
214                         if(subCode != 200 || ! message.equals("SUCCESS")){
215                             finalCode = AnsibleResultCodes.REQ_FAILURE.getValue();
216                         }
217                     }
218                     catch(JSONException e){
219                         ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
220                         ansibleResult.setStatusMessage(String.format("Error processing response message = %s from host %s", results.getString(host), host));
221                         break;
222                     }
223                 }
224
225                 ansibleResult.setStatusCode(finalCode);
226
227                 // We return entire Results object as message
228                 ansibleResult.setResults(results.toString());
229
230             }
231             else{
232                 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
233                 ansibleResult.setStatusMessage("Results not found in GET for response");
234             }
235             
236             
237         }
238         catch(JSONException e){
239             ansibleResult = new AnsibleResult(AnsibleResultCodes.INVALID_PAYLOAD.getValue(), "Error parsing response = " + input + ". Error = " + e.getMessage(), "");
240         }
241
242
243         return ansibleResult;
244     }
245
246     private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
247
248         Set<String> optionalParamsSet = new HashSet<>();
249         Collections.addAll(optionalParamsSet, optionalTestParams);
250
251         params.entrySet()
252             .stream()
253             .filter(entry -> optionalParamsSet.contains(entry.getKey()))
254             .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
255             .forEach(entry -> parseOptionalParam(entry, jsonPayload));
256     }
257
258     private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
259         String key = params.getKey();
260         String payload = params.getValue();
261
262         switch (key) {
263             case TIMEOUT_OPT_KEY:
264                 int timeout = Integer.parseInt(payload);
265                 if (timeout < 0) {
266                     throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
267                 }
268                 jsonPayload.put(key, payload);
269                 break;
270
271             case VERSION_OPT_KEY:
272                 jsonPayload.put(key, payload);
273                 break;
274
275             case LOCAL_PARAMETERS_OPT_KEY:
276             case ENV_PARAMETERS_OPT_KEY:
277                 JSONObject paramsJson = new JSONObject(payload);
278                 jsonPayload.put(key, paramsJson);
279                 break;
280
281             case NODE_LIST_OPT_KEY:
282                 JSONArray paramsArray = new JSONArray(payload);
283                 jsonPayload.put(key, paramsArray);
284                 break;
285
286             case FILE_PARAMETERS_OPT_KEY:
287                 // Files may have strings with newlines. Escape them as appropriate
288                 String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
289                 JSONObject fileParams = new JSONObject(formattedPayload);
290                 jsonPayload.put(key, fileParams);
291                 break;
292
293             default:
294                 break;
295         }
296     }
297
298     private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws APPCException {
299         if (!params.containsKey(key)) {
300             throw new APPCException(String.format("Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !", key));
301         }
302         if (Strings.isNullOrEmpty(params.get(key))) {
303             throw new APPCException(String.format("Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !", key));
304         }
305     }
306 }