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