2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright (C) 2017-2018 Samsung Electronics. All rights reserved.
 
   6  * ================================================================================
 
   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.
 
  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.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;
 
  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;
 
  47 import java.util.Properties;
 
  49 import java.util.UUID;
 
  52  * Class that validates and constructs requests sent/received from
 
  55 public class SaltstackMessageParser {
 
  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";
 
  71     private static final Logger LOGGER = LoggerFactory.getLogger(SaltstackMessageParser.class);
 
  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.
 
  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) {
 
  83         return params.get(SS_AGENT_PORT_KEY);
 
  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.
 
  91     public String reqHostNameResult(Map<String, String> params) throws SvcLogicException {
 
  93         throwIfMissingMandatoryParam(params, SS_AGENT_HOSTNAME_KEY);
 
  94         return params.get(SS_AGENT_HOSTNAME_KEY);
 
  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.
 
 102     public String reqId(Map<String, String> params) {
 
 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();
 
 109         return params.get(SaltstackMessageParser.SS_REQ_ID);
 
 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.
 
 117     public String reqCmd(Map<String, String> params) throws SvcLogicException {
 
 119         throwIfMissingMandatoryParam(params, CMD_EXEC);
 
 120         return params.get(SaltstackMessageParser.CMD_EXEC);
 
 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.
 
 128     public String reqSlsFile(Map<String, String> params) throws SvcLogicException {
 
 130         throwIfMissingMandatoryParam(params, SLS_FILE_LOCATION);
 
 131         return params.get(SaltstackMessageParser.SLS_FILE_LOCATION);
 
 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.
 
 139     public String reqSlsName(Map<String, String> params) throws SvcLogicException {
 
 141         throwIfMissingMandatoryParam(params, SLS_NAME);
 
 142         String slsName = params.get(SaltstackMessageParser.SLS_NAME);
 
 144             if (slsName.substring(slsName.lastIndexOf("."), slsName.length()).equalsIgnoreCase(".sls")) {
 
 145                 return stripExtension(slsName);
 
 147         } catch (StringIndexOutOfBoundsException e) {
 
 153     private String stripExtension(String str) {
 
 157         int pos = str.lastIndexOf(".");
 
 161         return str.substring(0, pos);
 
 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.
 
 169     public String reqApplyToDevices(Map<String, String> params) {
 
 171         if (params.get(SaltstackMessageParser.MINION_TO_APPLY) == null) {
 
 173         } else if (params.get(SaltstackMessageParser.MINION_TO_APPLY).equalsIgnoreCase("")) {
 
 176         return params.get(SaltstackMessageParser.MINION_TO_APPLY);
 
 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.
 
 184     public long reqExecTimeout(Map<String, String> params) {
 
 186         if (params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY) == null) {
 
 188         } else if (params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY).equalsIgnoreCase("")) {
 
 191         return Long.parseLong(params.get(SaltstackMessageParser.EXEC_TIMEOUT_TO_APPLY));
 
 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.
 
 199     public JSONObject reqEnvParameters(Map<String, String> params) throws JSONException {
 
 201         JSONObject jsonPayload = new JSONObject();
 
 202         final String[] optionalTestParam = {SaltstackMessageParser.ENV_PARAMETERS_OPT_KEY};
 
 203         parseParam(params, optionalTestParam, jsonPayload);
 
 205         return (JSONObject) jsonPayload.remove(SaltstackMessageParser.ENV_PARAMETERS_OPT_KEY);
 
 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.
 
 213     public JSONObject reqFileParameters(Map<String, String> params) throws JSONException {
 
 215         JSONObject jsonPayload = new JSONObject();
 
 216         final String[] optionalTestParam = {SaltstackMessageParser.FILE_PARAMETERS_OPT_KEY};
 
 217         parseParam(params, optionalTestParam, jsonPayload);
 
 219         return (JSONObject) jsonPayload.remove(SaltstackMessageParser.FILE_PARAMETERS_OPT_KEY);
 
 222     private void parseParam(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload)
 
 223             throws JSONException {
 
 225         Set<String> optionalParamsSet = new HashSet<>();
 
 226         Collections.addAll(optionalParamsSet, optionalTestParams);
 
 231                 .filter(entry -> optionalParamsSet.contains(entry.getKey()))
 
 232                 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
 
 233                 .forEach(entry -> parseParam(entry, jsonPayload));
 
 237     private void parseParam(Map.Entry<String, String> params, JSONObject jsonPayload)
 
 238             throws JSONException {
 
 239         String key = params.getKey();
 
 240         String payload = params.getValue();
 
 243             case ENV_PARAMETERS_OPT_KEY:
 
 244                 JSONObject paramsJson = new JSONObject(payload);
 
 245                 jsonPayload.put(key, paramsJson);
 
 248             case FILE_PARAMETERS_OPT_KEY:
 
 249                 jsonPayload.put(key, getFilePayload(payload));
 
 258      * Return payload with escaped newlines
 
 260     private JSONObject getFilePayload(String payload) {
 
 261         String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
 
 262         return new JSONObject(formattedPayload);
 
 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.
 
 270     public boolean reqIsSLSExec(Map<String, String> params) throws SvcLogicException {
 
 272         final String[] mandatoryTestParams = {CMD_EXEC, IS_SLS_EXEC};
 
 274         for (String key : mandatoryTestParams) {
 
 275             throwIfMissingMandatoryParam(params, key);
 
 278         return params.get(SaltstackMessageParser.IS_SLS_EXEC).equalsIgnoreCase("true");
 
 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.
 
 286     public String reqUserNameResult(Map<String, String> params) throws SvcLogicException {
 
 288         throwIfMissingMandatoryParam(params, USER_KEY);
 
 289         return params.get(USER_KEY);
 
 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.
 
 297     public String reqPasswordResult(Map<String, String> params) throws SvcLogicException {
 
 299         throwIfMissingMandatoryParam(params, PASS_KEY);
 
 300         return params.get(PASS_KEY);
 
 304      * This method parses response from the Saltstack Server when we do a post
 
 305      * and returns an SaltstackResult object.
 
 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;
 
 314         ByteArrayOutputStream outStream = saltstackResult.getOutputMessage();
 
 315         String outMessage = outStream.toString();
 
 317             Map<String, String> mm = JsonParser.convertToProperties(outMessage);
 
 319                 for (Map.Entry<String, String> entry : mm.entrySet()) {
 
 320                     if (entry.getKey().contains("retcode")) {
 
 322                         if (!entry.getValue().equalsIgnoreCase("0")) {
 
 323                             executionStatus = false;
 
 326                     ctx.setAttribute(pfx + "." + entry.getKey(), entry.getValue());
 
 327                     LOGGER.info("+++ " + pfx + "." + entry.getKey() + ": [" + entry.getValue() + "]");
 
 330         } catch (org.codehaus.jettison.json.JSONException e) {
 
 332                 return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE.getValue(), "error parsing response file"
 
 333                         + " : Output has to be in JSON format");
 
 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());
 
 341             if (outStream != null) {
 
 347                 if (outMessage != null && !outMessage.equalsIgnoreCase("")) {
 
 348                     return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
 
 351                 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
 
 352                                            "error in executing configuration at the server, check your command input");
 
 354             if (!executionStatus) {
 
 355                 if (outMessage != null && !outMessage.equalsIgnoreCase("")) {
 
 356                     return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
 
 359                 return new SaltstackResult(SaltstackResultCodes.COMMAND_EXEC_FAILED_STATUS.getValue(),
 
 360                                            "error in executing configuration at the server, check your command input");
 
 363         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
 
 364         return saltstackResult;
 
 367     public SaltstackResult putToProperties(SvcLogicContext ctx, String pfx,
 
 368                                            SaltstackResult saltstackResult) throws IOException {
 
 370         ByteArrayOutputStream buffer = saltstackResult.getOutputMessage();
 
 371         InputStream inputStream = null;
 
 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 + "]");
 
 386         } catch (Exception e) {
 
 387             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file." +
 
 388                     " Error = " + e.getMessage());
 
 390             if (buffer != null && inputStream != null) {
 
 395         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
 
 396         return saltstackResult;
 
 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 !",
 
 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 !",