aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGanesh Chandrasekaran <ganesh.c@samsung.com>2018-07-04 14:10:57 +0900
committerGanesh Chandrasekaran <ganesh.c@samsung.com>2018-07-08 23:14:16 +0000
commit43090d8778d60ed62089927c1f6732140036791a (patch)
treee41cf85c64bbacbd8d5af32355ff506e7fa9899f
parentccf87e9fe98de4be6d63f81aaa2b9034da27d039 (diff)
reqExec API implemented for saltstack
Issue-ID: CCSDK-320 Change-Id: I5c4eb36924f36ebc9a7786d2bd46c923d0c05ab0 Signed-off-by: Ganesh Chandrasekaran <ganesh.c@samsung.com>
-rw-r--r--saltstack-adapter/README.md30
-rw-r--r--saltstack-adapter/saltstack-adapter-features/ccsdk-saltstack-adapter/pom.xml43
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/pom.xml45
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/ConnectionBuilder.java149
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SaltstackAdapterImpl.java107
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SshConnection.java250
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/JsonParser.java89
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackMessageParser.java133
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResult.java27
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResultCodes.java4
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackServerEmulator.java47
-rw-r--r--saltstack-adapter/saltstack-adapter-provider/src/test/java/org/onap/appc/adapter/impl/TestSaltstackAdapterImpl.java163
12 files changed, 972 insertions, 115 deletions
diff --git a/saltstack-adapter/README.md b/saltstack-adapter/README.md
index 0c3161ac..cf21e105 100644
--- a/saltstack-adapter/README.md
+++ b/saltstack-adapter/README.md
@@ -31,4 +31,32 @@ Create an Adaptor to communicate with the SaltStack server:
***Requirements and benefits of the chosen SSH method:***
1) The SaltStack server should have it’s SSH enabled.
-2) Such execution method will give the DGs and adaptor with more refined control on the SaltStack server. \ No newline at end of file
+2) Such execution method will give the DGs and adaptor with more refined control on the SaltStack server.
+==================================================================================================================
+
+
+***Defining Saltstack server properties:*** Can be done with 2 different methods.
+1) Saltstack server details are found in the property file named saltstack-adapter.properties. Param has to be given with following types.
+ "org.onap.appc.adapter.saltstack.clientType"; -> Supported types are (BASIC || SSH_CERT || BOTH).
+ "org.onap.appc.adapter.saltstack.host"; -> Saltstack server's host name IP address.
+ "org.onap.appc.adapter.saltstack.port"; -> Saltstack server's port to make SSH connection to.
+ "org.onap.appc.adapter.saltstack.userName"; -> Saltstack server's SSH UserName.
+ "org.onap.appc.adapter.saltstack.userPasswd"; -> Saltstack server's SSH Password.
+ "org.onap.appc.adapter.saltstack.sshKey"; -> Saltstack server's SSH KEY file location.
+2) All the server related details can also be passed as param to the adaptor from the Directed Graphs. Param has to be given with following types.
+ "HostName"; -> Saltstack server's host name IP address.
+ "Port"; -> Saltstack server's port to make SSH connection to.
+ "Password"; -> Saltstack server's SSH UserName.
+ "User"; -> Saltstack server's SSH Password.
+ Note: SSH_CERT based Auth is not supported in this method.
+
+***Using Saltstack Adaptor Commands and params to pass in:*** reqExecCommand:
+Method to execute a single command on SaltState server. The command entered should request the output in JSON format, this can be done by appending json-out outputter as specified in https://docs.saltstack.com/en/latest/ref/output/all/salt.output.json_out.html#module-salt.output.json_out and https://docs.saltstack.com/en/2017.7/ref/cli/salt-call.html The response from Saltstack comes in json format and it is automatically put to context for DGs access, with a certain request-ID as prefix.
+Example command will look like:
+1) Command to test if all VNFC are running: "salt * test.ping --out=json --static"
+2) To check Network interfaces on your minions: "salt '*' network.interfaces --out=json --static"
+3) Restart Minion service after upgrade process: "salt minion1 service.restart salt-minion --out=json --static"
+Note: If using --out=json, you will probably want --static as well. Without the static option, you will get a separate JSON string per minion which makes JSON output invalid as a whole. This is due to using an iterative outputter. So if you want to feed it to a JSON parser, use --static as well.
+
+This "reqExecCommand" method gives the Operator/Directed Graphs to execute commands in a fine-tuned manner, which also means the operator/DG-creator should know what to expect as output as a result of command execution. By this way using DGs, the operator can check for success/failure of the executed comment.
+If the output is not in JSON format, then the adaptor still tries to convert it into properties, in addition "reqID.completeResult" param will have the whole result for DG access.
diff --git a/saltstack-adapter/saltstack-adapter-features/ccsdk-saltstack-adapter/pom.xml b/saltstack-adapter/saltstack-adapter-features/ccsdk-saltstack-adapter/pom.xml
index fa442ac3..92f404ec 100644
--- a/saltstack-adapter/saltstack-adapter-features/ccsdk-saltstack-adapter/pom.xml
+++ b/saltstack-adapter/saltstack-adapter-features/ccsdk-saltstack-adapter/pom.xml
@@ -15,39 +15,36 @@
<packaging>feature</packaging>
<name>ccsdk-sli-adaptors :: saltstack-adapter:: ${project.artifactId}</name>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>mdsal-artifacts</artifactId>
- <version>${odl.mdsal.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- <dependency>
- <groupId>org.opendaylight.mdsal.model</groupId>
- <artifactId>mdsal-model-artifacts</artifactId>
- <version>${odl.mdsal.model.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <dependencies>
-
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-artifacts</artifactId>
+ <version>${odl.mdsal.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal.model</groupId>
+ <artifactId>mdsal-model-artifacts</artifactId>
+ <version>${odl.mdsal.model.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <!-- <dependencies>
<dependency>
<groupId>org.onap.ccsdk.sli.core</groupId>
<artifactId>ccsdk-sli</artifactId>
<type>xml</type>
<classifier>features</classifier>
</dependency>
-
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>saltstack-adapter-provider</artifactId>
<version>${project.version}</version>
</dependency>
-
-
</dependencies>
+ -->
</project>
diff --git a/saltstack-adapter/saltstack-adapter-provider/pom.xml b/saltstack-adapter/saltstack-adapter-provider/pom.xml
index f4e04500..41bf7c67 100644
--- a/saltstack-adapter/saltstack-adapter-provider/pom.xml
+++ b/saltstack-adapter/saltstack-adapter-provider/pom.xml
@@ -44,6 +44,20 @@
<version>1.2</version>
</dependency>
+ <!-- Needed to run SSH -->
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-core</artifactId>
+ <version>0.12.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.appc</groupId>
+ <artifactId>appc-common</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ </dependency>
+
<!-- Needed to run test cases -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
@@ -60,6 +74,18 @@
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.3.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.jettison</groupId>
+ <artifactId>jettison</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
@@ -108,5 +134,22 @@
</dependencies>
-
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Service>org.onap.appc.adapter.ssh.SshAdapter</Export-Service>
+ <Private-Package>org.onap.appc.adapter.ssh.impl.*</Private-Package>
+ <Import-Package>!org.apache.log,!org.apache.commons.logging,!groovy.lang,!javax.jms,!org.codehaus.commons.compiler,!org.codehaus.groovy.*,!org.codehaus.janino,!com.ibm.icu.*,!com.sun.faces.*,!org.jasypt.*,*</Import-Package>
+ <Embed-Dependency>!dblib-provider,jasypt,eelf-core,logback-core,logback-classic;scope=compile|runtime;inline=false</Embed-Dependency>
+ <Embed-Transitive>true</Embed-Transitive>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/ConnectionBuilder.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/ConnectionBuilder.java
index 7702dc80..5dee9f5e 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/ConnectionBuilder.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/ConnectionBuilder.java
@@ -24,11 +24,18 @@
package org.onap.ccsdk.sli.adaptors.saltstack.impl;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
-import java.security.KeyManagementException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
import org.onap.ccsdk.sli.adaptors.saltstack.model.SaltstackResult;
import org.onap.ccsdk.sli.adaptors.saltstack.model.SaltstackResultCodes;
import org.onap.ccsdk.sli.core.sli.SvcLogicException;
@@ -47,30 +54,142 @@ import com.att.eelf.configuration.EELFManager;
public class ConnectionBuilder {
private static final EELFLogger logger = EELFManager.getInstance().getLogger(ConnectionBuilder.class);
+ SshConnection sshConnection;
+ /**
+ * Constructor that initializes an ssh client based on username and password
+ **/
+ public ConnectionBuilder(String host, String port, String userName, String userPasswd) {
+ sshConnection = new SshConnection(host, Integer.parseInt(port), userName, userPasswd);
+ }
/**
- * Constructor that initializes an ssh client based on certificate
+ * Constructor that initializes an ssh client based on ssh certificate
**/
- public ConnectionBuilder(String userName, String userPasswd) throws KeyStoreException, CertificateException, IOException,
- KeyManagementException, NoSuchAlgorithmException, SvcLogicException {
+ public ConnectionBuilder(String host, String port, String certFile) {
+ sshConnection = new SshConnection(host, Integer.parseInt(port), certFile);
+ }
+ /**
+ * Constructor that initializes an ssh client based on ssh username password and certificate
+ **/
+ public ConnectionBuilder(String host, String port, String userName, String userPasswd,
+ String certFile) {
+ sshConnection = new SshConnection(host, Integer.parseInt(port), userName, userPasswd, certFile);
}
/**
- * Constructor which trusts all certificates in a specific java keystore file (assumes a JKS
- * file)
- **/
- public ConnectionBuilder(String certFile) throws KeyStoreException, IOException,
- KeyManagementException, NoSuchAlgorithmException, CertificateException {
+ * 1. Connect to SSH server.
+ * 2. Exec remote command over SSH. Return command execution status.
+ * Command output is written to out or err stream.
+ *
+ * @param cmd Commands to execute
+ * @return command execution status
+ */
+ public SaltstackResult connectNExecute(String cmd) {
+ return connectNExecute(cmd,-1,-1);
+ }
+
+ /**
+ * 1. Connect to SSH server with retry enabled.
+ * 2. Exec remote command over SSH. Return command execution status.
+ * Command output is written to out or err stream.
+ *
+ * @param cmd Commands to execute
+ * @param retryDelay delay between retry to make a SSH connection.
+ * @param retryCount number of count retry to make a SSH connection.
+ * @return command execution status
+ */
+ public SaltstackResult connectNExecute(String cmd, int retryCount, int retryDelay) {
+
+ SaltstackResult result = new SaltstackResult();
+ try {
+ if (retryCount != -1) {
+ result = sshConnection.connectWithRetry(retryCount, retryDelay);
+ } else {
+ result = sshConnection.connect();
+ }
+ if (result.getStatusCode() != SaltstackResultCodes.SUCCESS.getValue()) {
+ return result;
+ }
+ String outFilePath = "/tmp/"+ RandomStringUtils.random(5,true,true);
+ String errFilePath = "/tmp/"+ RandomStringUtils.random(5,true,true);
+ OutputStream out = new FileOutputStream(outFilePath);
+ OutputStream errs = new FileOutputStream(errFilePath);
+ result = sshConnection.execCommand(cmd, out, errs);
+ sshConnection.disconnect();
+ out.close();
+ errs.close();
+ if (result.getSshExitStatus() != 0) {
+ return sortExitStatus(result.getSshExitStatus(), errFilePath, cmd);
+ }
+ if (result.getStatusCode() != SaltstackResultCodes.SUCCESS.getValue()) {
+ return result;
+ }
+ result.setStatusMessage("Success");
+ result.setOutputFileName(outFilePath);
+ } catch (Exception io) {
+ logger.error("Caught Exception", io);
+ result.setStatusCode(SaltstackResultCodes.UNKNOWN_EXCEPTION.getValue());
+ result.setStatusMessage(io.getMessage());
+ }
+ return result;
+ }
+ public SaltstackResult sortExitStatus (int exitStatus, String errFilePath, String cmd) {
+ SaltstackResult result = new SaltstackResult();
+ String err;
+ StringWriter writer = new StringWriter();
+ try {
+ IOUtils.copy(new FileInputStream(new File(errFilePath)), writer, "UTF-8");
+ err = writer.toString();
+ } catch (Exception e){
+ err = "";
+ }
+ if (exitStatus == 255 || exitStatus == 1) {
+ String errMessage = "Error executing command [" + cmd + "] over SSH [" + sshConnection.toString()
+ + "]. Exit Code " + exitStatus + " and Error message : " +
+ "Malformed configuration. "+ err;
+ logger.error(errMessage);
+ result.setStatusCode(SaltstackResultCodes.INVALID_COMMAND.getValue());
+ result.setStatusMessage(errMessage);
+ } else if (exitStatus == 5 || exitStatus == 65) {
+ String errMessage = "Error executing command [" + cmd + "] over SSH [" + sshConnection.toString()
+ + "]. Exit Code " + exitStatus + " and Error message : " +
+ "Host not allowed to connect. "+ err;
+ logger.error(errMessage);
+ result.setStatusCode(SaltstackResultCodes.USER_UNAUTHORIZED.getValue());
+ result.setStatusMessage(errMessage);
+ } else if (exitStatus == 67 || exitStatus == 73) {
+ String errMessage = "Error executing command [" + cmd + "] over SSH [" + sshConnection.toString()
+ + "]. Exit Code " + exitStatus + " and Error message : " +
+ "Key exchange failed. "+ err;
+ logger.error(errMessage);
+ result.setStatusCode(SaltstackResultCodes.CERTIFICATE_ERROR.getValue());
+ result.setStatusMessage(errMessage);
+ } else {
+ String errMessage = "Error executing command [" + cmd + "] over SSH [" + sshConnection.toString()
+ + "]. Exit Code " + exitStatus + " and Error message : "+ err;
+ logger.error(errMessage);
+ result.setStatusCode(SaltstackResultCodes.UNKNOWN_EXCEPTION.getValue());
+ result.setStatusMessage(errMessage);
+ }
+ return result;
}
/**
- * Connect to SSH server.
+ * 1. Connect to SSH server.
+ * 2. Exec remote command over SSH. Return command execution status.
+ * Command output is written to out or err stream.
+ *
+ * @param commands list of commands to execute
+ * @param payloadSLS has the SLS file location that is to be sent to server
+ * @param retryDelay delay between retry to make a SSH connection.
+ * @param retryCount number of count retry to make a SSH connection.
+ * @return command execution status
*/
- public SaltstackResult connect(String agentUrl, String payload) {
+ public SaltstackResult connectNExecuteSLS(String commands, String payloadSLS, int retryDelay, int retryCount) {
SaltstackResult result = new SaltstackResult();
try {
@@ -104,8 +223,6 @@ public class ConnectionBuilder {
* Command output is written to out or err stream.
*
* @param cmd command to execute
- * @param out content of sysout will go to this stream
- * @param err content of syserr will go to this stream
* @return command execution status
*/
public SaltstackResult execute(String cmd) {
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SaltstackAdapterImpl.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SaltstackAdapterImpl.java
index 6ff5e574..5fe130fc 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SaltstackAdapterImpl.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SaltstackAdapterImpl.java
@@ -79,7 +79,12 @@ public class SaltstackAdapterImpl implements SaltstackAdapter {
private static final String ID_ATTRIBUTE_NAME = "org.onap.appc.adapter.saltstack.Id";
private static final String LOG_ATTRIBUTE_NAME = "org.onap.appc.adapter.saltstack.log";
+ public static final String CONNECTION_RETRY_DELAY = "retryDelay";
+ public static final String CONNECTION_RETRY_COUNT = "retryCount";
+
private static final String CLIENT_TYPE_PROPERTY_NAME = "org.onap.appc.adapter.saltstack.clientType";
+ private static final String SS_SERVER_HOSTNAME = "org.onap.appc.adapter.saltstack.host";
+ private static final String SS_SERVER_PORT = "org.onap.appc.adapter.saltstack.port";
private static final String SS_SERVER_USERNAME = "org.onap.appc.adapter.saltstack.userName";
private static final String SS_SERVER_PASSWORD = "org.onap.appc.adapter.saltstack.userPasswd";
private static final String SS_SERVER_SSH_KEY = "org.onap.appc.adapter.saltstack.sshKey";
@@ -134,7 +139,7 @@ public class SaltstackAdapterImpl implements SaltstackAdapter {
* Returns the symbolic name of the adapter
*
* @return The adapter name
- * @see org.onap.appc.adapter.rest.SaltstackAdapter#getAdapterName()
+ * @see SaltstackAdapter#getAdapterName()
*/
@Override
public String getAdapterName() {
@@ -146,7 +151,7 @@ public class SaltstackAdapterImpl implements SaltstackAdapter {
this.timeout = timeout;
}
/**
- * @param rc Method posts info to Context memory in case of an error and throws a
+ * Method posts info to Context memory in case of an error and throws a
* SvcLogicException causing SLI to register this as a failure
*/
@SuppressWarnings("static-method")
@@ -182,22 +187,32 @@ public class SaltstackAdapterImpl implements SaltstackAdapter {
String clientType = props.getProperty(CLIENT_TYPE_PROPERTY_NAME);
logger.info("Saltstack ssh client type set to " + clientType);
- if ("BASIC".equals(clientType)) {
+ if ("BASIC".equalsIgnoreCase(clientType)) {
logger.info("Creating ssh client connection");
// set path to keystore file
- String trustStoreFile = props.getProperty(SS_SERVER_USERNAME);
- String key = props.getProperty(SS_SERVER_PASSWORD);
- //TODO: Connect to SSH Saltstack server (using username and password) and return client to execute command
- sshClient = null;
- } else if ("SSH_CERT".equals(clientType)) {
+ String sshHost = props.getProperty(SS_SERVER_HOSTNAME);
+ String sshPort = props.getProperty(SS_SERVER_PORT);
+ String sshUserName = props.getProperty(SS_SERVER_USERNAME);
+ String sshPassword = props.getProperty(SS_SERVER_PASSWORD);
+ sshClient = new ConnectionBuilder(sshHost, sshPort, sshUserName, sshPassword);
+ } else if ("SSH_CERT".equalsIgnoreCase(clientType)) {
// set path to keystore file
- String key = props.getProperty(SS_SERVER_SSH_KEY);
- logger.info("Creating ssh client with ssh KEY from " + key);
- //TODO: Connect to SSH Saltstack server (using SSH Key) and return client to execute command
- sshClient = null;
+ String sshKey = props.getProperty(SS_SERVER_SSH_KEY);
+ String sshHost = props.getProperty(SS_SERVER_HOSTNAME);
+ String sshPort = props.getProperty(SS_SERVER_PORT);
+ logger.info("Creating ssh client with ssh KEY from " + sshKey);
+ sshClient = new ConnectionBuilder(sshHost, sshPort, sshKey);
+ } else if ("BOTH".equalsIgnoreCase(clientType)) {
+ // set path to keystore file
+ String sshKey = props.getProperty(SS_SERVER_SSH_KEY);
+ String sshHost = props.getProperty(SS_SERVER_HOSTNAME);
+ String sshUserName = props.getProperty(SS_SERVER_USERNAME);
+ String sshPassword = props.getProperty(SS_SERVER_PASSWORD);
+ String sshPort = props.getProperty(SS_SERVER_PORT);
+ logger.info("Creating ssh client with ssh KEY from " + sshKey);
+ sshClient = new ConnectionBuilder(sshHost, sshPort, sshUserName, sshPassword, sshKey);
} else {
- logger.info("Creating ssh client without any Auth");
- //TODO: Connect to SSH Saltstack server without any Auth
+ logger.info("No saltstack-adapter.properties defined so reading from DG props");
sshClient = null;
}
} catch (Exception e) {
@@ -214,7 +229,49 @@ public class SaltstackAdapterImpl implements SaltstackAdapter {
// org.onap.appc.adapter.saltstack.req.Id : a unique uuid to reference the request
@Override
public void reqExecCommand(Map<String, String> params, SvcLogicContext ctx) throws SvcLogicException {
- //TODO: to implement
+ String reqID;
+ SaltstackResult testResult;
+ if (sshClient == null){
+ logger.info("saltstack-adapter.properties not defined so reading saltstack host and " +
+ "auth details from DG's parameters");
+ String sshHost = messageProcessor.reqHostNameResult(params);
+ String sshPort = messageProcessor.reqPortResult(params);
+ String sshUserName = messageProcessor.reqUserNameResult(params);
+ String sshPassword = messageProcessor.reqPasswordResult(params);
+ logger.info("Creating ssh client with BASIC Auth");
+ if(!testMode)
+ sshClient = new ConnectionBuilder(sshHost, sshPort, sshUserName, sshPassword);
+ }
+ try {
+ reqID = params.get("Id");
+ String commandToExecute = params.get("cmd");
+ testResult = execCommand(params, commandToExecute);
+ testResult = messageProcessor.parseResponse(ctx, reqID, testResult);
+
+ // Check status of test request returned by Agent
+ if (testResult.getStatusCode() == SaltstackResultCodes.FINAL_SUCCESS.getValue()) {
+ logger.info(String.format("Execution of request-ID : %s successful.", reqID));
+ testResult.setResults("Success");
+ } else {
+ doFailure(ctx, testResult.getStatusCode(), "Request for execution of command failed. Reason = " + testResult.getStatusMessage());
+ return;
+ }
+ } catch (SvcLogicException e) {
+ logger.error(APPC_EXCEPTION_CAUGHT, e);
+ doFailure(ctx, SaltstackResultCodes.UNKNOWN_EXCEPTION.getValue(),
+ "Request for execution of command failed. Reason = "
+ + e.getMessage());
+ return;
+ } catch (Exception e) {
+ logger.error("Exception caught", e);
+ doFailure(ctx, SaltstackResultCodes.INVALID_COMMAND.getValue(),
+ "Request for execution of command failed. Reason = "
+ + e.getMessage());
+ return;
+ }
+ ctx.setAttribute(RESULT_CODE_ATTRIBUTE_NAME, Integer.toString(testResult.getStatusCode()));
+ ctx.setAttribute(MESSAGE_ATTRIBUTE_NAME, testResult.getResults());
+ ctx.setAttribute(ID_ATTRIBUTE_NAME, reqID);
}
/**
@@ -243,4 +300,24 @@ public class SaltstackAdapterImpl implements SaltstackAdapter {
//TODO: to implement
}
+
+ public SaltstackResult execCommand(Map<String, String> params, String commandToExecute){
+ SaltstackResult testResult;
+ if (params.get(CONNECTION_RETRY_DELAY) != null && params.get(CONNECTION_RETRY_COUNT) != null) {
+ int retryDelay = Integer.parseInt(params.get(CONNECTION_RETRY_DELAY));
+ int retryCount = Integer.parseInt(params.get(CONNECTION_RETRY_COUNT));
+ if(!testMode)
+ testResult = sshClient.connectNExecute(commandToExecute, retryCount, retryDelay);
+ else {
+ testResult = testServer.MockReqExec(params);
+ }
+ } else {
+ if(!testMode)
+ testResult = sshClient.connectNExecute(commandToExecute);
+ else {
+ testResult = testServer.MockReqExec(params);
+ }
+ }
+ return testResult;
+ }
}
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SshConnection.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SshConnection.java
new file mode 100644
index 00000000..41e6102d
--- /dev/null
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/impl/SshConnection.java
@@ -0,0 +1,250 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP : APPC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Copyright (C) 2017 Amdocs
+ * =============================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.ccsdk.sli.adaptors.saltstack.impl;
+
+import org.onap.appc.encryption.EncryptionTool;
+import org.apache.sshd.ClientChannel;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.client.channel.ChannelExec;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.client.future.OpenFuture;
+import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+
+import com.att.eelf.configuration.EELFLogger;
+import com.att.eelf.configuration.EELFManager;
+import org.onap.ccsdk.sli.adaptors.saltstack.model.SaltstackResult;
+import org.onap.ccsdk.sli.adaptors.saltstack.model.SaltstackResultCodes;
+import org.onap.ccsdk.sli.core.sli.SvcLogicException;
+
+import java.io.OutputStream;
+import java.security.KeyPair;
+
+/**
+ * Implementation of SshConnection interface based on Apache MINA SSHD library.
+ */
+class SshConnection {
+
+ private static final EELFLogger logger = EELFManager.getInstance().getApplicationLogger();
+
+ private static final long AUTH_TIMEOUT = 60000;
+ private static final long EXEC_TIMEOUT = 120000;
+
+ private String host;
+ private int port;
+ private String username;
+ private String password;
+ private long timeout = EXEC_TIMEOUT;
+ private String keyFile;
+ private SshClient sshClient;
+ private ClientSession clientSession;
+
+ public static final int DEFAULT_CONNECTION_RETRY_DELAY = 60;
+ public static final int DEFAULT_CONNECTION_RETRY_COUNT = 5;
+
+ public SshConnection(String host, int port, String username, String password, String keyFile) {
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ this.keyFile = keyFile;
+ }
+
+ public SshConnection(String host, int port, String username, String password) {
+ this(host, port, username, password, null);
+ }
+
+ public SshConnection(String host, int port, String keyFile) {
+ this(host, port, null, null, keyFile);
+ }
+
+ public SaltstackResult connect() {
+ SaltstackResult result = new SaltstackResult();
+ sshClient = SshClient.setUpDefaultClient();
+ sshClient.start();
+ try {
+ clientSession =
+ sshClient.connect(EncryptionTool.getInstance().decrypt(username), host, port).await().getSession();
+ if (password != null) {
+ clientSession.addPasswordIdentity(EncryptionTool.getInstance().decrypt(password));
+ }
+ if (keyFile != null) {
+ KeyPairProvider keyPairProvider = new FileKeyPairProvider(new String[] {
+ keyFile
+ });
+ KeyPair keyPair = keyPairProvider.loadKeys().iterator().next();
+ clientSession.addPublicKeyIdentity(keyPair);
+ }
+ AuthFuture authFuture = clientSession.auth();
+ authFuture.await(AUTH_TIMEOUT);
+ if (!authFuture.isSuccess()) {
+ String errMessage = "Error establishing ssh connection to [" + username + "@" + host + ":" + port
+ + "]. Authentication failed.";
+ result.setStatusCode(SaltstackResultCodes.USER_UNAUTHORIZED.getValue());
+ result.setStatusMessage(errMessage);
+ }
+ } catch (RuntimeException e) {
+ String errMessage = "Error establishing ssh connection to [" + username + "@" + host + ":" + port + "]." +
+ "Runtime Exception : "+ e.getMessage();
+ result.setStatusCode(SaltstackResultCodes.UNKNOWN_EXCEPTION.getValue());
+ result.setStatusMessage(errMessage);
+ } catch (Exception e) {
+ String errMessage = "Error establishing ssh connection to [" + username + "@" + host + ":" + port + "]." +
+ "Host Unknown : " + e.getMessage();
+ result.setStatusCode(SaltstackResultCodes.HOST_UNKNOWN.getValue());
+ result.setStatusMessage(errMessage);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("SSH: connected to [" + toString() + "]");
+ }
+ result.setStatusCode(SaltstackResultCodes.SUCCESS.getValue());
+ return result;
+ }
+
+ public SaltstackResult connectWithRetry(int retryCount, int retryDelay) {
+ int retriesLeft;
+ SaltstackResult result = new SaltstackResult();
+ if(retryCount == 0)
+ retryCount = DEFAULT_CONNECTION_RETRY_COUNT;
+ if(retryDelay == 0)
+ retryDelay = DEFAULT_CONNECTION_RETRY_DELAY;
+ retriesLeft = retryCount + 1;
+ do {
+ try {
+ result = this.connect();
+ break;
+ } catch (RuntimeException e) {
+ if (retriesLeft > 1) {
+ logger.debug("SSH Connection failed. Waiting for change in server's state.");
+ waitForConnection(retryDelay);
+ retriesLeft--;
+ logger.debug("Retrying SSH connection. Attempt [" + Integer.toString(retryCount - retriesLeft + 1)
+ + "] out of [" + retryCount + "]");
+ } else {
+ throw e;
+ }
+ }
+ } while (retriesLeft > 0);
+ return result;
+ }
+
+ public void disconnect() {
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("SSH: disconnecting from [" + toString() + "]");
+ }
+ clientSession.close(false);
+ } finally {
+ if (sshClient != null) {
+ sshClient.stop();
+ }
+ }
+ }
+
+ public void setExecTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ public SaltstackResult execCommand(String cmd, OutputStream out, OutputStream err) {
+ return execCommand(cmd, out, err, false);
+ }
+
+ public SaltstackResult execCommandWithPty(String cmd, OutputStream out) {
+ return execCommand(cmd, out, out, true);
+ }
+
+ private SaltstackResult execCommand(String cmd, OutputStream out, OutputStream err, boolean usePty) {
+ SaltstackResult result = new SaltstackResult();
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("SSH: executing command");
+ }
+ ChannelExec client = clientSession.createExecChannel(cmd);
+ client.setUsePty(usePty); // use pseudo-tty?
+ client.setOut(out);
+ client.setErr(err);
+ OpenFuture openFuture = client.open();
+ int exitStatus;
+ try {
+ client.waitFor(ClientChannel.CLOSED, timeout);
+ openFuture.verify();
+ Integer exitStatusI = client.getExitStatus();
+ if (exitStatusI == null) {
+ String errMessage = "Error executing command [" + cmd + "] over SSH [" + username + "@" + host
+ + ":" + port + "]. SSH operation timed out.";
+ result.setStatusCode(SaltstackResultCodes.OPERATION_TIMEOUT.getValue());
+ result.setStatusMessage(errMessage);
+ return result;
+ }
+ exitStatus = exitStatusI;
+ } finally {
+ client.close(false);
+ }
+ result.setSshExitStatus(exitStatus);
+ return result;
+ } catch (RuntimeException e) {
+ String errMessage = "Error establishing ssh connection to [" + username + "@" + host + ":" + port + "]." +
+ "Runtime Exception : "+ e.getMessage();
+ result.setStatusCode(SaltstackResultCodes.UNKNOWN_EXCEPTION.getValue());
+ result.setStatusMessage(errMessage);
+ } catch (Exception e1) {
+ String errMessage = "Error executing command [" + cmd + "] over SSH [" + username + "@" + host + ":" +
+ port + "]"+ e1.getMessage();
+ result.setStatusCode(SaltstackResultCodes.UNKNOWN_EXCEPTION.getValue());
+ result.setStatusMessage(errMessage);
+ }
+ result.setStatusCode(SaltstackResultCodes.SUCCESS.getValue());
+ return result;
+ }
+
+ private void waitForConnection(int retryDelay) {
+ long time = retryDelay * 1000L;
+ long future = System.currentTimeMillis() + time;
+ if (time != 0) {
+ while (System.currentTimeMillis() < future && time > 0) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ /*
+ * This is rare, but it can happen if another thread interrupts us while we are sleeping. In that
+ * case, the thread is resumed before the delay time has actually expired, so re-calculate the
+ * amount of delay time needed and reenter the sleep until we get to the future time.
+ */
+ time = future - System.currentTimeMillis();
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ String address = host;
+ if (username != null) {
+ address = username + '@' + address + ':' + port;
+ }
+ return address;
+ }
+}
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/JsonParser.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/JsonParser.java
new file mode 100644
index 00000000..f33799fd
--- /dev/null
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/JsonParser.java
@@ -0,0 +1,89 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * openECOMP : SDN-C
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.ccsdk.sli.adaptors.saltstack.model;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public final class JsonParser {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonParser.class);
+
+ private JsonParser() {
+ // Preventing instantiation of the same.
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Map<String, String> convertToProperties(String s)
+ throws JSONException {
+
+ checkNotNull(s, "Input should not be null.");
+
+ JSONObject json = new JSONObject(s);
+ Map<String, Object> wm = new HashMap<>();
+ Iterator<String> ii = json.keys();
+ while (ii.hasNext()) {
+ String key1 = ii.next();
+ wm.put(key1, json.get(key1));
+ }
+
+ Map<String, String> mm = new HashMap<>();
+
+ while (!wm.isEmpty())
+ for (String key : new ArrayList<>(wm.keySet())) {
+ Object o = wm.get(key);
+ wm.remove(key);
+
+ if (o instanceof Boolean || o instanceof Number || o instanceof String) {
+ mm.put(key, o.toString());
+
+ log.info("Added property: {} : {}", key, o.toString());
+ } else if (o instanceof JSONObject) {
+ JSONObject jo = (JSONObject) o;
+ Iterator<String> i = jo.keys();
+ while (i.hasNext()) {
+ String key1 = i.next();
+ wm.put(key + "." + key1, jo.get(key1));
+ }
+ } else if (o instanceof JSONArray) {
+ JSONArray ja = (JSONArray) o;
+ mm.put(key + "_length", String.valueOf(ja.length()));
+
+ log.info("Added property: {}_length: {}", key, String.valueOf(ja.length()));
+
+ for (int i = 0; i < ja.length(); i++)
+ wm.put(key + '[' + i + ']', ja.get(i));
+ }
+ }
+ return mm;
+ }
+}
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackMessageParser.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackMessageParser.java
index 5a548f84..1ea31516 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackMessageParser.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackMessageParser.java
@@ -28,15 +28,21 @@ package org.onap.ccsdk.sli.adaptors.saltstack.model;
* This module implements the APP-C/Saltstack Server interface
* based on the REST API specifications
*/
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
import org.onap.ccsdk.sli.core.sli.SvcLogicException;
import com.google.common.base.Strings;
import org.slf4j.Logger;
@@ -53,10 +59,10 @@ public class SaltstackMessageParser {
private static final String STATUS_CODE_KEY = "StatusCode";
private static final String SALTSTATE_NAME_KEY = "SaltStateName";
- private static final String AGENT_URL_KEY = "AgentUrl";
+ private static final String SS_AGENT_HOSTNAME_KEY = "HostName";
+ private static final String SS_AGENT_PORT_KEY = "Port";
private static final String PASS_KEY = "Password";
private static final String USER_KEY = "User";
- private static final String ID_KEY = "Id";
private static final String LOCAL_PARAMETERS_OPT_KEY = "LocalParameters";
private static final String FILE_PARAMETERS_OPT_KEY = "FileParameters";
@@ -80,7 +86,7 @@ public class SaltstackMessageParser {
*
*/
public JSONObject reqMessage(Map<String, String> params) throws SvcLogicException {
- final String[] mandatoryTestParams = {AGENT_URL_KEY, SALTSTATE_NAME_KEY, USER_KEY, PASS_KEY};
+ final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SALTSTATE_NAME_KEY, USER_KEY, PASS_KEY};
final String[] optionalTestParams = {ENV_PARAMETERS_OPT_KEY, NODE_LIST_OPT_KEY, LOCAL_PARAMETERS_OPT_KEY,
TIMEOUT_OPT_KEY, VERSION_OPT_KEY, FILE_PARAMETERS_OPT_KEY, ACTION_OPT_KEY};
@@ -95,7 +101,7 @@ public class SaltstackMessageParser {
// Generate a unique uuid for the test
String reqId = UUID.randomUUID().toString();
- jsonPayload.put(ID_KEY, reqId);
+ jsonPayload.put(SS_AGENT_HOSTNAME_KEY, reqId);
return jsonPayload;
}
@@ -103,60 +109,121 @@ public class SaltstackMessageParser {
/**
* Method that validates that the Map has enough information
* to query Saltstack server for a result. If so, it returns
- * the appropriate url, else an empty string.
+ * the appropriate PORT number.
*/
- public String reqUriResult(Map<String, String> params) throws SvcLogicException {
+ public String reqPortResult(Map<String, String> params) throws SvcLogicException {
- final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
+ final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
for (String key : mandatoryTestParams) {
throwIfMissingMandatoryParam(params, key);
}
- return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetResult";
+ return params.get(SS_AGENT_PORT_KEY);
}
/**
* Method that validates that the Map has enough information
- * to query Saltstack server for logs. If so, it populates the appropriate
- * returns the appropriate url, else an empty string.
+ * to query Saltstack server for a result. If so, it returns
+ * the appropriate HOST name.
*/
- public String reqUriLog(Map<String, String> params) throws SvcLogicException {
+ public String reqHostNameResult(Map<String, String> params) throws SvcLogicException {
- final String[] mandatoryTestParams = {AGENT_URL_KEY, ID_KEY, USER_KEY, PASS_KEY};
+ final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
- for (String mandatoryParam : mandatoryTestParams) {
- throwIfMissingMandatoryParam(params, mandatoryParam);
+ for (String key : mandatoryTestParams) {
+ throwIfMissingMandatoryParam(params, key);
}
- return params.get(AGENT_URL_KEY) + "?Id=" + params.get(ID_KEY) + "&Type=GetLog";
+ return params.get(SS_AGENT_HOSTNAME_KEY);
}
/**
- * This method parses response from the Saltstack Server when we do a post
- * and returns an SaltstackResult object.
+ * Method that validates that the Map has enough information
+ * to query Saltstack server for a result. If so, it returns
+ * the appropriate Saltstack server login user name.
*/
- public SaltstackResult parsePostResponse(String input) throws SvcLogicException {
- SaltstackResult saltstackResult;
- try {
- JSONObject postResponse = new JSONObject(input);
+ public String reqUserNameResult(Map<String, String> params) throws SvcLogicException {
- int code = postResponse.getInt(STATUS_CODE_KEY);
- String msg = postResponse.getString(STATUS_MESSAGE_KEY);
+ final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
- int initResponseValue = SaltstackResultCodes.INITRESPONSE.getValue();
- boolean validCode = SaltstackResultCodes.CODE.checkValidCode(initResponseValue, code);
- if (!validCode) {
- throw new SvcLogicException("Invalid InitResponse code = " + code + " received. MUST be one of "
- + SaltstackResultCodes.CODE.getValidCodes(initResponseValue));
- }
+ for (String key : mandatoryTestParams) {
+ throwIfMissingMandatoryParam(params, key);
+ }
+ return params.get(USER_KEY);
+ }
+
+ /**
+ * Method that validates that the Map has enough information
+ * to query Saltstack server for a result. If so, it returns
+ * the appropriate Saltstack server login password.
+ */
+ public String reqPasswordResult(Map<String, String> params) throws SvcLogicException {
- saltstackResult = new SaltstackResult(code, msg);
+ final String[] mandatoryTestParams = {SS_AGENT_HOSTNAME_KEY, SS_AGENT_PORT_KEY, USER_KEY, PASS_KEY};
+ for (String key : mandatoryTestParams) {
+ throwIfMissingMandatoryParam(params, key);
+ }
+ return params.get(PASS_KEY);
+ }
+
+ /**
+ * This method parses response from the Saltstack Server when we do a post
+ * and returns an SaltstackResult object.
+ */
+ public SaltstackResult parseResponse(SvcLogicContext ctx, String pfx, SaltstackResult saltstackResult) {
+ int code = saltstackResult.getStatusCode();
+ if (code != SaltstackResultCodes.SUCCESS.getValue()) {
+ return saltstackResult;
+ }
+ try {
+ File file = new File(saltstackResult.getOutputFileName());
+ InputStream in = new FileInputStream(file);
+ byte[] data = new byte[(int) file.length()];
+ in.read(data);
+ String str = new String(data, "UTF-8");
+ in.close();
+ Map<String, String> mm = JsonParser.convertToProperties(str);
+ if (mm != null) {
+ for (Map.Entry<String,String> entry : mm.entrySet()) {
+ ctx.setAttribute(pfx + entry.getKey(), entry.getValue());
+ LOGGER.info("+++ " + pfx + entry.getKey() + ": [" + entry.getValue() + "]");
+ }
+ }
+ } catch (FileNotFoundException e){
+ return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
+ + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
} catch (JSONException e) {
- saltstackResult = new SaltstackResult(600, "Error parsing response = " + input + ". Error = " + e.getMessage());
+ LOGGER.info("Output not in JSON format");
+ return putToProperties(ctx, pfx, saltstackResult);
+ } catch (Exception e) {
+ return new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
+ + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
}
+ saltstackResult.setStatusCode(SaltstackResultCodes.FINAL_SUCCESS.getValue());
return saltstackResult;
}
+ public SaltstackResult putToProperties(SvcLogicContext ctx, String pfx, SaltstackResult saltstackResult) {
+ try {
+ File file = new File(saltstackResult.getOutputFileName());
+ InputStream in = new FileInputStream(file);
+ Properties prop = new Properties();
+ prop.load(in);
+ ctx.setAttribute(pfx + "completeResult", prop.toString());
+ for (Object key : prop.keySet()) {
+ String name = (String) key;
+ String value = prop.getProperty(name);
+ if (value != null && value.trim().length() > 0) {
+ ctx.setAttribute(pfx + name, value.trim());
+ LOGGER.info("+++ " + pfx + name + ": [" + value + "]");
+ }
+ }
+ } catch (Exception e) {
+ saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_RESPONSE_FILE.getValue(), "Error parsing response file = "
+ + saltstackResult.getOutputFileName() + ". Error = " + e.getMessage());
+ }
+ return saltstackResult;
+ }
/**
* This method parses response from an Saltstack server when we do a GET for a result
* and returns an SaltstackResult object.
@@ -169,8 +236,8 @@ public class SaltstackMessageParser {
JSONObject postResponse = new JSONObject(input);
saltstackResult = parseGetResponseNested(saltstackResult, postResponse);
} catch (JSONException e) {
- saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_PAYLOAD.getValue(),
- "Error parsing response = " + input + ". Error = " + e.getMessage(), "");
+ saltstackResult = new SaltstackResult(SaltstackResultCodes.INVALID_COMMAND.getValue(),
+ "Error parsing response = " + input + ". Error = " + e.getMessage(), "", -1);
}
return saltstackResult;
}
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResult.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResult.java
index f1fb40d9..05873024 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResult.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResult.java
@@ -24,6 +24,8 @@
package org.onap.ccsdk.sli.adaptors.saltstack.model;
+import java.io.OutputStream;
+
/**
* Simple class to store code and message returned by POST/GET to an Saltstack Server
*/
@@ -34,19 +36,22 @@ public class SaltstackResult {
private int statusCode;
private String statusMessage;
private String results;
+ private String out;
+ private int sshExitStatus;
public SaltstackResult() {
- this(-1, EMPTY_VALUE, EMPTY_VALUE);
+ this(-1, EMPTY_VALUE, EMPTY_VALUE, -1);
}
public SaltstackResult(int code, String message) {
- this(code, message, EMPTY_VALUE);
+ this(code, message, EMPTY_VALUE, -1);
}
- public SaltstackResult(int code, String message, String result) {
+ public SaltstackResult(int code, String message, String result, int sshCode) {
statusCode = code;
statusMessage = message;
results = result;
+ sshExitStatus = sshCode;
}
public void setStatusCode(int code) {
@@ -67,6 +72,14 @@ public class SaltstackResult {
this.results = results;
}
+ public void setOutputFileName (String out) {
+ this.out = out;
+ }
+
+ public String getOutputFileName() {
+ return out;
+ }
+
public int getStatusCode() {
return this.statusCode;
}
@@ -78,4 +91,12 @@ public class SaltstackResult {
public String getResults() {
return this.results;
}
+
+ public int getSshExitStatus() {
+ return sshExitStatus;
+ }
+
+ public void setSshExitStatus(int sshExitStatus) {
+ this.sshExitStatus = sshExitStatus;
+ }
}
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResultCodes.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResultCodes.java
index e520dda6..ab88c212 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResultCodes.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackResultCodes.java
@@ -44,9 +44,11 @@ public enum SaltstackResultCodes {
HOST_UNKNOWN(625),
USER_UNAUTHORIZED(613),
UNKNOWN_EXCEPTION(699),
+ OPERATION_TIMEOUT(659),
SSL_EXCEPTION(697),
- INVALID_PAYLOAD(698),
+ INVALID_COMMAND(698),
INVALID_RESPONSE(601),
+ INVALID_RESPONSE_FILE(600),
PENDING(100),
REJECTED(101),
FINAL_SUCCESS(200),
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackServerEmulator.java b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackServerEmulator.java
index a9bf7e7c..9737efd3 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackServerEmulator.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/main/java/org/onap/ccsdk/sli/adaptors/saltstack/model/SaltstackServerEmulator.java
@@ -32,6 +32,7 @@
package org.onap.ccsdk.sli.adaptors.saltstack.model;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
@@ -56,14 +57,36 @@ public class SaltstackServerEmulator {
* Returns an saltstack object result. The response code is always the ssh code 200 (i.e connection successful)
* payload is json string as would be sent back by Saltstack Server
**/
+ public SaltstackResult MockReqExec(Map<String, String> params) {
+ SaltstackResult result = new SaltstackResult();
+
+ try {
+ if (params.get("Test") == "fail") {
+ result = rejectRequest(result, "Must provide a valid Id");
+ } else {
+ result = acceptRequest(result);
+ }
+ } catch (Exception e) {
+ logger.error("JSONException caught", e);
+ rejectRequest(result, e.getMessage());
+ }
+ return result;
+ }
+
+ /**
+ * Method that emulates the response from an Saltstack Server
+ * when presented with a request to execute a saltState
+ * Returns an saltstack object result. The response code is always the ssh code 200 (i.e connection successful)
+ * payload is json string as would be sent back by Saltstack Server
+ **/
//TODO: This class is to be altered completely based on the SALTSTACK server communicaiton.
- public SaltstackResult Connect(String agentUrl, String payload) {
+ public SaltstackResult Connect(Map<String, String> params) {
SaltstackResult result = new SaltstackResult();
try {
// Request must be a JSON object
- JSONObject message = new JSONObject(payload);
+ JSONObject message = new JSONObject();
if (message.isNull("Id")) {
rejectRequest(result, "Must provide a valid Id");
} else if (message.isNull(SALTSTATE_NAME)) {
@@ -120,19 +143,15 @@ public class SaltstackServerEmulator {
return getResult;
}
- private void rejectRequest(SaltstackResult result, String Message) {
- result.setStatusCode(200);
- JSONObject response = new JSONObject();
- response.put(STATUS_CODE, SaltstackResultCodes.REJECTED.getValue());
- response.put(STATUS_MESSAGE, Message);
- result.setStatusMessage(response.toString());
+ private SaltstackResult rejectRequest(SaltstackResult result, String Message) {
+ result.setStatusCode(SaltstackResultCodes.REJECTED.getValue());
+ result.setStatusMessage("Rejected");
+ return result;
}
- private void acceptRequest(SaltstackResult result) {
- result.setStatusCode(200);
- JSONObject response = new JSONObject();
- response.put(STATUS_CODE, SaltstackResultCodes.PENDING.getValue());
- response.put(STATUS_MESSAGE, "PENDING");
- result.setStatusMessage(response.toString());
+ private SaltstackResult acceptRequest(SaltstackResult result) {
+ result.setStatusCode(SaltstackResultCodes.SUCCESS.getValue());
+ result.setStatusMessage("Success");
+ return result;
}
} \ No newline at end of file
diff --git a/saltstack-adapter/saltstack-adapter-provider/src/test/java/org/onap/appc/adapter/impl/TestSaltstackAdapterImpl.java b/saltstack-adapter/saltstack-adapter-provider/src/test/java/org/onap/appc/adapter/impl/TestSaltstackAdapterImpl.java
index 5ca6e6ea..d7b33038 100644
--- a/saltstack-adapter/saltstack-adapter-provider/src/test/java/org/onap/appc/adapter/impl/TestSaltstackAdapterImpl.java
+++ b/saltstack-adapter/saltstack-adapter-provider/src/test/java/org/onap/appc/adapter/impl/TestSaltstackAdapterImpl.java
@@ -71,26 +71,173 @@ public class TestSaltstackAdapterImpl {
svcContext = null;
}
- @Test
- public void reqExecCommand_shouldSetPending() throws IllegalStateException, IllegalArgumentException {
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetFailed() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "fail");
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("101", status);
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetUserFailed() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("Password", "test");
+ params.put("Test", "fail");
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("101", status);
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetHostFailed() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "fail");
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("101", status);
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetPortFailed() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("HostName", "test");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "fail");
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("101", status);
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetPasswordFailed() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Test", "fail");
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("101", status);
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetMandatoryFailed() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("Test", "fail");
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("101", status);
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetSuccess() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
params.put("PlaybookName", "test_playbook.yaml");
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "success");
+ try {
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("400", status);
+ } catch (NullPointerException e) {
+ fail(e.getMessage() + " Unknown exception encountered ");
+ }
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetSuccessWithRetry() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+ params.put("PlaybookName", "test_playbook.yaml");
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "success");
+ params.put("retryDelay", "10");
+ params.put("retryCount", "10");
try {
adapter.reqExecCommand(params, svcContext);
String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
- // System.out.println("Comparing " + PENDING + " and " + status);
- //assertEquals(PENDING, status);
- assertEquals(null, status);
- } catch (SvcLogicException e) {
+ assertEquals("400", status);
+ } catch (NullPointerException e) {
+ fail(e.getMessage() + " Unknown exception encountered ");
+ }
+ }
+
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetSuccessWithRetryZero() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("PlaybookName", "test_playbook.yaml");
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "success");
+ params.put("retryDelay", "0");
+ params.put("retryCount", "0");
+ try {
+ adapter.reqExecCommand(params, svcContext);
String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
- fail(e.getMessage() + " Code = " + status);
- } catch (Exception e) {
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("400", status);
+ } catch (NullPointerException e) {
fail(e.getMessage() + " Unknown exception encountered ");
}
}
+ @Test(expected = SvcLogicException.class)
+ public void reqExecCommand_shouldSetSuccessWithNoRetry() throws SvcLogicException,
+ IllegalStateException, IllegalArgumentException {
+
+ params.put("PlaybookName", "test_playbook.yaml");
+ params.put("HostName", "test");
+ params.put("Port", "10");
+ params.put("User", "test");
+ params.put("Password", "test");
+ params.put("Test", "success");
+ params.put("retryDelay", "-1");
+ params.put("retryCount", "-1");
+ try {
+ adapter.reqExecCommand(params, svcContext);
+ String status = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.code");
+ TestId = svcContext.getAttribute("org.onap.appc.adapter.saltstack.result.Id");
+ assertEquals("400", status);
+ } catch (NullPointerException e) {
+ fail(e.getMessage() + " Unknown exception encountered ");
+ }
+ }
@Test
public void reqExecSLS_shouldSetSuccess() throws IllegalStateException, IllegalArgumentException {