2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
20 * ============LICENSE_END=========================================================
23 package org.onap.ccsdk.sli.adaptors.ansible.model;
25 import com.google.common.base.Strings;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.Iterator;
31 import java.util.UUID;
32 import org.apache.commons.lang.StringUtils;
33 import org.json.JSONArray;
34 import org.json.JSONException;
35 import org.json.JSONObject;
36 import org.onap.ccsdk.sli.adaptors.ansible.AnsibleAdapterConstants;
37 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import static org.onap.ccsdk.sli.adaptors.ansible.AnsibleAdapterConstants.*;
44 * Class that validates and constructs requests sent/received from
47 public class AnsibleMessageParser {
50 private static final String JSON_ERROR_MESSAGE = "JSONException: Error parsing response";
52 private static final Logger LOGGER = LoggerFactory.getLogger(AnsibleMessageParser.class);
55 * Accepts a map of strings and
56 * a) validates if all parameters are appropriate (else, throws an exception) and
57 * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
59 * Mandatory parameters, that must be in the supplied information to the Ansible Adapter
60 * 1. URL to connect to
61 * 2. credentials for URL (assume user pswd for now)
64 public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
65 final String[] mandatoryTestParams = {AGENT_URL, PLAYBOOK_NAME, USER, PSWD};
66 final String[] optionalTestParams = {ENV_PARAMETERS, NODE_LIST, LOCAL_PARAMETERS, TIMEOUT, VERSION, FILE_PARAMETERS,
67 ACTION, INVENTORY_NAMES, AUTO_NODE_LIST};
68 JSONObject jsonPayload = new JSONObject();
70 for (String key : mandatoryTestParams) {
71 throwIfMissingMandatoryParam(params, key);
72 jsonPayload.put(key, params.get(key));
75 parseOptionalParams(params, optionalTestParams, jsonPayload);
77 // Generate a unique uuid for the test
78 String reqId = UUID.randomUUID().toString();
79 jsonPayload.put(ID, reqId);
84 * Method that validates that the Map has enough information
85 * to query Ansible server for a result. If so, it returns
86 * the appropriate url, else an empty string.
88 public String reqUriResult(Map<String, String> params) throws SvcLogicException {
89 final String[] mandatoryTestParams = {AGENT_URL, ID, USER, PSWD};
90 for (String key : mandatoryTestParams) {
91 throwIfMissingMandatoryParam(params, key);
93 return params.get(AGENT_URL) + "?Id=" + params.get(ID) + "&Type=GetResult";
97 * Method that validates that the Map has enough information to query Ansible
98 * server for a result. If so, it returns the appropriate url, else an empty
101 public String reqUriResultWithIP(Map<String, String> params, String serverIP) throws SvcLogicException {
102 final String[] mandatoryTestParams = {AGENT_URL, ID, USER, PSWD};
103 for (String key : mandatoryTestParams) {
104 throwIfMissingMandatoryParam(params, key);
106 String[] arr1 = params.get(AGENT_URL).split("//", 2);
107 String[] arr2 = arr1[1].split(":", 2);
108 return arr1[0] + "//" + serverIP + ":" + arr2[1] + "?Id=" + params.get(ID) + "&Type=GetResult";
112 * Method that validates that the Map has enough information to query Ansible
113 * server for logs. If so, it populates the appropriate returns the appropriate
114 * url, else an empty string.
116 public String reqUriLog(Map<String, String> params) throws SvcLogicException {
117 final String[] mandatoryTestParams = {AGENT_URL, ID, USER, PSWD};
118 for (String mandatoryParam : mandatoryTestParams) {
119 throwIfMissingMandatoryParam(params, mandatoryParam);
121 return params.get(AGENT_URL) + "?Id=" + params.get(ID) + "&Type=GetLog";
125 * Method that validates that the Map has enough information
126 * to query Ansible server for an output. If so, it returns
127 * the appropriate url, else an empty string.
129 public String reqUriOutput(Map<String, String> params) throws SvcLogicException {
130 final String[] mandatoryTestParams = {AGENT_URL, ID, USER, PSWD};
131 for (String mandatoryParam : mandatoryTestParams) {
132 throwIfMissingMandatoryParam(params, mandatoryParam);
134 return params.get(AGENT_URL) + "?Id=" + params.get(ID) + "&Type=GetOutput";
138 * This method parses response from the Ansible Server when we do a post
139 * and returns an AnsibleResult object.
141 public AnsibleResult parsePostResponse(String input) throws SvcLogicException {
142 AnsibleResult ansibleResult;
144 JSONObject postResponse = new JSONObject(input);
145 int code = postResponse.getInt(STATUS_CODE);
146 int initResponseValue = AnsibleResultCodes.INITRESPONSE.getValue();
147 boolean validCode = AnsibleResultCodes.CODE.checkValidCode(initResponseValue, code);
149 throw new SvcLogicException(String.format("Invalid InitResponse code = %s received. MUST be one of %s",
150 code, AnsibleResultCodes.CODE.getValidCodes(initResponseValue)));
153 ansibleResult = new AnsibleResult(code, postResponse.getString(STATUS_MESSAGE));
154 if (postResponse.has(ANSIBLE_SERVER) && StringUtils.isNotBlank(postResponse.getString(ANSIBLE_SERVER))) {
155 ansibleResult.setServerIp(postResponse.getString(ANSIBLE_SERVER));
157 if (!postResponse.isNull(OUTPUT)) {
158 LOGGER.info("Processing results-output in post response");
159 JSONObject output = postResponse.getJSONObject(OUTPUT);
160 ansibleResult.setOutput(output.toString());
162 } catch (JSONException e) {
163 LOGGER.error(JSON_ERROR_MESSAGE, e);
164 ansibleResult = new AnsibleResult(600, "Error parsing response = " + input + ". Error = " + e.getMessage());
166 return ansibleResult;
170 * This method parses response from an Ansible server when we do a GET for a result
171 * and returns an AnsibleResult object.
173 public AnsibleResult parseGetResponse(String input) throws SvcLogicException {
174 AnsibleResult ansibleResult = new AnsibleResult();
176 JSONObject postResponse = new JSONObject(input);
177 parseGetResponseNested(ansibleResult, postResponse);
178 } catch (JSONException e) {
179 LOGGER.error(JSON_ERROR_MESSAGE, e);
180 ansibleResult = new AnsibleResult(AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
181 "Error parsing response = " + input + ". Error = " + e.getMessage(), "");
183 return ansibleResult;
186 private void parseGetResponseNested(AnsibleResult ansibleResult, JSONObject postRsp) throws SvcLogicException {
187 String messageStatus = postRsp.getString(STATUS_MESSAGE);
188 int codeStatus = postRsp.getInt(STATUS_CODE);
189 int finalCode = AnsibleResultCodes.FINAL_SUCCESS.getValue();
190 boolean valCode = AnsibleResultCodes.CODE.checkValidCode(AnsibleResultCodes.FINALRESPONSE.getValue(), codeStatus);
192 throw new SvcLogicException(String.format("Invalid InitResponse code = %s received. MUST be one of %s",
193 codeStatus, AnsibleResultCodes.CODE.getValidCodes(AnsibleResultCodes.FINALRESPONSE.getValue())));
196 ansibleResult.setStatusCode(codeStatus);
197 ansibleResult.setStatusMessage(messageStatus);
198 ansibleResult.setConfigData("UNKNOWN");
199 LOGGER.info("Received response with code = {}, Message = {}", codeStatus, messageStatus);
201 if (!postRsp.isNull("Results")) {
203 // Results are available. process them
204 // Results is a dictionary of the form
206 LOGGER.info("Processing results in response");
207 JSONObject results = postRsp.getJSONObject("Results");
209 LOGGER.info("Get JSON dictionary from Results by Iterating through hosts");
210 Iterator<String> hosts = results.keys();
211 while (hosts.hasNext()) {
212 String host = hosts.next();
213 LOGGER.info("Processing host = {}",
214 (host.matches("^[\\w\\-.]+$")) ? host : "[unexpected value, logging suppressed]");
216 JSONObject hostResponse = results.getJSONObject(host);
217 int subCode = hostResponse.getInt(STATUS_CODE);
218 String message = hostResponse.getString(STATUS_MESSAGE);
220 LOGGER.info("Code = {}, Message = {}", subCode, message);
222 if (subCode != 200 || !"SUCCESS".equals(message)) {
223 finalCode = AnsibleResultCodes.REQ_FAILURE.getValue();
225 if ((hostResponse.optJSONObject(OUTPUT)) != null) {
226 JSONObject hostResponseObjectInfo = hostResponse.optJSONObject(OUTPUT).optJSONObject("info");
227 JSONObject hostResponseConfigData = hostResponseObjectInfo.optJSONObject("configData");
228 if (hostResponseConfigData != null) {
229 ansibleResult.setConfigData(hostResponseConfigData.toString());
232 } catch (JSONException e) {
233 LOGGER.error(JSON_ERROR_MESSAGE, e);
234 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
235 ansibleResult.setStatusMessage(String.format("Error processing response message = %s from host %s",
236 results.getString(host), host));
240 ansibleResult.setStatusCode(finalCode);
242 // We return entire Results object as message
243 ansibleResult.setResults(results.toString());
245 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
246 ansibleResult.setStatusMessage("Results not found in GET for response");
248 if (!postRsp.isNull(OUTPUT)) {
249 LOGGER.info("Processing results-output in response");
250 JSONObject output = postRsp.getJSONObject(OUTPUT);
251 ansibleResult.setOutput(output.toString());
255 private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
257 Set<String> optionalParamsSet = new HashSet<>();
258 Collections.addAll(optionalParamsSet, optionalTestParams);
261 params.entrySet().stream().filter(entry -> optionalParamsSet.contains(entry.getKey()))
262 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
263 .forEach(entry -> parseOptionalParam(entry, jsonPayload));
267 private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
268 String key = params.getKey();
269 String payload = params.getValue();
273 if (dataIsVariable(payload)) {
276 int timeout = Integer.parseInt(payload);
278 throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
280 jsonPayload.put(key, payload);
283 if (payload.equalsIgnoreCase("true") || payload.equalsIgnoreCase("false")) {
284 jsonPayload.put(key, payload);
286 throw new IllegalArgumentException(" : specified invalid boolean value of AutoNodeList = " + payload);
290 if (dataIsVariable(payload)) {
293 case INVENTORY_NAMES:
294 jsonPayload.put(key, payload);
297 case LOCAL_PARAMETERS:
300 JSONObject paramsJson = new JSONObject(payload);
301 jsonDataIsVariable(paramsJson);
302 jsonPayload.put(key, paramsJson);
306 if (payload.startsWith("$")) {
309 JSONArray paramsArray = new JSONArray(payload);
310 jsonPayload.put(key, paramsArray);
313 case FILE_PARAMETERS:
314 if (dataIsVariable(payload)) {
317 jsonPayload.put(key, getFilePayload(payload));
326 * Return payload with escaped newlines
328 private JSONObject getFilePayload(String payload) {
329 String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
330 return new JSONObject(formattedPayload);
333 private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
334 if (!params.containsKey(key)) {
335 throw new SvcLogicException(String.format(
336 "Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !", key));
338 if (Strings.isNullOrEmpty(params.get(key))) {
339 throw new SvcLogicException(String.format(
340 "Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !", key));
342 if (StringUtils.startsWith(params.get(key), "$")) {
343 throw new SvcLogicException(String.format(
344 "Ansible: Mandatory AnsibleAdapter key %s is a variable", key));
348 private boolean dataIsVariable(String payload) {
349 return StringUtils.startsWith(payload, "$") || StringUtils.isEmpty(payload);
352 private void jsonDataIsVariable(JSONObject paramsJson) {
353 LOGGER.info("input json is " + paramsJson);
354 String[] keys = JSONObject.getNames(paramsJson);
355 for (String k : keys) {
356 Object a = paramsJson.get(k);
357 if (a instanceof String) {
358 if (StringUtils.startsWith(a.toString(), "$") || StringUtils.isEmpty(a.toString())) {
359 LOGGER.info("removing key " + k);
360 paramsJson.remove(k);
364 LOGGER.info("returning json as {}", paramsJson);