3095fca9b88a8e5d4ca263665c25aadca2fc8352
[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 : CCSDK
4  * ================================================================================
5  * Copyright (C) 2017-2018 Samsung Electronics. All rights reserved.
6  * ================================================================================
7  *
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  *
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.JSONException;
34 import org.json.JSONArray;
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.Map;
49 import java.util.Properties;
50 import java.util.Set;
51 import java.util.UUID;
52
53 /**
54  * Class that validates and constructs requests sent/received from
55  * Saltstack Server
56  */
57 public class SaltstackMessageParser {
58
59     private static final String SS_AGENT_HOSTNAME_KEY = "HostName";
60     private static final String SS_AGENT_PORT_KEY = "Port";
61     private static final String PASS_KEY = "Password";
62     private static final String USER_KEY = "User";
63     private static final String CMD_EXEC = "Cmd"; //cmd
64     private static final String IS_SLS_EXEC = "SlsExec"; //slsExec
65     private static final String SS_REQ_ID = "Id";
66     private static final String SLS_FILE_LOCATION = "SlsFile"; //slsFile
67     private static final String SLS_NAME = "SlsName"; //slsName
68     private static final String MINION_TO_APPLY = "NodeList"; //applyTo
69     private static final String EXEC_TIMEOUT_TO_APPLY = "Timeout"; //execTimeout
70     private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
71     private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
72
73     private static final Logger LOGGER = LoggerFactory.getLogger(SaltstackMessageParser.class);
74
75     /**
76      * Method that validates that the Map has enough information
77      * to query Saltstack server for a result. If so, it returns
78      * the appropriate PORT number.
79      */
80     public String reqPortResult(Map<String, String> params) throws SvcLogicException {
81
82         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
83                 PASS_KEY};
84
85         for (String key : mandatoryTestParams) {
86             throwIfMissingMandatoryParam(params, key);
87         }
88         return params.get(SS_AGENT_PORT_KEY);
89     }
90
91     /**
92      * Method that validates that the Map has enough information
93      * to query Saltstack server for a result. If so, it returns
94      * the appropriate HOST name.
95      */
96     public String reqHostNameResult(Map<String, String> params) throws SvcLogicException {
97
98         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
99                 PASS_KEY};
100
101         for (String key : mandatoryTestParams) {
102             throwIfMissingMandatoryParam(params, key);
103         }
104         return params.get(SS_AGENT_HOSTNAME_KEY);
105     }
106
107     /**
108      * Method that validates that the Map has enough information
109      * to query Saltstack server for a result. If so, it returns
110      * the appropriate request ID.
111      */
112     public String reqId(Map<String, String> params) {
113
114         if (params.get(SaltstackMessageParser.SS_REQ_ID) == null) {
115             return UUID.randomUUID().toString();
116         } else if (params.get(SaltstackMessageParser.SS_REQ_ID).equalsIgnoreCase("")) {
117             return UUID.randomUUID().toString();
118         }
119         return params.get(SaltstackMessageParser.SS_REQ_ID);
120     }
121
122     /**
123      * Method that validates that the Map has enough information
124      * to query Saltstack server for a result. If so, it returns
125      * the appropriate command to execute.
126      */
127     public String reqCmd(Map<String, String> params) throws SvcLogicException {
128
129         final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
130
131         for (String key : mandatoryTestParams) {
132             throwIfMissingMandatoryParam(params, key);
133         }
134
135         return params.get(SaltstackMessageParser.CMD_EXEC);
136     }
137
138     /**
139      * Method that validates that the Map has enough information
140      * to query Saltstack server for a result. If so, it returns
141      * the appropriate SLS file location to execute.
142      */
143     public String reqSlsFile(Map<String, String> params) throws SvcLogicException {
144
145         final String[] mandatoryTestParams = {SLS_FILE_LOCATION};
146
147         for (String key : mandatoryTestParams) {
148             throwIfMissingMandatoryParam(params, key);
149         }
150
151         return params.get(SaltstackMessageParser.SLS_FILE_LOCATION);
152     }
153
154     /**
155      * Method that validates that the Map has enough information
156      * to query Saltstack server for a result. If so, it returns
157      * the appropriate SLS file location to execute.
158      */
159     public String reqSlsName(Map<String, String> params) throws SvcLogicException {
160
161         final String[] mandatoryTestParams = {SLS_NAME};
162
163         for (String key : mandatoryTestParams) {
164             throwIfMissingMandatoryParam(params, key);
165         }
166         String slsName = params.get(SaltstackMessageParser.SLS_NAME);
167         try {
168             if (slsName.substring(slsName.lastIndexOf("."), slsName.length()).equalsIgnoreCase(".sls")) {
169                 return stripExtension(slsName);
170             }
171         } catch (StringIndexOutOfBoundsException e) {
172             return slsName;
173         }
174         return slsName;
175     }
176
177     private String stripExtension(String str) {
178         if (str == null) {
179             return null;
180         }
181         int pos = str.lastIndexOf(".");
182         if (pos == -1) {
183             return str;
184         }
185         return str.substring(0, pos);
186     }
187
188     /**
189      * Method that validates that the Map has enough information
190      * to query Saltstack server for a result. If so, it returns
191      * the appropriate minions/vnfc to execute the SLS file.
192      */
193     public String reqApplyToDevices(Map<String, String> params) {
194
195         if (params.get(SaltstackMessageParser.MINION_TO_APPLY) == null) {
196             return "*";
197         } else if (params.get(SaltstackMessageParser.MINION_TO_APPLY).equalsIgnoreCase("")) {
198             return "*";
199         }
200         return params.get(SaltstackMessageParser.MINION_TO_APPLY);
201     }
202
203     /**
204      * Method that validates that the Map has enough information
205      * to query Saltstack server for a result. If so, it returns
206      * the appropriate minions/vnfc to execute the SLS file.
207      */
208     public long reqExecTimeout(Map<String, String> params) {
209
210         if (params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY) == null) {
211             return -1;
212         } else if (params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY).equalsIgnoreCase("")) {
213             return -1;
214         }
215         return Long.parseLong(params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY));
216     }
217
218     /**
219      * Method that validates that the Map has enough information
220      * to query Saltstack server for a result. If so, it returns
221      * the appropriate EnvParameters to execute the SLS file.
222      */
223     public JSONObject reqEnvParameters(Map<String, String> params) throws JSONException {
224
225         JSONObject jsonPayload = new JSONObject();
226         final String[] optionalTestParam = { SaltstackMessageParser.ENV_PARAMETERS_OPT_KEY };
227         parseParam(params, optionalTestParam, jsonPayload);
228
229         return (JSONObject) jsonPayload.remove(SaltstackMessageParser.ENV_PARAMETERS_OPT_KEY);
230     }
231
232     /**
233      * Method that validates that the Map has enough information
234      * to query Saltstack server for a result. If so, it returns
235      * the appropriate EnvParameters to execute the SLS file.
236      */
237     public JSONObject reqFileParameters(Map<String, String> params) throws JSONException {
238
239         JSONObject jsonPayload = new JSONObject();
240         final String[] optionalTestParam = { SaltstackMessageParser.FILE_PARAMETERS_OPT_KEY };
241         parseParam(params, optionalTestParam, jsonPayload);
242
243         return (JSONObject) jsonPayload.remove(SaltstackMessageParser.FILE_PARAMETERS_OPT_KEY);
244     }
245
246     private void parseParam(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload)
247             throws JSONException {
248
249         Set<String> optionalParamsSet = new HashSet<>();
250         Collections.addAll(optionalParamsSet, optionalTestParams);
251
252         //@formatter:off
253         params.entrySet()
254                 .stream()
255                 .filter(entry -> optionalParamsSet.contains(entry.getKey()))
256                 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
257                 .forEach(entry -> parseParam(entry, jsonPayload));
258         //@formatter:on
259     }
260
261     private void parseParam(Map.Entry<String, String> params, JSONObject jsonPayload)
262             throws JSONException {
263         String key = params.getKey();
264         String payload = params.getValue();
265
266         switch (key) {
267             case ENV_PARAMETERS_OPT_KEY:
268                 JSONObject paramsJson = new JSONObject(payload);
269                 jsonPayload.put(key, paramsJson);
270                 break;
271
272             case FILE_PARAMETERS_OPT_KEY:
273                 jsonPayload.put(key, getFilePayload(payload));
274                 break;
275
276             default:
277                 break;
278         }
279     }
280
281     /**
282      * Return payload with escaped newlines
283      */
284     private JSONObject getFilePayload(String payload) {
285         String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
286         return new JSONObject(formattedPayload);
287     }
288
289     /**
290      * Method that validates that the Map has enough information
291      * to query Saltstack server for a result. If so, it returns
292      * the appropriate IsSLSExec true or false.
293      */
294     public boolean reqIsSLSExec(Map<String, String> params) throws SvcLogicException {
295
296         final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
297
298         for (String key : mandatoryTestParams) {
299             throwIfMissingMandatoryParam(params, key);
300         }
301
302         return params.get(SaltstackMessageParser.IS_SLS_EXEC).equalsIgnoreCase("true");
303     }
304
305     /**
306      * Method that validates that the Map has enough information
307      * to query Saltstack server for a result. If so, it returns
308      * the appropriate Saltstack server login user name.
309      */
310     public String reqUserNameResult(Map<String, String> params) throws SvcLogicException {
311
312         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
313                 PASS_KEY};
314
315         for (String key : mandatoryTestParams) {
316             throwIfMissingMandatoryParam(params, key);
317         }
318         return params.get(USER_KEY);
319     }
320
321     /**
322      * Method that validates that the Map has enough information
323      * to query Saltstack server for a result. If so, it returns
324      * the appropriate Saltstack server login password.
325      */
326     public String reqPasswordResult(Map<String, String> params) throws SvcLogicException {
327
328         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
329                 PASS_KEY};
330
331         for (String key : mandatoryTestParams) {
332             throwIfMissingMandatoryParam(params, key);
333         }
334         return params.get(PASS_KEY);
335     }
336
337     /**
338      * This method parses response from the Saltstack Server when we do a post
339      * and returns an SaltstackResult object.
340      */
341     public SaltstackResult parseResponse(SvcLogicContext ctx, String pfx,
342                                          SaltstackResult saltstackResult, boolean slsExec) throws IOException {
343         int code = saltstackResult.getStatusCode();
344         InputStream in = null;
345         boolean executionStatus = true, retCodeFound = false;
346         if (code != SaltstackResultCodes.SUCCESS.getValue()) {
347             return saltstackResult;
348         }
349         try {
350             File file = new File(saltstackResult.getOutputFileName());
351             in = new FileInputStream(file);
352             byte[] data = new byte[(int) file.length()];
353             in.read(data);
354             String str = new String(data, "UTF-8");
355             in.close();
356             Map<String, String> mm = JsonParser.convertToProperties(str);
357             if (mm != null) {
358                 for (Map.Entry<String, String> entry : mm.entrySet()) {
359                     if (entry.getKey().contains("retcode")) {
360                         retCodeFound = true;
361                         if (!entry.getValue().equalsIgnoreCase("0")) {
362                             executionStatus = false;
363                         }
364                     }
365                     ctx.setAttribute(pfx + "." + entry.getKey(), entry.getValue());
366                     LOGGER.info("+++ " + pfx + "." + entry.getKey() + ": [" + entry.getValue() + "]");
367                 }
368             }
369         } catch (FileNotFoundException e) {
370             return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "error parsing response file "
371                     + saltstackResult.getOutputFileName() + " : " + e.getMessage());
372         } catch (org.codehaus.jettison.json.JSONException e) {
373             LOGGER.info("Output not in JSON format");
374             return putToProperties(ctx, pfx, saltstackResult);
375         } catch (Exception e) {
376             return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "error parsing response file "
377                     + saltstackResult.getOutputFileName() + " : " + e.getMessage());
378         } finally {
379             if (in != null) {
380                 in.close();
381             }
382         }
383         if (slsExec) {
384             if (!retCodeFound) {
385                 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
386                                            "error in executing configuration at the server, check your command input");
387             }
388             if (!executionStatus) {
389                 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
390                                            "error in executing configuration at the server, check your command input");
391             }
392         }
393         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
394         return saltstackResult;
395     }
396
397     public SaltstackResult putToProperties(SvcLogicContext ctx, String pfx,
398                                            SaltstackResult saltstackResult) throws IOException {
399         InputStream in = null;
400         try {
401             File file = new File(saltstackResult.getOutputFileName());
402             in = new FileInputStream(file);
403             Properties prop = new Properties();
404             prop.load(in);
405             ctx.setAttribute(pfx + "completeResult", prop.toString());
406             for (Object key : prop.keySet()) {
407                 String name = (String) key;
408                 String value = prop.getProperty(name);
409                 if (value != null && value.trim().length() > 0) {
410                     ctx.setAttribute(pfx + "." + name, value.trim());
411                     LOGGER.info("+++ " + pfx + "." + name + ": [" + value + "]");
412                 }
413             }
414         } catch (Exception e) {
415             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
416                     + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
417         } finally {
418             if (in != null) {
419                 in.close();
420             }
421         }
422         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
423         return saltstackResult;
424     }
425
426     private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
427         if (!params.containsKey(key)) {
428             throw new SvcLogicException(String.format(
429                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
430                     key));
431         }
432         if (Strings.isNullOrEmpty(params.get(key))) {
433             throw new SvcLogicException(String.format(
434                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
435                     key));
436         }
437     }
438 }