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