2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2017 Amdocs
8 * =============================================================================
9 * Modifications Copyright (C) 2018 IBM.
10 * ================================================================================
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
15 * http://www.apache.org/licenses/LICENSE-2.0
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
23 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
24 * ============LICENSE_END=========================================================
27 package org.onap.ccsdk.sli.adaptors.ansible.model;
30 * This module implements the APP-C/Ansible Server interface
31 * based on the REST API specifications
33 import java.util.Collections;
34 import java.util.HashSet;
35 import java.util.Iterator;
38 import java.util.UUID;
39 import org.json.JSONArray;
40 import org.json.JSONException;
41 import org.json.JSONObject;
42 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
43 import com.google.common.base.Strings;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * Class that validates and constructs requests sent/received from
51 public class AnsibleMessageParser {
53 private static final String STATUS_MESSAGE_KEY = "StatusMessage";
54 private static final String STATUS_CODE_KEY = "StatusCode";
56 private static final String PLAYBOOK_NAME_KEY = "PlaybookName";
57 private static final String AGENT_URL_KEY = "AgentUrl";
58 private static final String PASS_KEY = "Password";
59 private static final String USER_KEY = "User";
60 private static final String ID_KEY = "Id";
62 private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
63 private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
64 private static final String ENV_PARAMETERS_OPT_KEY = "EnvParameters";
65 private static final String NODE_LIST_OPT_KEY = "NodeList";
66 private static final String TIMEOUT_OPT_KEY = "Timeout";
67 private static final String VERSION_OPT_KEY = "Version";
68 private static final String ACTION_OPT_KEY = "Action";
70 private String jsonException = "JSON exception";
71 private static final Logger LOGGER = LoggerFactory.getLogger(AnsibleMessageParser.class);
74 * Accepts a map of strings and
75 * a) validates if all parameters are appropriate (else, throws an exception) and
76 * b) if correct returns a JSON object with appropriate key-value pairs to send to the server.
78 * Mandatory parameters, that must be in the supplied information to the Ansible Adapter
79 * 1. URL to connect to
80 * 2. credentials for URL (assume username password for now)
84 public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
85 final String[] mandatoryTestParams = {AGENT_URL_KEY, PLAYBOOK_NAME_KEY, USER_KEY, PASS_KEY};
86 final String[] optionalTestParams = {ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
87 TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY};
89 JSONObject jsonPayload = new JSONObject();
91 for (String key : mandatoryTestParams) {
92 throwIfMissingMandatoryParam(params, key);
93 jsonPayload.put(key, params.get(key));
96 parseOptionalParams(params, optionalTestParams, jsonPayload);
98 // Generate a unique uuid for the test
99 String reqId = UUID.randomUUID().toString();
100 jsonPayload.put(ID_KEY, reqId);
106 * Method that validates that the Map has enough information
107 * to query Ansible server for a result. If so, it returns
108 * the appropriate url, else an empty string.
110 public String reqUriResult(Map<String, String> params) throws SvcLogicException {
112 final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
114 for (String key : mandatoryTestParams) {
115 throwIfMissingMandatoryParam(params, key);
117 return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetResult";
121 * Method that validates that the Map has enough information
122 * to query Ansible server for logs. If so, it populates the appropriate
123 * returns the appropriate url, else an empty string.
125 public String reqUriLog(Map<String, String> params) throws SvcLogicException {
127 final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
129 for (String mandatoryParam : mandatoryTestParams) {
130 throwIfMissingMandatoryParam(params, mandatoryParam);
132 return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetLog";
136 * Method that validates that the Map has enough information
137 * to query Ansible server for an output. If so, it returns
138 * the appropriate url, else an empty string.
140 public String reqUriOutput(Map<String, String> params) throws SvcLogicException {
142 final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
144 for (String mandatoryParam : mandatoryTestParams) {
145 throwIfMissingMandatoryParam(params, mandatoryParam);
147 return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetOutput";
151 * This method parses response from the Ansible Server when we do a post
152 * and returns an AnsibleResult object.
154 public AnsibleResult parsePostResponse(String input) throws SvcLogicException {
155 AnsibleResult ansibleResult;
157 JSONObject postResponse = new JSONObject(input);
159 int code = postResponse.getInt(STATUS_CODE_KEY);
160 String msg = postResponse.getString(STATUS_MESSAGE_KEY);
162 int initResponseValue = AnsibleResultCodes.INITRESPONSE.getValue();
163 boolean validCode = AnsibleResultCodes.CODE.checkValidCode(initResponseValue, code);
165 throw new SvcLogicException("Invalid InitResponse code = " + code + " received. MUST be one of "
166 + AnsibleResultCodes.CODE.getValidCodes(initResponseValue));
169 ansibleResult = new AnsibleResult(code, msg);
171 } catch (JSONException e) {
172 LOGGER.error(jsonException, e);
173 ansibleResult = new AnsibleResult(600, "Error parsing response = " + input + ". Error = " + e.getMessage());
175 return ansibleResult;
179 * This method parses response from an Ansible server when we do a GET for a result
180 * and returns an AnsibleResult object.
182 public AnsibleResult parseGetResponse(String input) throws SvcLogicException {
184 AnsibleResult ansibleResult = new AnsibleResult();
187 JSONObject postResponse = new JSONObject(input);
188 ansibleResult = parseGetResponseNested(ansibleResult, postResponse);
189 } catch (JSONException e) {
190 LOGGER.error(jsonException, e);
191 ansibleResult = new AnsibleResult(AnsibleResultCodes.INVALID_PAYLOAD.getValue(),
192 "Error parsing response = " + input + ". Error = " + e.getMessage(), "");
194 return ansibleResult;
197 private AnsibleResult parseGetResponseNested(AnsibleResult ansibleResult, JSONObject postRsp) throws SvcLogicException {
199 int codeStatus = postRsp.getInt(STATUS_CODE_KEY);
200 String messageStatus = postRsp.getString(STATUS_MESSAGE_KEY);
201 int finalCode = AnsibleResultCodes.FINAL_SUCCESS.getValue();
204 AnsibleResultCodes.CODE.checkValidCode(AnsibleResultCodes.FINALRESPONSE.getValue(), codeStatus);
207 throw new SvcLogicException("Invalid FinalResponse code = " + codeStatus + " received. MUST be one of "
208 + AnsibleResultCodes.CODE.getValidCodes(AnsibleResultCodes.FINALRESPONSE.getValue()));
211 ansibleResult.setStatusCode(codeStatus);
212 ansibleResult.setStatusMessage(messageStatus);
213 LOGGER.info("Received response with code = {}, Message = {}", codeStatus, messageStatus);
215 if (!postRsp.isNull("Results")) {
217 // Results are available. process them
218 // Results is a dictionary of the form
220 LOGGER.info("Processing results in response");
221 JSONObject results = postRsp.getJSONObject("Results");
222 LOGGER.info("Get JSON dictionary from Results ..");
223 Iterator<String> hosts = results.keys();
224 LOGGER.info("Iterating through hosts");
226 while (hosts.hasNext()) {
227 String host = hosts.next();
228 LOGGER.info("Processing host = {}", host);
231 JSONObject hostResponse = results.getJSONObject(host);
232 int subCode = hostResponse.getInt(STATUS_CODE_KEY);
233 String message = hostResponse.getString(STATUS_MESSAGE_KEY);
235 LOGGER.info("Code = {}, Message = {}", subCode, message);
237 if (subCode != 200 || !("SUCCESS").equals(message)) {
238 finalCode = AnsibleResultCodes.REQ_FAILURE.getValue();
240 } catch (JSONException e) {
241 LOGGER.error(jsonException, e);
242 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
243 ansibleResult.setStatusMessage(String.format(
244 "Error processing response message = %s from host %s", results.getString(host), host));
249 ansibleResult.setStatusCode(finalCode);
251 // We return entire Results object as message
252 ansibleResult.setResults(results.toString());
255 ansibleResult.setStatusCode(AnsibleResultCodes.INVALID_RESPONSE.getValue());
256 ansibleResult.setStatusMessage("Results not found in GET for response");
258 return ansibleResult;
261 private void parseOptionalParams(Map<String, String> params, String[] optionalTestParams, JSONObject jsonPayload) {
263 Set<String> optionalParamsSet = new HashSet<>();
264 Collections.addAll(optionalParamsSet, optionalTestParams);
269 .filter(entry -> optionalParamsSet.contains(entry.getKey()))
270 .filter(entry -> !Strings.isNullOrEmpty(entry.getValue()))
271 .forEach(entry -> parseOptionalParam(entry, jsonPayload));
275 private void parseOptionalParam(Map.Entry<String, String> params, JSONObject jsonPayload) {
276 String key = params.getKey();
277 String payload = params.getValue();
280 case TIMEOUT_OPT_KEY:
281 int timeout = Integer.parseInt(payload);
283 throw new NumberFormatException(" : specified negative integer for timeout = " + payload);
285 jsonPayload.put(key, payload);
288 case VERSION_OPT_KEY:
289 jsonPayload.put(key, payload);
292 case LOCAL_PARAMETERS_OPT_KEY:
293 case ENV_PARAMETERS_OPT_KEY:
294 JSONObject paramsJson = new JSONObject(payload);
295 jsonPayload.put(key, paramsJson);
298 case NODE_LIST_OPT_KEY:
299 JSONArray paramsArray = new JSONArray(payload);
300 jsonPayload.put(key, paramsArray);
303 case FILE_PARAMETERS_OPT_KEY:
304 jsonPayload.put(key, getFilePayload(payload));
313 * Return payload with escaped newlines
315 private JSONObject getFilePayload(String payload) {
316 String formattedPayload = payload.replace("\n", "\\n").replace("\r", "\\r");
317 return new JSONObject(formattedPayload);
320 private void throwIfMissingMandatoryParam(Map<String, String> params, String key) throws SvcLogicException {
321 if (!params.containsKey(key)) {
322 throw new SvcLogicException(String.format(
323 "Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !",
326 if (Strings.isNullOrEmpty(params.get(key))) {
327 throw new SvcLogicException(String.format(
328 "Ansible: Mandatory AnsibleAdapter key %s not found in parameters provided by calling agent !",