2 * ============LICENSE_START=======================================================
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22 * ============LICENSE_END=========================================================
25 package org.onap.ccsdk.sli.adaptors.saltstack.model;
28 * This module implements the APP-C/Saltstack Server interface
29 * based on the REST API specifications
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;
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;
50 import java.util.Properties;
52 import java.util.UUID;
55 * Class that validates and constructs requests sent/received from
58 //TODO: This class is to be altered completely based on the SALTSTACK server communicaiton.
59 public class SaltstackMessageParser {
61 private static final String STATUS_MESSAGE_KEY = "StatusMessage";
62 private static final String STATUS_CODE_KEY = "StatusCode";
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 private static final String EXEC_TIMEOUT_TO_APPLY = "execTimeout";
77 private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
78 private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
79 private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
80 private static final String NODE_LIST_OPT_KEY = "NodeList";
81 private static final String TIMEOUT_OPT_KEY = "Timeout";
82 private static final String VERSION_OPT_KEY = "Version";
83 private static final String ACTION_OPT_KEY = "Action";
85 private static final Logger LOGGER = LoggerFactory.getLogger(SaltstackMessageParser.class);
88 * Accepts a map of strings and
89 * a) validates if all parameters are appropriate (else, throws an exception) and
90 * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
92 * Mandatory parameters, that must be in the supplied information to the Saltstack Adapter
93 * 1. URL to connect to
94 * 2. credentials for URL (assume username password for now)
97 public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
98 final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SALTSTATE_NAME_KEY, USER_KEY, PASS_KEY};
99 final String[] optionalTestParams = {ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
100 TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY};
102 JSONObject jsonPayload = new JSONObject();
104 for (String key : mandatoryTestParams) {
105 throwIfMissingMandatoryParam(params, key);
106 jsonPayload.put(key, params.get(key));
109 parseOptionalParams(params, optionalTestParams, jsonPayload);
111 // Generate a unique uuid for the test
112 String reqId = UUID.randomUUID().toString();
113 jsonPayload.put(SS_AGENT_HOSTNAME_KEY, reqId);
119 * Method that validates that the Map has enough information
120 * to query Saltstack server for a result. If so, it returns
121 * the appropriate PORT number.
123 public String reqPortResult(Map<String, String> params) throws SvcLogicException {
125 final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
128 for (String key : mandatoryTestParams) {
129 throwIfMissingMandatoryParam(params, key);
131 return params.get(SS_AGENT_PORT_KEY);
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 HOST name.
139 public String reqHostNameResult(Map<String, String> params) throws SvcLogicException {
141 final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
144 for (String key : mandatoryTestParams) {
145 throwIfMissingMandatoryParam(params, key);
147 return params.get(SS_AGENT_HOSTNAME_KEY);
151 * Method that validates that the Map has enough information
152 * to query Saltstack server for a result. If so, it returns
153 * the appropriate request ID.
155 public String reqId(Map<String, String> params) {
157 if (params.get(SaltstackMessageParser.SS_REQ_ID) == null) {
158 return UUID.randomUUID().toString();
159 } else if (params.get(SaltstackMessageParser.SS_REQ_ID).equalsIgnoreCase("")) {
160 return UUID.randomUUID().toString();
162 return params.get(SaltstackMessageParser.SS_REQ_ID);
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 command to execute.
170 public String reqCmd(Map<String, String> params) throws SvcLogicException {
172 final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
174 for (String key : mandatoryTestParams) {
175 throwIfMissingMandatoryParam(params, key);
178 return params.get(SaltstackMessageParser.CMD_EXEC);
182 * Method that validates that the Map has enough information
183 * to query Saltstack server for a result. If so, it returns
184 * the appropriate SLS file location to execute.
186 public String reqSlsFile(Map<String, String> params) throws SvcLogicException {
188 final String[] mandatoryTestParams = {SLS_FILE_LOCATION};
190 for (String key : mandatoryTestParams) {
191 throwIfMissingMandatoryParam(params, key);
194 return params.get(SaltstackMessageParser.SLS_FILE_LOCATION);
198 * Method that validates that the Map has enough information
199 * to query Saltstack server for a result. If so, it returns
200 * the appropriate SLS file location to execute.
202 public String reqSlsName(Map<String, String> params) throws SvcLogicException {
204 final String[] mandatoryTestParams = {SLS_NAME};
206 for (String key : mandatoryTestParams) {
207 throwIfMissingMandatoryParam(params, key);
209 String slsName = params.get(SaltstackMessageParser.SLS_NAME);
211 if(slsName.substring(slsName.lastIndexOf("."), slsName.length()).equalsIgnoreCase(".sls"))
212 return stripExtension(slsName);
213 } catch (StringIndexOutOfBoundsException e) {
219 private String stripExtension (String str) {
220 if (str == null) return null;
221 int pos = str.lastIndexOf(".");
222 if (pos == -1) return str;
223 return str.substring(0, pos);
227 * Method that validates that the Map has enough information
228 * to query Saltstack server for a result. If so, it returns
229 * the appropriate minions/vnfc to execute the SLS file to.
231 public String reqApplyToDevices(Map<String, String> params) {
233 if (params.get(SaltstackMessageParser.MINION_TO_APPLY) == null) {
235 } else if (params.get(SaltstackMessageParser.MINION_TO_APPLY).equalsIgnoreCase("")) {
238 return params.get(SaltstackMessageParser.MINION_TO_APPLY);
242 * Method that validates that the Map has enough information
243 * to query Saltstack server for a result. If so, it returns
244 * the appropriate minions/vnfc to execute the SLS file to.
246 public long reqExecTimeout(Map<String, String> params) {
248 if (params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY) == null) {
250 } else if (params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY).equalsIgnoreCase("")) {
253 return Long.parseLong(params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY));
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 IsSLSExec true or false.
261 public boolean reqIsSLSExec(Map<String, String> params) throws SvcLogicException {
263 final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
265 for (String key : mandatoryTestParams) {
266 throwIfMissingMandatoryParam(params, key);
269 return params.get(SaltstackMessageParser.IS_SLS_EXEC).equalsIgnoreCase("true");
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 user name.
277 public String reqUserNameResult(Map<String, String> params) throws SvcLogicException {
279 final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
282 for (String key : mandatoryTestParams) {
283 throwIfMissingMandatoryParam(params, key);
285 return params.get(USER_KEY);
289 * Method that validates that the Map has enough information
290 * to query Saltstack server for a result. If so, it returns
291 * the appropriate Saltstack server login password.
293 public String reqPasswordResult(Map<String, String> params) throws SvcLogicException {
295 final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY,
298 for (String key : mandatoryTestParams) {
299 throwIfMissingMandatoryParam(params, key);
301 return params.get(PASS_KEY);
305 * This method parses response from the Saltstack Server when we do a post
306 * and returns an SaltstackResult object.
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;
317 File file = new File(saltstackResult.getOutputFileName());
318 in = new FileInputStream(file);
319 byte[] data = new byte[(int) file.length()];
321 String str = new String(data, "UTF-8");
323 Map<String, String> mm = JsonParser.convertToProperties(str);
325 for (Map.Entry<String, String> entry : mm.entrySet()) {
326 if (entry.getKey().contains("retcode")) {
328 if (!entry.getValue().equalsIgnoreCase("0")) {
329 executionStatus = false;
332 ctx.setAttribute(pfx + "." + entry.getKey(), entry.getValue());
333 LOGGER.info("+++ " + pfx + "." + entry.getKey() + ": [" + entry.getValue() + "]");
336 } catch (FileNotFoundException e) {
337 return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "error parsing response file "
338 + saltstackResult.getOutputFileName() + " : " + e.getMessage());
339 } catch (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());
351 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
352 "error in executing configuration at the server");
353 if (!executionStatus)
354 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
355 "error in executing configuration at the server");
357 saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
358 return saltstackResult;
361 public SaltstackResult putToProperties(SvcLogicContext ctx, String pfx,
362 SaltstackResult saltstackResult) throws IOException{
363 InputStream in = null;
365 File file = new File(saltstackResult.getOutputFileName());
366 in = new FileInputStream(file);
367 Properties prop = new Properties();
369 ctx.setAttribute(pfx + "completeResult", prop.toString());
370 for (Object key : prop.keySet()) {
371 String name = (String) key;
372 String value = prop.getProperty(name);
373 if (value != null && value.trim().length() > 0) {
374 ctx.setAttribute(pfx + "." + name, value.trim());
375 LOGGER.info("+++ " + pfx + "." + name + ": [" + value + "]");
378 } catch (Exception e) {
379 saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
380 + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
385 saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
386 return saltstackResult;
390 * This method parses response from an Saltstack server when we do a GET for a result
391 * and returns an SaltstackResult object.
393 public SaltstackResult parseGetResponse(String input) throws SvcLogicException {
395 SaltstackResult saltstackResult = new SaltstackResult();
398 JSONObject postResponse = new JSONObject(input);
399 saltstackResult = parseGetResponseNested(saltstackResult, postResponse);
400 } catch (Exception e) {
401 saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_COMMAND.getValue(),
402 "Error parsing response = " + input + ". Error = " + e.getMessage(), "", -1);
404 return saltstackResult;
407 private SaltstackResult parseGetResponseNested(SaltstackResult saltstackResult, JSONObject postRsp) throws SvcLogicException {
409 int codeStatus = postRsp.getInt(STATUS_CODE_KEY);
410 String messageStatus = postRsp.getString(STATUS_MESSAGE_KEY);
411 int finalCode = SaltstackResultCodes.FINAL_SUCCESS.getValue();
414 SaltstackResultCodes.CODE.checkValidCode(SaltstackResultCodes.FINALRESPONSE.getValue(), codeStatus);
417 throw new SvcLogicException("Invalid FinalResponse code = " + codeStatus + " received. MUST be one of "
418 + SaltstackResultCodes.CODE.getValidCodes(SaltstackResultCodes.FINALRESPONSE.getValue()));
421 saltstackResult.setStatusCode(codeStatus);
422 saltstackResult.setStatusMessage(messageStatus);
423 LOGGER.info("Received response with code = {}, Message = {}", codeStatus, messageStatus);
425 if (!postRsp.isNull("Results")) {
427 // Results are available. process them
428 // Results is a dictionary of the form
429 // {host :{status:s, group:g, message:m, hostname:h}, ...}
430 LOGGER.info("Processing results in response");
431 JSONObject results = postRsp.getJSONObject("Results");
432 LOGGER.info("Get JSON dictionary from Results ..");
433 Iterator<String> hosts = results.keys();
434 LOGGER.info("Iterating through hosts");
436 while (hosts.hasNext()) {
437 String host = hosts.next();
438 LOGGER.info("Processing host = {}", host);
441 JSONObject hostResponse = results.getJSONObject(host);
442 int subCode = hostResponse.getInt(STATUS_CODE_KEY);
443 String message = hostResponse.getString(STATUS_MESSAGE_KEY);
445 LOGGER.info("Code = {}, Message = {}", subCode, message);
447 if (subCode != 200 || !message.equals("SUCCESS")) {
448 finalCode = SaltstackResultCodes.REQ_FAILURE.getValue();
450 } catch (Exception e) {
451 saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
452 saltstackResult.setStatusMessage(String.format(
453 "Error processing response message = %s from host %s", results.getString(host), host));
458 saltstackResult.setStatusCode(finalCode);
460 // We return entire Results object as message
461 saltstackResult.setResults(results.toString());
464 saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
465 saltstackResult.setStatusMessage("Results not found in GET for response");
467 return saltstackResult;
470 private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
472 Set<String> optionalParamsSet = new HashSet<>();
473 Collections.addAll(optionalParamsSet, optionalTestParams);
478 .filter(entry -> optionalParamsSet.contains(entry.getKey()))
479 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
480 .forEach(entry -> parseOptionalParam(entry, jsonPayload));
484 private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
485 String key = params.getKey();
486 String payload = params.getValue();
489 case TIMEOUT_OPT_KEY:
490 int timeout = Integer.parseInt(payload);
492 throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
494 jsonPayload.put(key, payload);
497 case VERSION_OPT_KEY:
498 jsonPayload.put(key, payload);
501 case LOCAL_PARAMETERS_OPT_KEY:
502 case ENV_PARAMETERS_OPT_KEY:
503 JSONObject paramsJson = new JSONObject(payload);
504 jsonPayload.put(key, paramsJson);
507 case NODE_LIST_OPT_KEY:
508 JSONArray paramsArray = new JSONArray(payload);
509 jsonPayload.put(key, paramsArray);
512 case FILE_PARAMETERS_OPT_KEY:
513 jsonPayload.put(key, getFilePayload(payload));
522 * Return payload with escaped newlines
524 private JSONObject getFilePayload(String payload) {
525 String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
526 return new JSONObject(formattedPayload);
529 private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
530 if (!params.containsKey(key)) {
531 throw new SvcLogicException(String.format(
532 "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
535 if (Strings.isNullOrEmpty(params.get(key))) {
536 throw new SvcLogicException(String.format(
537 "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",