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 * ============LICENSE_END=========================================================
24 package org.onap.appc.adapter.ansible.model;
27 * This module implements the APP-C/Ansible Server interface
28 * based on the REST API specifications
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.Iterator;
35 import java.util.UUID;
36 import org.json.JSONArray;
37 import org.json.JSONException;
38 import org.json.JSONObject;
39 import org.onap.appc.exceptions.APPCException;
40 import com.google.common.base.Strings;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.apache.commons.lang.StringUtils;
46 * Class that validates and constructs requests sent/received from Ansible
49 public class AnsibleMessageParser {
51 private static final String STATUS_MESSAGE_KEY = "StatusMessage";
52 private static final String STATUS_CODE_KEY = "StatusCode";
53 private static final String SERVER_IP_KEY = "AnsibleServer";
54 private static final String PLAYBOOK_NAME_KEY = "PlaybookName";
55 private static final String AGENT_URL_KEY = "AgentUrl";
56 private static final String PASS_KEY = "Password";
57 private static final String USER_KEY = "User";
58 private static final String ID_KEY = "Id";
59 private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
60 private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
61 private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
62 private static final String NODE_LIST_OPT_KEY = "NodeList";
63 private static final String TIMEOUT_OPT_KEY = "Timeout";
64 private static final String VERSION_OPT_KEY = "Version";
65 private static final String ACTION_OPT_KEY = "Action";
67 private static final Logger LOGGER = LoggerFactory.getLogger(AnsibleMessageParser.class);
70 * Accepts a map of strings and a) validates if all parameters are appropriate
71 * (else, throws an exception) and b) if correct returns a JSON object with
72 * appropriate key-value pairs to send to the server.
74 * Mandatory parameters, that must be in the supplied information to the Ansible
75 * Adapter 1. URL to connect to 2. credentials for URL (assume username password
76 * for now) 3. Playbook name
79 public JSONObject reqMessage(Map<String, String> params) throws APPCException {
80 final String[] mandatoryTestParams = { AGENT_URL_KEY, PLAYBOOK_NAME_KEY, USER_KEY, PASS_KEY };
81 final String[] optionalTestParams = { ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
82 TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY };
84 JSONObject jsonPayload = new JSONObject();
86 for (String key : mandatoryTestParams) {
87 throwIfMissingMandatoryParam(params, key);
88 jsonPayload.put(key, params.get(key));
91 parseOptionalParams(params, optionalTestParams, jsonPayload);
93 // Generate a unique uuid for the test
94 String reqId = UUID.randomUUID().toString();
95 jsonPayload.put(ID_KEY, reqId);
101 * Method that validates that the Map has enough information to query Ansible
102 * server for a result. If so, it returns the appropriate url, else an empty
105 public String reqUriResult(Map<String, String> params) throws APPCException {
107 final String[] mandatoryTestParams = { AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY };
109 for (String key : mandatoryTestParams) {
110 throwIfMissingMandatoryParam(params, key);
112 return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetResult";
116 * Method that validates that the Map has enough information to query Ansible
117 * server for a result. If so, it returns the appropriate url, else an empty
120 public String reqUriResultWithIP(Map<String, String> params, String serverIP) throws APPCException {
122 final String[] mandatoryTestParams = { AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY };
124 for (String key : mandatoryTestParams) {
125 throwIfMissingMandatoryParam(params, key);
127 String[] arr1 = params.get(AGENT_URL_KEY).split("//", 2);
128 String[] arr2 = arr1[1].split(":", 2);
129 return arr1[0] + "//" + serverIP + ":" + arr2[1] + "?Id=" + params.get(ID_KEY) + "&Type=GetResult";
133 * Method that validates that the Map has enough information to query Ansible
134 * server for logs. If so, it populates the appropriate returns the appropriate
135 * url, else an empty string.
137 public String reqUriLog(Map<String, String> params) throws APPCException {
139 final String[] mandatoryTestParams = { AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY };
141 for (String mandatoryParam : mandatoryTestParams) {
142 throwIfMissingMandatoryParam(params, mandatoryParam);
144 return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetLog";
148 * This method parses response from the Ansible Server when we do a post and
149 * returns an AnsibleResult object.
151 public AnsibleResult parsePostResponse(String input) throws APPCException {
152 AnsibleResult ansibleResult;
154 JSONObject postResponse = new JSONObject(input);
156 int code = postResponse.getInt(STATUS_CODE_KEY);
157 String msg = postResponse.getString(STATUS_MESSAGE_KEY);
158 String serverIP = "";
159 if (postResponse.has(SERVER_IP_KEY))
160 serverIP = postResponse.getString(SERVER_IP_KEY);
163 int initResponseValue = AnsibleResultCodes.INITRESPONSE.getValue();
164 boolean validCode = AnsibleResultCodes.CODE.checkValidCode(initResponseValue, code);
166 throw new APPCException("Invalid InitResponse code = " + code + " received. MUST be one of "
167 + AnsibleResultCodes.CODE.getValidCodes(initResponseValue));
170 ansibleResult = new AnsibleResult(code, msg);
171 if (StringUtils.isNotBlank(serverIP))
172 ansibleResult.setServerIp(serverIP);
174 if (!postResponse.isNull("Output")) {
175 LOGGER.info("Processing results-output in post response");
176 JSONObject output = postResponse.getJSONObject("Output");
177 ansibleResult.setOutput(output.toString());
180 } catch (JSONException e) {
181 LOGGER.error("JSONException: Error parsing response", e);
182 ansibleResult = new AnsibleResult(600, "Error parsing response = " + input + ". Error = " + e.getMessage());
184 return ansibleResult;
188 * This method parses response from an Ansible server when we do a GET for a
189 * result and returns an AnsibleResult object.
191 public AnsibleResult parseGetResponse(String input) throws APPCException {
193 AnsibleResult ansibleResult = new AnsibleResult();
196 JSONObject postResponse = new JSONObject(input);
197 ansibleResult = parseGetResponseNested(ansibleResult, postResponse);
198 } catch (JSONException e) {
199 LOGGER.error("JSONException: Error parsing response", e);
200 ansibleResult = new AnsibleResult(AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
201 "Error parsing response = " + input + ". Error = " + e.getMessage(), "");
203 return ansibleResult;
206 private AnsibleResult parseGetResponseNested(AnsibleResult ansibleResult, JSONObject postRsp) throws APPCException {
208 int codeStatus = postRsp.getInt(STATUS_CODE_KEY);
209 String messageStatus = postRsp.getString(STATUS_MESSAGE_KEY);
210 int finalCode = AnsibleResultCodes.FINAL_SUCCESS.getValue();
212 boolean valCode = AnsibleResultCodes.CODE.checkValidCode(AnsibleResultCodes.FINALRESPONSE.getValue(),
216 throw new APPCException("Invalid FinalResponse code = " + codeStatus + " received. MUST be one of "
217 + AnsibleResultCodes.CODE.getValidCodes(AnsibleResultCodes.FINALRESPONSE.getValue()));
220 ansibleResult.setStatusCode(codeStatus);
221 ansibleResult.setStatusMessage(messageStatus);
222 LOGGER.info("Received response with code = {}, Message = {}", codeStatus, messageStatus);
224 if (!postRsp.isNull("Results")) {
226 // Results are available. process them
227 // Results is a dictionary of the form
228 // {host :{status:s, group:g, message:m, hostname:h}, ...}
229 LOGGER.info("Processing results in response");
230 JSONObject results = postRsp.getJSONObject("Results");
231 LOGGER.info("Get JSON dictionary from Results ..");
232 Iterator<String> hosts = results.keys();
233 LOGGER.info("Iterating through hosts");
235 while (hosts.hasNext()) {
236 String host = hosts.next();
237 LOGGER.info("Processing host = {}",
238 (host.matches("^[\\w\\-.]+$")) ? host : "[unexpected value, logging suppressed]");
240 JSONObject hostResponse = results.getJSONObject(host);
241 int subCode = hostResponse.getInt(STATUS_CODE_KEY);
242 String message = hostResponse.getString(STATUS_MESSAGE_KEY);
244 LOGGER.info("Code = {}, Message = {}", subCode, message);
246 if (subCode != 200 || !(("SUCCESS").equals(message))) {
247 finalCode = AnsibleResultCodes.REQ_FAILURE.getValue();
249 } catch (JSONException e) {
250 LOGGER.error("JSONException: Error parsing response", e);
251 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
252 ansibleResult.setStatusMessage(String.format("Error processing response message = %s from host %s",
253 results.getString(host), host));
258 ansibleResult.setStatusCode(finalCode);
260 // We return entire Results object as message
261 ansibleResult.setResults(results.toString());
264 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
265 ansibleResult.setStatusMessage("Results not found in GET for response");
267 if (!postRsp.isNull("Output")) {
268 LOGGER.info("Processing results-output in response");
269 JSONObject output = postRsp.getJSONObject("Output");
270 ansibleResult.setOutput(output.toString());
273 return ansibleResult;
276 private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
278 Set<String> optionalParamsSet = new HashSet<>();
279 Collections.addAll(optionalParamsSet, optionalTestParams);
282 params.entrySet().stream().filter(entry -> optionalParamsSet.contains(entry.getKey()))
283 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
284 .forEach(entry -> parseOptionalParam(entry, jsonPayload));
288 private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
289 String key = params.getKey();
290 String payload = params.getValue();
293 case TIMEOUT_OPT_KEY:
294 int timeout = Integer.parseInt(payload);
296 throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
298 jsonPayload.put(key, payload);
301 case VERSION_OPT_KEY:
302 jsonPayload.put(key, payload);
305 case LOCAL_PARAMETERS_OPT_KEY:
306 case ENV_PARAMETERS_OPT_KEY:
307 JSONObject paramsJson = new JSONObject(payload);
308 jsonPayload.put(key, paramsJson);
311 case NODE_LIST_OPT_KEY:
312 JSONArray paramsArray = new JSONArray(payload);
313 jsonPayload.put(key, paramsArray);
316 case FILE_PARAMETERS_OPT_KEY:
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 APPCException {
334 if (!params.containsKey(key)) {
335 throw new APPCException(String.format(
336 "Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !",
339 if (Strings.isNullOrEmpty(params.get(key))) {
340 throw new APPCException(String.format(
341 "Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !",