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 !",