diff options
author | Steve Siani <alphonse.steve.siani.djissitchi@ibm.com> | 2019-11-19 22:44:58 -0500 |
---|---|---|
committer | Steve Siani <alphonse.steve.siani.djissitchi@ibm.com> | 2019-12-09 21:44:01 -0500 |
commit | 210cc0ab1bdff78fcefd80ad59e340635a21b063 (patch) | |
tree | a3bf62582a01890d33798c2090d6df6829ee0ef3 /ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin | |
parent | a3247099971acd800a753cdfee51f84ec7679a78 (diff) |
Cli executor doesn't keep/support execution context
Issue-ID: CCSDK-1927
Signed-off-by: Steve Siani <alphonse.steve.siani.djissitchi@ibm.com>
Change-Id: Ib417bfd62662676fe7520a5500df82ade716f66c
Diffstat (limited to 'ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin')
2 files changed, 107 insertions, 36 deletions
diff --git a/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BasicAuthSshClientService.kt b/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BasicAuthSshClientService.kt index 61baaa1ef..2885d6528 100644 --- a/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BasicAuthSshClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BasicAuthSshClientService.kt @@ -1,6 +1,8 @@ /* * Copyright © 2019 IBM. * + * Modifications Copyright © 2018-2019 IBM, Bell Canada + * * 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 @@ -16,9 +18,9 @@ package org.onap.ccsdk.cds.blueprintsprocessor.ssh.service +import org.apache.commons.io.output.TeeOutputStream import org.apache.sshd.client.SshClient -import org.apache.sshd.client.channel.ChannelExec -import org.apache.sshd.client.channel.ClientChannel +import org.apache.sshd.client.channel.ChannelShell import org.apache.sshd.client.channel.ClientChannelEvent import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier import org.apache.sshd.client.session.ClientSession @@ -26,75 +28,142 @@ import org.onap.ccsdk.cds.blueprintsprocessor.ssh.BasicAuthSshClientProperties import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.PipedInputStream +import java.io.PipedOutputStream import java.util.Collections import java.util.EnumSet +import java.util.Scanner +import java.util.ArrayList open class BasicAuthSshClientService(private val basicAuthSshClientProperties: BasicAuthSshClientProperties) : - BlueprintSshClientService { + BlueprintSshClientService { private val log = LoggerFactory.getLogger(BasicAuthSshClientService::class.java)!! + private val newLine = "\n".toByteArray() + private var channel: ChannelShell? = null + private var teeOutput: TeeOutputStream? = null private lateinit var sshClient: SshClient private lateinit var clientSession: ClientSession - var channel: ChannelExec? = null override suspend fun startSessionNB(): ClientSession { sshClient = SshClient.setUpDefaultClient() sshClient.serverKeyVerifier = AcceptAllServerKeyVerifier.INSTANCE sshClient.start() log.debug("SSH Client Service started successfully") + clientSession = sshClient.connect( - basicAuthSshClientProperties.username, basicAuthSshClientProperties.host, - basicAuthSshClientProperties.port - ) - .verify(basicAuthSshClientProperties.connectionTimeOut) - .session + basicAuthSshClientProperties.username, basicAuthSshClientProperties.host, + basicAuthSshClientProperties.port).verify(basicAuthSshClientProperties.connectionTimeOut).session clientSession.addPasswordIdentity(basicAuthSshClientProperties.password) clientSession.auth().verify(basicAuthSshClientProperties.connectionTimeOut) + startChannel() + log.info("SSH client session($clientSession) created") return clientSession } - override suspend fun executeCommandsNB(commands: List<String>, timeOut: Long): String { - val buffer = StringBuffer() + private fun startChannel() { try { - commands.forEach { command -> - buffer.append("\nCommand : $command") - buffer.append("\n" + executeCommandNB(command, timeOut)) + channel = clientSession.createShellChannel() + val pipedIn = PipedOutputStream() + channel!!.setIn(PipedInputStream(pipedIn)) + teeOutput = TeeOutputStream(ByteArrayOutputStream(), pipedIn) + channel!!.out = ByteArrayOutputStream() + channel!!.err = ByteArrayOutputStream() + channel!!.open() + } catch (e: Exception) { + throw BluePrintProcessorException("Failed to start Shell channel: ${e.message}") + } + } + + override suspend fun executeCommandsNB(commands: List <String>, timeOut: Long): List<CommandResult> { + val response = ArrayList<CommandResult>() + try { + var stopLoop = false + val commandsIterator = commands.iterator() + while (commandsIterator.hasNext() && !stopLoop) { + val command = commandsIterator.next() + log.debug("Executing host command($command) \n") + val result = executeCommand(command, timeOut) + response.add(result) + // Once a command in the template has failed break out of the loop to stop executing further commands + if (!result.successful) { + log.debug("Template execution will stop because command ({}) has failed.", command) + stopLoop = true + } } } catch (e: Exception) { - throw BluePrintProcessorException("Failed to execute commands, below the output : $buffer") + throw BluePrintProcessorException("Failed to execute commands, below the error message : ${e.message}") } - return buffer.toString() + return response } - override suspend fun executeCommandNB(command: String, timeOut: Long): String { - log.debug("Executing host($clientSession) command($command)") + override suspend fun executeCommandNB(command: String, timeOut: Long): CommandResult { + val deviceOutput: String + var isSuccessful = true + try { + teeOutput!!.write(command.toByteArray()) + teeOutput!!.write(newLine) + teeOutput!!.flush() + deviceOutput = waitForPrompt(timeOut) + } catch (e: IOException) { + throw BluePrintProcessorException("Exception during command execution: ${e.message}", e) + } + + if (detectFailure(deviceOutput)) { + isSuccessful = false + } - channel = clientSession.createExecChannel(command) - checkNotNull(channel) { "failed to create Channel for the command : $command" } + val commandResult = CommandResult(command, deviceOutput, isSuccessful) + log.info("Command Response: ({}) $newLine", commandResult) + return commandResult + } - // TODO("Convert to streaming ") - val outputStream = ByteArrayOutputStream() - channel!!.out = outputStream - channel!!.err = outputStream - channel!!.open().await() - val waitMask = channel!!.waitFor(Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)), timeOut) - if (waitMask.contains(ClientChannelEvent.TIMEOUT)) { - throw BluePrintProcessorException("Failed to retrieve command result in time: $command") + private fun waitForPrompt(timeOut: Long): String { + val waitMask = channel!!.waitFor( + Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)), timeOut) + if (channel!!.out.toString().indexOfAny(arrayListOf("$", ">", "#")) <= 0 && waitMask.contains(ClientChannelEvent.TIMEOUT)) { + throw BluePrintProcessorException("Timeout: Failed to retrieve commands result in $timeOut ms") } - val exitStatus = channel!!.exitStatus - ClientChannel.validateCommandExitStatusCode(command, exitStatus!!) - return outputStream.toString() + val outputResult = channel!!.out.toString() + channel!!.out.flush() + return outputResult } override suspend fun closeSessionNB() { - if (channel != null) + if (channel != null) { channel!!.close() + } + + if (clientSession.isOpen && !clientSession.isClosing) { + clientSession.close() + } + if (sshClient.isStarted) { sshClient.stop() } log.debug("SSH Client Service stopped successfully") } + + // TODO filter output to check error message + private fun detectFailure(output: String): Boolean { + if (output.isNotBlank()) { + // Output can be multiline, need to check if any of the line starts with % + Scanner(output).use { scanner -> + while (scanner.hasNextLine()) { + val temp = scanner.nextLine() + if (temp.isNotBlank() && (temp.trim { it <= ' ' }.startsWith("%") || + temp.trim { it <= ' ' }.startsWith("syntax error"))) { + return true + } + } + } + } + return false + } } + +data class CommandResult(val command: String, val deviceOutput: String, val successful: Boolean) diff --git a/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BlueprintSshClientService.kt b/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BlueprintSshClientService.kt index 724c4277d..27ebf50bc 100644 --- a/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BlueprintSshClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/ssh-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ssh/service/BlueprintSshClientService.kt @@ -1,6 +1,8 @@ /* * Copyright © 2019 IBM. * + * Modifications Copyright © 2018-2019 IBM, Bell Canada + * * 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 @@ -25,11 +27,11 @@ interface BlueprintSshClientService { startSessionNB() } - fun executeCommands(commands: List<String>, timeOut: Long): String = runBlocking { + fun executeCommands(commands: List<String>, timeOut: Long): List<CommandResult> = runBlocking { executeCommandsNB(commands, timeOut) } - fun executeCommand(command: String, timeOut: Long): String = runBlocking { + fun executeCommand(command: String, timeOut: Long): CommandResult = runBlocking { executeCommandNB(command, timeOut) } @@ -39,9 +41,9 @@ interface BlueprintSshClientService { suspend fun startSessionNB(): ClientSession - suspend fun executeCommandsNB(commands: List<String>, timeOut: Long): String + suspend fun executeCommandsNB(commands: List<String>, timeOut: Long): List<CommandResult> - suspend fun executeCommandNB(command: String, timeOut: Long): String + suspend fun executeCommandNB(command: String, timeOut: Long): CommandResult suspend fun closeSessionNB() } |