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