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