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