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 java.io.FileInputStream;
 
  33 import java.io.FileNotFoundException;
 
  34 import java.io.InputStream;
 
  35 import java.util.Collections;
 
  36 import java.util.HashSet;
 
  37 import java.util.Iterator;
 
  39 import java.util.Properties;
 
  41 import java.util.UUID;
 
  42 import org.json.JSONArray;
 
  43 import org.json.JSONException;
 
  44 import org.json.JSONObject;
 
  45 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
 
  46 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
 
  47 import com.google.common.base.Strings;
 
  48 import org.slf4j.Logger;
 
  49 import org.slf4j.LoggerFactory;
 
  52  * Class that validates and constructs requests sent/received from
 
  55 //TODO: This class is to be altered completely based on the SALTSTACK server communicaiton.
 
  56 public class SaltstackMessageParser {
 
  58     private static final String STATUS_MESSAGE_KEY = "StatusMessage";
 
  59     private static final String STATUS_CODE_KEY = "StatusCode";
 
  61     private static final String SALTSTATE_NAME_KEY = "SaltStateName";
 
  62     private static final String SS_AGENT_HOSTNAME_KEY = "HostName";
 
  63     private static final String SS_AGENT_PORT_KEY = "Port";
 
  64     private static final String PASS_KEY = "Password";
 
  65     private static final String USER_KEY = "User";
 
  67     private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
 
  68     private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
 
  69     private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
 
  70     private static final String NODE_LIST_OPT_KEY = "NodeList";
 
  71     private static final String TIMEOUT_OPT_KEY = "Timeout";
 
  72     private static final String VERSION_OPT_KEY = "Version";
 
  73     private static final String ACTION_OPT_KEY = "Action";
 
  75     private static final Logger LOGGER = LoggerFactory.getLogger(SaltstackMessageParser.class);
 
  78      * Accepts a map of strings and
 
  79      * a) validates if all parameters are appropriate (else, throws an exception) and
 
  80      * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
 
  82      * Mandatory parameters, that must be in the supplied information to the Saltstack Adapter
 
  83      * 1. URL to connect to
 
  84      * 2. credentials for URL (assume username password for now)
 
  88     public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
 
  89         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SALTSTATE_NAME_KEY, USER_KEY, PASS_KEY};
 
  90         final String[] optionalTestParams = {ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
 
  91                 TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY};
 
  93         JSONObject jsonPayload = new JSONObject();
 
  95         for (String key : mandatoryTestParams) {
 
  96             throwIfMissingMandatoryParam(params, key);
 
  97             jsonPayload.put(key, params.get(key));
 
 100         parseOptionalParams(params, optionalTestParams, jsonPayload);
 
 102         // Generate a unique uuid for the test
 
 103         String reqId = UUID.randomUUID().toString();
 
 104         jsonPayload.put(SS_AGENT_HOSTNAME_KEY, reqId);
 
 110      * Method that validates that the Map has enough information
 
 111      * to query Saltstack server for a result. If so, it returns
 
 112      * the appropriate PORT number.
 
 114     public String reqPortResult(Map<String, String> params) throws SvcLogicException {
 
 116         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
 
 118         for (String key : mandatoryTestParams) {
 
 119             throwIfMissingMandatoryParam(params, key);
 
 121         return params.get(SS_AGENT_PORT_KEY);
 
 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 HOST name.
 
 129     public String reqHostNameResult(Map<String, String> params) throws SvcLogicException {
 
 131         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
 
 133         for (String key : mandatoryTestParams) {
 
 134             throwIfMissingMandatoryParam(params, key);
 
 136         return params.get(SS_AGENT_HOSTNAME_KEY);
 
 140      * Method that validates that the Map has enough information
 
 141      * to query Saltstack server for a result. If so, it returns
 
 142      * the appropriate Saltstack server login user name.
 
 144     public String reqUserNameResult(Map<String, String> params) throws SvcLogicException {
 
 146         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
 
 148         for (String key : mandatoryTestParams) {
 
 149             throwIfMissingMandatoryParam(params, key);
 
 151         return params.get(USER_KEY);
 
 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 Saltstack server login password.
 
 159     public String reqPasswordResult(Map<String, String> params) throws SvcLogicException {
 
 161         final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
 
 163         for (String key : mandatoryTestParams) {
 
 164             throwIfMissingMandatoryParam(params, key);
 
 166         return params.get(PASS_KEY);
 
 170      * This method parses response from the Saltstack Server when we do a post
 
 171      * and returns an SaltstackResult object.
 
 173     public SaltstackResult parseResponse(SvcLogicContext ctx, String pfx, SaltstackResult saltstackResult) {
 
 174         int code = saltstackResult.getStatusCode();
 
 175         if (code != SaltstackResultCodes.SUCCESS.getValue()) {
 
 176             return saltstackResult;
 
 179             File file = new File(saltstackResult.getOutputFileName());
 
 180             InputStream in = new FileInputStream(file);
 
 181             byte[] data = new byte[(int) file.length()];
 
 183             String str = new String(data, "UTF-8");
 
 185             Map<String, String> mm = JsonParser.convertToProperties(str);
 
 187                 for (Map.Entry<String,String> entry : mm.entrySet()) {
 
 188                     ctx.setAttribute(pfx + entry.getKey(), entry.getValue());
 
 189                     LOGGER.info("+++ " + pfx + entry.getKey() + ": [" + entry.getValue() + "]");
 
 192         } catch (FileNotFoundException e){
 
 193             return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
 
 194                     + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
 
 195         } catch (JSONException e) {
 
 196             LOGGER.info("Output not in JSON format");
 
 197             return putToProperties(ctx, pfx, saltstackResult);
 
 198         } catch (Exception e) {
 
 199             return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
 
 200                     + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
 
 202         saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
 
 203         return saltstackResult;
 
 206     public SaltstackResult putToProperties(SvcLogicContext ctx, String pfx, SaltstackResult saltstackResult) {
 
 208             File file = new File(saltstackResult.getOutputFileName());
 
 209             InputStream in = new FileInputStream(file);
 
 210             Properties prop = new Properties();
 
 212             ctx.setAttribute(pfx + "completeResult", prop.toString());
 
 213             for (Object key : prop.keySet()) {
 
 214                 String name = (String) key;
 
 215                 String value = prop.getProperty(name);
 
 216                 if (value != null && value.trim().length() > 0) {
 
 217                     ctx.setAttribute(pfx + name, value.trim());
 
 218                     LOGGER.info("+++ " + pfx + name + ": [" + value + "]");
 
 221         } catch (Exception e) {
 
 222             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
 
 223                     + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
 
 225         return saltstackResult;
 
 228      * This method parses response from an Saltstack server when we do a GET for a result
 
 229      * and returns an SaltstackResult object.
 
 231     public SaltstackResult parseGetResponse(String input) throws SvcLogicException {
 
 233         SaltstackResult saltstackResult = new SaltstackResult();
 
 236             JSONObject postResponse = new JSONObject(input);
 
 237             saltstackResult = parseGetResponseNested(saltstackResult, postResponse);
 
 238         } catch (JSONException e) {
 
 239             saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_COMMAND.getValue(),
 
 240                     "Error parsing response = " + input + ". Error = " + e.getMessage(), "", -1);
 
 242         return saltstackResult;
 
 245     private SaltstackResult parseGetResponseNested(SaltstackResult saltstackResult, JSONObject postRsp) throws SvcLogicException  {
 
 247         int codeStatus = postRsp.getInt(STATUS_CODE_KEY);
 
 248         String messageStatus = postRsp.getString(STATUS_MESSAGE_KEY);
 
 249         int finalCode = SaltstackResultCodes.FINAL_SUCCESS.getValue();
 
 252                 SaltstackResultCodes.CODE.checkValidCode(SaltstackResultCodes.FINALRESPONSE.getValue(), codeStatus);
 
 255             throw new SvcLogicException("Invalid FinalResponse code  = " + codeStatus + " received. MUST be one of "
 
 256                     + SaltstackResultCodes.CODE.getValidCodes(SaltstackResultCodes.FINALRESPONSE.getValue()));
 
 259         saltstackResult.setStatusCode(codeStatus);
 
 260         saltstackResult.setStatusMessage(messageStatus);
 
 261         LOGGER.info("Received response with code = {}, Message = {}", codeStatus, messageStatus);
 
 263         if (!postRsp.isNull("Results")) {
 
 265             // Results are available. process them
 
 266             // Results is a dictionary of the form
 
 267             // {host :{status:s, group:g, message:m, hostname:h}, ...}
 
 268             LOGGER.info("Processing results in response");
 
 269             JSONObject results = postRsp.getJSONObject("Results");
 
 270             LOGGER.info("Get JSON dictionary from Results ..");
 
 271             Iterator<String> hosts = results.keys();
 
 272             LOGGER.info("Iterating through hosts");
 
 274             while (hosts.hasNext()) {
 
 275                 String host = hosts.next();
 
 276                 LOGGER.info("Processing host = {}", host);
 
 279                     JSONObject hostResponse = results.getJSONObject(host);
 
 280                     int subCode = hostResponse.getInt(STATUS_CODE_KEY);
 
 281                     String message = hostResponse.getString(STATUS_MESSAGE_KEY);
 
 283                     LOGGER.info("Code = {}, Message = {}", subCode, message);
 
 285                     if (subCode != 200 || !message.equals("SUCCESS")) {
 
 286                         finalCode = SaltstackResultCodes.REQ_FAILURE.getValue();
 
 288                 } catch (JSONException e) {
 
 289                     saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
 
 290                     saltstackResult.setStatusMessage(String.format(
 
 291                             "Error processing response message = %s from host %s", results.getString(host), host));
 
 296             saltstackResult.setStatusCode(finalCode);
 
 298             // We return entire Results object as message
 
 299             saltstackResult.setResults(results.toString());
 
 302             saltstackResult.setStatusCode(SaltstackResultCodes.INVALID_RESPONSE.getValue());
 
 303             saltstackResult.setStatusMessage("Results not found in GET for response");
 
 305         return saltstackResult;
 
 308     private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
 
 310         Set<String> optionalParamsSet = new HashSet<>();
 
 311         Collections.addAll(optionalParamsSet, optionalTestParams);
 
 316             .filter(entry -> optionalParamsSet.contains(entry.getKey()))
 
 317             .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
 
 318              .forEach(entry -> parseOptionalParam(entry, jsonPayload));
 
 322     private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
 
 323         String key = params.getKey();
 
 324         String payload = params.getValue();
 
 327             case TIMEOUT_OPT_KEY:
 
 328                 int timeout = Integer.parseInt(payload);
 
 330                     throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
 
 332                 jsonPayload.put(key, payload);
 
 335             case VERSION_OPT_KEY:
 
 336                 jsonPayload.put(key, payload);
 
 339             case LOCAL_PARAMETERS_OPT_KEY:
 
 340             case ENV_PARAMETERS_OPT_KEY:
 
 341                 JSONObject paramsJson = new JSONObject(payload);
 
 342                 jsonPayload.put(key, paramsJson);
 
 345             case NODE_LIST_OPT_KEY:
 
 346                 JSONArray paramsArray = new JSONArray(payload);
 
 347                 jsonPayload.put(key, paramsArray);
 
 350             case FILE_PARAMETERS_OPT_KEY:
 
 351                 jsonPayload.put(key, getFilePayload(payload));
 
 360      * Return payload with escaped newlines
 
 362     private JSONObject getFilePayload(String payload) {
 
 363         String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
 
 364         return new JSONObject(formattedPayload);
 
 367     private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
 
 368         if (!params.containsKey(key)) {
 
 369             throw new SvcLogicException(String.format(
 
 370                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",
 
 373         if (Strings.isNullOrEmpty(params.get(key))) {
 
 374             throw new SvcLogicException(String.format(
 
 375                     "Saltstack: Mandatory SaltstackAdapter key %s not found in parameters provided by calling agent !",