5a548f84dac4aa9b8c717d6da4a19a3534f4d329
[ccsdk/sli/adaptors.git] / saltstack-adapter / saltstack-adapter-provider / src / main / java / org / onap / ccsdk / sli / adaptors / saltstack / model / SaltstackMessageParser.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.ccsdk.sli.adaptors.saltstack.model;
26
27 /**
28  * This module implements the APP-C/Saltstack 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.ccsdk.sli.core.sli.SvcLogicException;
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  * Saltstack Server
48  */
49 //TODO: This class is to be altered completely based on the SALTSTACK server communicaiton.
50 public class SaltstackMessageParser {
51
52     private static final String STATUS_MESSAGE_KEY = "StatusMessage";
53     private static final String STATUS_CODE_KEY = "StatusCode";
54
55     private static final String SALTSTATE_NAME_KEY = "SaltStateName";
56     private static final String AGENT_URL_KEY = "AgentUrl";
57     private static final String PASS_KEY = "Password";
58     private static final String USER_KEY = "User";
59     private static final String ID_KEY = "Id";
60
61     private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
62     private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
63     private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
64     private static final String NODE_LIST_OPT_KEY = "NodeList";
65     private static final String TIMEOUT_OPT_KEY = "Timeout";
66     private static final String VERSION_OPT_KEY = "Version";
67     private static final String ACTION_OPT_KEY = "Action";
68
69     private static final Logger LOGGER = LoggerFactory.getLogger(SaltstackMessageParser.class);
70
71     /**
72      * Accepts a map of strings and
73      * a) validates if all parameters are appropriate (else, throws an exception) and
74      * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
75      *
76      * Mandatory parameters, that must be in the supplied information to the Saltstack Adapter
77      * 1. URL to connect to
78      * 2. credentials for URL (assume username password for now)
79      * 3. SaltState name
80      *
81      */
82     public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
83         final String[] mandatoryTestParams = {AGENT_URL_KEY, SALTSTATE_NAME_KEY, USER_KEY, PASS_KEY};
84         final String[] optionalTestParams = {ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
85                 TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY};
86
87         JSONObject jsonPayload = new JSONObject();
88
89         for (String key : mandatoryTestParams) {
90             throwIfMissingMandatoryParam(params, key);
91             jsonPayload.put(key, params.get(key));
92         }
93
94         parseOptionalParams(params, optionalTestParams, jsonPayload);
95
96         // Generate a unique uuid for the test
97         String reqId = UUID.randomUUID().toString();
98         jsonPayload.put(ID_KEY, reqId);
99
100         return jsonPayload;
101     }
102
103     /**
104      * Method that validates that the Map has enough information
105      * to query Saltstack server for a result. If so, it returns
106      * the appropriate url, else an empty string.
107      */
108     public String reqUriResult(Map<String, String> params) throws SvcLogicException {
109
110         final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
111
112         for (String key : mandatoryTestParams) {
113             throwIfMissingMandatoryParam(params, key);
114         }
115         return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetResult";
116     }
117
118     /**
119      * Method that validates that the Map has enough information
120      * to query Saltstack server for logs. If so, it populates the appropriate
121      * returns the appropriate url, else an empty string.
122      */
123     public String reqUriLog(Map<String, String> params) throws SvcLogicException {
124
125         final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
126
127         for (String mandatoryParam : mandatoryTestParams) {
128             throwIfMissingMandatoryParam(params, mandatoryParam);
129         }
130         return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetLog";
131     }
132
133     /**
134      * This method parses response from the Saltstack Server when we do a post
135      * and returns an SaltstackResult object.
136      */
137     public SaltstackResult parsePostResponse(String input) throws SvcLogicException {
138         SaltstackResult saltstackResult;
139         try {
140             JSONObject postResponse = new JSONObject(input);
141
142             int code = postResponse.getInt(STATUS_CODE_KEY);
143             String msg = postResponse.getString(STATUS_MESSAGE_KEY);
144
145             int initResponseValue = SaltstackResultCodes.INITRESPONSE.getValue();
146             boolean validCode = SaltstackResultCodes.CODE.checkValidCode(initResponseValue, code);
147             if (!validCode) {
148                 throw new SvcLogicException("Invalid InitResponse code  = " + code + " received. MUST be one of "
149                         + SaltstackResultCodes.CODE.getValidCodes(initResponseValue));
150             }
151
152             saltstackResult = new SaltstackResult(code, msg);
153
154         } catch (JSONException e) {
155             saltstackResult = new SaltstackResult(600, "Error parsing response = " + input + ". Error = " + e.getMessage());
156         }
157         return saltstackResult;
158     }
159
160     /**
161      * This method parses response from an Saltstack server when we do a GET for a result
162      * and returns an SaltstackResult object.
163      **/
164     public SaltstackResult parseGetResponse(String input) throws SvcLogicException {
165
166         SaltstackResult saltstackResult = new SaltstackResult();
167
168         try {
169             JSONObject postResponse = new JSONObject(input);
170             saltstackResult = parseGetResponseNested(saltstackResult, postResponse);
171         } catch (JSONException e) {
172             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_PAYLOAD.getValue(),
173                     "Error parsing response = " + input + ". Error = " + e.getMessage(), "");
174         }
175         return saltstackResult;
176     }
177
178     private SaltstackResult parseGetResponseNested(SaltstackResult saltstackResult, JSONObject postRsp) throws SvcLogicException  {
179
180         int codeStatus = postRsp.getInt(STATUS_CODE_KEY);
181         String messageStatus = postRsp.getString(STATUS_MESSAGE_KEY);
182         int finalCode = SaltstackResultCodes.FINAL_SUCCESS.getValue();
183
184         boolean valCode =
185                 SaltstackResultCodes.CODE.checkValidCode(SaltstackResultCodes.FINALRESPONSE.getValue(), codeStatus);
186
187         if (!valCode) {
188             throw new SvcLogicException("Invalid FinalResponse code  = " + codeStatus + " received. MUST be one of "
189                     + SaltstackResultCodes.CODE.getValidCodes(SaltstackResultCodes.FINALRESPONSE.getValue()));
190         }
191
192         saltstackResult.setStatusCode(codeStatus);
193         saltstackResult.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 || !message.equals("SUCCESS")) {
219                         finalCode = SaltstackResultCodes.REQ_FAILURE.getValue();
220                     }
221                 } catch (JSONException e) {
222                     saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
223                     saltstackResult.setStatusMessage(String.format(
224                             "Error processing response message = %s from host %s", results.getString(host), host));
225                     break;
226                 }
227             }
228
229             saltstackResult.setStatusCode(finalCode);
230
231             // We return entire Results object as message
232             saltstackResult.setResults(results.toString());
233
234         } else {
235             saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
236             saltstackResult.setStatusMessage("Results not found in GET for response");
237         }
238         return saltstackResult;
239     }
240
241     private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
242
243         Set<String> optionalParamsSet = new HashSet<>();
244         Collections.addAll(optionalParamsSet, optionalTestParams);
245
246         //@formatter:off
247         params.entrySet()
248             .stream()
249             .filter(entry -> optionalParamsSet.contains(entry.getKey()))
250             .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
251              .forEach(entry -> parseOptionalParam(entry, jsonPayload));
252         //@formatter:on
253     }
254
255     private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
256         String key = params.getKey();
257         String payload = params.getValue();
258
259         switch (key) {
260             case TIMEOUT_OPT_KEY:
261                 int timeout = Integer.parseInt(payload);
262                 if (timeout < 0) {
263                     throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
264                 }
265                 jsonPayload.put(key, payload);
266                 break;
267
268             case VERSION_OPT_KEY:
269                 jsonPayload.put(key, payload);
270                 break;
271
272             case LOCAL_PARAMETERS_OPT_KEY:
273             case ENV_PARAMETERS_OPT_KEY:
274                 JSONObject paramsJson = new JSONObject(payload);
275                 jsonPayload.put(key, paramsJson);
276                 break;
277
278             case NODE_LIST_OPT_KEY:
279                 JSONArray paramsArray = new JSONArray(payload);
280                 jsonPayload.put(key, paramsArray);
281                 break;
282
283             case FILE_PARAMETERS_OPT_KEY:
284                 jsonPayload.put(key, getFilePayload(payload));
285                 break;
286
287             default:
288                 break;
289         }
290     }
291
292     /**
293      * Return payload with escaped newlines
294      */
295     private JSONObject getFilePayload(String payload) {
296         String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
297         return new JSONObject(formattedPayload);
298     }
299
300     private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
301         if (!params.containsKey(key)) {
302             throw new SvcLogicException(String.format(
303                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
304                     key));
305         }
306         if (Strings.isNullOrEmpty(params.get(key))) {
307             throw new SvcLogicException(String.format(
308                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
309                     key));
310         }
311     }
312 }