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