saltstack adaptor fixes from Sonar
[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
32 import com.google.common.base.Strings;
33 import org.json.JSONArray;
34 import org.codehaus.jettison.json.JSONException;
35 import org.json.JSONObject;
36 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
37 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.util.Collections;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.Map;
50 import java.util.Properties;
51 import java.util.Set;
52 import java.util.UUID;
53
54 /**
55  * Class that validates and constructs requests sent/received from
56  * Saltstack Server
57  */
58 //TODO: This class is to be altered completely based on the SALTSTACK server communicaiton.
59 public class SaltstackMessageParser {
60
61     private static final String STATUS_MESSAGE_KEY = "StatusMessage";
62     private static final String STATUS_CODE_KEY = "StatusCode";
63
64     private static final String SALTSTATE_NAME_KEY = "SaltStateName";
65     private static final String SS_AGENT_HOSTNAME_KEY = "HostName";
66     private static final String SS_AGENT_PORT_KEY = "Port";
67     private static final String PASS_KEY = "Password";
68     private static final String USER_KEY = "User";
69     private static final String CMD_EXEC = "cmd";
70     private static final String IS_SLS_EXEC = "slsExec";
71     private static final String SS_REQ_ID = "Id";
72     private static final String SLS_FILE_LOCATION = "slsFile";
73     private static final String SLS_NAME = "slsName";
74     private static final String MINION_TO_APPLY = "applyTo";
75
76     private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
77     private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
78     private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
79     private static final String NODE_LIST_OPT_KEY = "NodeList";
80     private static final String TIMEOUT_OPT_KEY = "Timeout";
81     private static final String VERSION_OPT_KEY = "Version";
82     private static final String ACTION_OPT_KEY = "Action";
83
84     private static final Logger LOGGER = LoggerFactory.getLogger(SaltstackMessageParser.class);
85
86     /**
87      * Accepts a map of strings and
88      * a) validates if all parameters are appropriate (else, throws an exception) and
89      * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
90      * <p>
91      * Mandatory parameters, that must be in the supplied information to the Saltstack Adapter
92      * 1. URL to connect to
93      * 2. credentials for URL (assume username password for now)
94      * 3. SaltState name
95      */
96     public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
97         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SALTSTATE_NAME_KEY, USER_KEY, PASS_KEY};
98         final String[] optionalTestParams = {ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
99                 TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY};
100
101         JSONObject jsonPayload = new JSONObject();
102
103         for (String key : mandatoryTestParams) {
104             throwIfMissingMandatoryParam(params, key);
105             jsonPayload.put(key, params.get(key));
106         }
107
108         parseOptionalParams(params, optionalTestParams, jsonPayload);
109
110         // Generate a unique uuid for the test
111         String reqId = UUID.randomUUID().toString();
112         jsonPayload.put(SS_AGENT_HOSTNAME_KEY, reqId);
113
114         return jsonPayload;
115     }
116
117     /**
118      * Method that validates that the Map has enough information
119      * to query Saltstack server for a result. If so, it returns
120      * the appropriate PORT number.
121      */
122     public String reqPortResult(Map<String, String> params) throws SvcLogicException {
123
124         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
125                 PASS_KEY};
126
127         for (String key : mandatoryTestParams) {
128             throwIfMissingMandatoryParam(params, key);
129         }
130         return params.get(SS_AGENT_PORT_KEY);
131     }
132
133     /**
134      * Method that validates that the Map has enough information
135      * to query Saltstack server for a result. If so, it returns
136      * the appropriate HOST name.
137      */
138     public String reqHostNameResult(Map<String, String> params) throws SvcLogicException {
139
140         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
141                 PASS_KEY};
142
143         for (String key : mandatoryTestParams) {
144             throwIfMissingMandatoryParam(params, key);
145         }
146         return params.get(SS_AGENT_HOSTNAME_KEY);
147     }
148
149     /**
150      * Method that validates that the Map has enough information
151      * to query Saltstack server for a result. If so, it returns
152      * the appropriate request ID.
153      */
154     public String reqId(Map<String, String> params) {
155
156         if (params.get(SaltstackMessageParser.SS_REQ_ID) == null) {
157             return UUID.randomUUID().toString();
158         } else if (params.get(SaltstackMessageParser.SS_REQ_ID).equalsIgnoreCase("")) {
159             return UUID.randomUUID().toString();
160         }
161         return params.get(SaltstackMessageParser.SS_REQ_ID);
162     }
163
164     /**
165      * Method that validates that the Map has enough information
166      * to query Saltstack server for a result. If so, it returns
167      * the appropriate command to execute.
168      */
169     public String reqCmd(Map<String, String> params) throws SvcLogicException {
170
171         final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
172
173         for (String key : mandatoryTestParams) {
174             throwIfMissingMandatoryParam(params, key);
175         }
176
177         return params.get(SaltstackMessageParser.CMD_EXEC);
178     }
179
180     /**
181      * Method that validates that the Map has enough information
182      * to query Saltstack server for a result. If so, it returns
183      * the appropriate SLS file location to execute.
184      */
185     public String reqSlsFile(Map<String, String> params) throws SvcLogicException {
186
187         final String[] mandatoryTestParams = {SLS_FILE_LOCATION};
188
189         for (String key : mandatoryTestParams) {
190             throwIfMissingMandatoryParam(params, key);
191         }
192
193         return params.get(SaltstackMessageParser.SLS_FILE_LOCATION);
194     }
195
196     /**
197      * Method that validates that the Map has enough information
198      * to query Saltstack server for a result. If so, it returns
199      * the appropriate SLS file location to execute.
200      */
201     public String reqSlsName(Map<String, String> params) throws SvcLogicException {
202
203         final String[] mandatoryTestParams = {SLS_NAME};
204
205         for (String key : mandatoryTestParams) {
206             throwIfMissingMandatoryParam(params, key);
207         }
208         String slsName = params.get(SaltstackMessageParser.SLS_NAME);
209         try {
210             if(slsName.substring(slsName.lastIndexOf("."), slsName.length()).equalsIgnoreCase(".sls"))
211                 return stripExtension(slsName);
212         } catch (StringIndexOutOfBoundsException e) {
213             return slsName;
214         }
215         return slsName;
216     }
217
218     private String stripExtension (String str) {
219         if (str == null) return null;
220         int pos = str.lastIndexOf(".");
221         if (pos == -1) return str;
222         return str.substring(0, pos);
223     }
224
225     /**
226      * Method that validates that the Map has enough information
227      * to query Saltstack server for a result. If so, it returns
228      * the appropriate minions/vnfc to execute the SLS file to.
229      */
230     public String reqApplyToDevices(Map<String, String> params) {
231
232         if (params.get(SaltstackMessageParser.MINION_TO_APPLY) == null) {
233             return "*";
234         } else if (params.get(SaltstackMessageParser.MINION_TO_APPLY).equalsIgnoreCase("")) {
235             return "*";
236         }
237         return params.get(SaltstackMessageParser.MINION_TO_APPLY);
238     }
239
240     /**
241      * Method that validates that the Map has enough information
242      * to query Saltstack server for a result. If so, it returns
243      * the appropriate IsSLSExec true or false.
244      */
245     public boolean reqIsSLSExec(Map<String, String> params) throws SvcLogicException {
246
247         final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
248
249         for (String key : mandatoryTestParams) {
250             throwIfMissingMandatoryParam(params, key);
251         }
252
253         return params.get(SaltstackMessageParser.IS_SLS_EXEC).equalsIgnoreCase("true");
254     }
255
256     /**
257      * Method that validates that the Map has enough information
258      * to query Saltstack server for a result. If so, it returns
259      * the appropriate Saltstack server login user name.
260      */
261     public String reqUserNameResult(Map<String, String> params) throws SvcLogicException {
262
263         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
264                 PASS_KEY};
265
266         for (String key : mandatoryTestParams) {
267             throwIfMissingMandatoryParam(params, key);
268         }
269         return params.get(USER_KEY);
270     }
271
272     /**
273      * Method that validates that the Map has enough information
274      * to query Saltstack server for a result. If so, it returns
275      * the appropriate Saltstack server login password.
276      */
277     public String reqPasswordResult(Map<String, String> params) throws SvcLogicException {
278
279         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
280                 PASS_KEY};
281
282         for (String key : mandatoryTestParams) {
283             throwIfMissingMandatoryParam(params, key);
284         }
285         return params.get(PASS_KEY);
286     }
287
288     /**
289      * This method parses response from the Saltstack Server when we do a post
290      * and returns an SaltstackResult object.
291      */
292     public SaltstackResult parseResponse(SvcLogicContext ctx, String pfx,
293                                          SaltstackResult saltstackResult, boolean slsExec) throws IOException{
294         int code = saltstackResult.getStatusCode();
295         InputStream in = null;
296         boolean executionStatus = true, retCodeFound = false;
297         if (code != SaltstackResultCodes.SUCCESS.getValue()) {
298             return saltstackResult;
299         }
300         try {
301             File file = new File(saltstackResult.getOutputFileName());
302             in = new FileInputStream(file);
303             byte[] data = new byte[(int) file.length()];
304             in.read(data);
305             String str = new String(data, "UTF-8");
306             in.close();
307             Map<String, String> mm = JsonParser.convertToProperties(str);
308             if (mm != null) {
309                 for (Map.Entry<String, String> entry : mm.entrySet()) {
310                     if (entry.getKey().contains("retcode")) {
311                         retCodeFound = true;
312                         if (!entry.getValue().equalsIgnoreCase("0")) {
313                             executionStatus = false;
314                         }
315                     }
316                     ctx.setAttribute(pfx + "." + entry.getKey(), entry.getValue());
317                     LOGGER.info("+++ " + pfx + "." + entry.getKey() + ": [" + entry.getValue() + "]");
318                 }
319             }
320         } catch (FileNotFoundException e) {
321             return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "error parsing response file "
322                     + saltstackResult.getOutputFileName() + " : " + e.getMessage());
323         } catch (JSONException e) {
324             LOGGER.info("Output not in JSON format");
325             return putToProperties(ctx, pfx, saltstackResult);
326         } catch (Exception e) {
327             return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "error parsing response file "
328                     + saltstackResult.getOutputFileName() + " : " + e.getMessage());
329         } finally {
330             if( in != null )
331                 in.close();
332         }
333         if (slsExec) {
334             if (!retCodeFound)
335                 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
336                                            "error in parsing response Json after SLS file execution in server");
337             if (!executionStatus)
338                 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
339                                            "error in parsing response Json after SLS file execution in server");
340         }
341         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
342         return saltstackResult;
343     }
344
345     public SaltstackResult putToProperties(SvcLogicContext ctx, String pfx,
346                                            SaltstackResult saltstackResult) throws IOException{
347         InputStream in = null;
348         try {
349             File file = new File(saltstackResult.getOutputFileName());
350             in = new FileInputStream(file);
351             Properties prop = new Properties();
352             prop.load(in);
353             ctx.setAttribute(pfx + "completeResult", prop.toString());
354             for (Object key : prop.keySet()) {
355                 String name = (String) key;
356                 String value = prop.getProperty(name);
357                 if (value != null && value.trim().length() > 0) {
358                     ctx.setAttribute(pfx + name, value.trim());
359                     LOGGER.info("+++ " + pfx + name + ": [" + value + "]");
360                 }
361             }
362         } catch (Exception e) {
363             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
364                     + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
365         } finally {
366             if( in != null )
367                 in.close();
368         }
369         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
370         return saltstackResult;
371     }
372
373     /**
374      * This method parses response from an Saltstack server when we do a GET for a result
375      * and returns an SaltstackResult object.
376      **/
377     public SaltstackResult parseGetResponse(String input) throws SvcLogicException {
378
379         SaltstackResult saltstackResult = new SaltstackResult();
380
381         try {
382             JSONObject postResponse = new JSONObject(input);
383             saltstackResult = parseGetResponseNested(saltstackResult, postResponse);
384         } catch (Exception e) {
385             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_COMMAND.getValue(),
386                                                   "Error parsing response = " + input + ". Error = " + e.getMessage(), "", -1);
387         }
388         return saltstackResult;
389     }
390
391     private SaltstackResult parseGetResponseNested(SaltstackResult saltstackResult, JSONObject postRsp) throws SvcLogicException {
392
393         int codeStatus = postRsp.getInt(STATUS_CODE_KEY);
394         String messageStatus = postRsp.getString(STATUS_MESSAGE_KEY);
395         int finalCode = SaltstackResultCodes.FINAL_SUCCESS.getValue();
396
397         boolean valCode =
398                 SaltstackResultCodes.CODE.checkValidCode(SaltstackResultCodes.FINALRESPONSE.getValue(), codeStatus);
399
400         if (!valCode) {
401             throw new SvcLogicException("Invalid FinalResponse code  = " + codeStatus + " received. MUST be one of "
402                                                 + SaltstackResultCodes.CODE.getValidCodes(SaltstackResultCodes.FINALRESPONSE.getValue()));
403         }
404
405         saltstackResult.setStatusCode(codeStatus);
406         saltstackResult.setStatusMessage(messageStatus);
407         LOGGER.info("Received response with code = {}, Message = {}", codeStatus, messageStatus);
408
409         if (!postRsp.isNull("Results")) {
410
411             // Results are available. process them
412             // Results is a dictionary of the form
413             // {host :{status:s, group:g, message:m, hostname:h}, ...}
414             LOGGER.info("Processing results in response");
415             JSONObject results = postRsp.getJSONObject("Results");
416             LOGGER.info("Get JSON dictionary from Results ..");
417             Iterator<String> hosts = results.keys();
418             LOGGER.info("Iterating through hosts");
419
420             while (hosts.hasNext()) {
421                 String host = hosts.next();
422                 LOGGER.info("Processing host = {}", host);
423
424                 try {
425                     JSONObject hostResponse = results.getJSONObject(host);
426                     int subCode = hostResponse.getInt(STATUS_CODE_KEY);
427                     String message = hostResponse.getString(STATUS_MESSAGE_KEY);
428
429                     LOGGER.info("Code = {}, Message = {}", subCode, message);
430
431                     if (subCode != 200 || !message.equals("SUCCESS")) {
432                         finalCode = SaltstackResultCodes.REQ_FAILURE.getValue();
433                     }
434                 } catch (Exception e) {
435                     saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
436                     saltstackResult.setStatusMessage(String.format(
437                             "Error processing response message = %s from host %s", results.getString(host), host));
438                     break;
439                 }
440             }
441
442             saltstackResult.setStatusCode(finalCode);
443
444             // We return entire Results object as message
445             saltstackResult.setResults(results.toString());
446
447         } else {
448             saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
449             saltstackResult.setStatusMessage("Results not found in GET for response");
450         }
451         return saltstackResult;
452     }
453
454     private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
455
456         Set<String> optionalParamsSet = new HashSet<>();
457         Collections.addAll(optionalParamsSet, optionalTestParams);
458
459         //@formatter:off
460         params.entrySet()
461                 .stream()
462                 .filter(entry -> optionalParamsSet.contains(entry.getKey()))
463                 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
464                 .forEach(entry -> parseOptionalParam(entry, jsonPayload));
465         //@formatter:on
466     }
467
468     private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
469         String key = params.getKey();
470         String payload = params.getValue();
471
472         switch (key) {
473             case TIMEOUT_OPT_KEY:
474                 int timeout = Integer.parseInt(payload);
475                 if (timeout < 0) {
476                     throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
477                 }
478                 jsonPayload.put(key, payload);
479                 break;
480
481             case VERSION_OPT_KEY:
482                 jsonPayload.put(key, payload);
483                 break;
484
485             case LOCAL_PARAMETERS_OPT_KEY:
486             case ENV_PARAMETERS_OPT_KEY:
487                 JSONObject paramsJson = new JSONObject(payload);
488                 jsonPayload.put(key, paramsJson);
489                 break;
490
491             case NODE_LIST_OPT_KEY:
492                 JSONArray paramsArray = new JSONArray(payload);
493                 jsonPayload.put(key, paramsArray);
494                 break;
495
496             case FILE_PARAMETERS_OPT_KEY:
497                 jsonPayload.put(key, getFilePayload(payload));
498                 break;
499
500             default:
501                 break;
502         }
503     }
504
505     /**
506      * Return payload with escaped newlines
507      */
508     private JSONObject getFilePayload(String payload) {
509         String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
510         return new JSONObject(formattedPayload);
511     }
512
513     private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
514         if (!params.containsKey(key)) {
515             throw new SvcLogicException(String.format(
516                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
517                     key));
518         }
519         if (Strings.isNullOrEmpty(params.get(key))) {
520             throw new SvcLogicException(String.format(
521                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
522                     key));
523         }
524     }
525 }