2 * Copyright © 2019 IBM.
4 * Modifications Copyright © 2018-2020 IBM, Bell Canada.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org.onap.ccsdk.cds.blueprintsprocessor.ssh.service
21 import org.apache.commons.io.output.TeeOutputStream
22 import org.apache.sshd.client.SshClient
23 import org.apache.sshd.client.channel.ChannelShell
24 import org.apache.sshd.client.channel.ClientChannelEvent
25 import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier
26 import org.apache.sshd.client.session.ClientSession
27 import org.onap.ccsdk.cds.blueprintsprocessor.ssh.BasicAuthSshClientProperties
28 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintProcessorException
29 import org.slf4j.LoggerFactory
30 import java.io.ByteArrayOutputStream
31 import java.io.IOException
32 import java.io.PipedInputStream
33 import java.io.PipedOutputStream
34 import java.util.ArrayList
35 import java.util.Collections
36 import java.util.EnumSet
37 import java.util.Scanner
39 open class BasicAuthSshClientService(private val basicAuthSshClientProperties: BasicAuthSshClientProperties) :
40 BlueprintSshClientService {
42 private val log = LoggerFactory.getLogger(BasicAuthSshClientService::class.java)!!
43 private val newLine = "\n".toByteArray()
44 private var channel: ChannelShell? = null
45 private var teeOutput: TeeOutputStream? = null
47 private lateinit var sshClient: SshClient
48 private lateinit var clientSession: ClientSession
50 override suspend fun startSessionNB(): ClientSession {
51 sshClient = SshClient.setUpDefaultClient()
52 sshClient.serverKeyVerifier = AcceptAllServerKeyVerifier.INSTANCE
54 log.debug("SSH Client Service started successfully")
56 clientSession = sshClient.connect(
57 basicAuthSshClientProperties.username, basicAuthSshClientProperties.host,
58 basicAuthSshClientProperties.port
59 ).verify(basicAuthSshClientProperties.connectionTimeOut).session
61 clientSession.addPasswordIdentity(basicAuthSshClientProperties.password)
62 clientSession.auth().verify(basicAuthSshClientProperties.connectionTimeOut)
65 log.info("SSH client session($clientSession) created")
69 private fun startChannel() {
71 channel = clientSession.createShellChannel()
72 val pipedIn = PipedOutputStream()
73 channel!!.setIn(PipedInputStream(pipedIn))
74 teeOutput = TeeOutputStream(ByteArrayOutputStream(), pipedIn)
75 channel!!.out = ByteArrayOutputStream()
76 channel!!.err = ByteArrayOutputStream()
78 } catch (e: Exception) {
79 throw BlueprintProcessorException("Failed to start Shell channel: ${e.message}")
83 override suspend fun executeCommandsNB(commands: List<String>, timeOut: Long): List<CommandResult> {
84 val response = ArrayList<CommandResult>()
87 val commandsIterator = commands.iterator()
88 while (commandsIterator.hasNext() && !stopLoop) {
89 val command = commandsIterator.next()
90 log.debug("Executing host command($command) \n")
91 val result = executeCommand(command, timeOut)
93 // Once a command in the template has failed break out of the loop to stop executing further commands
94 if (!result.successful) {
95 log.debug("Template execution will stop because command ({}) has failed.", command)
99 } catch (e: Exception) {
100 throw BlueprintProcessorException("Failed to execute commands, below the error message : ${e.message}")
105 override suspend fun executeCommandNB(command: String, timeOut: Long): CommandResult {
106 val deviceOutput: String
107 var isSuccessful = true
109 teeOutput!!.write(command.toByteArray())
110 teeOutput!!.write(newLine)
112 deviceOutput = waitForPrompt(timeOut)
113 } catch (e: IOException) {
114 throw BlueprintProcessorException("Exception during command execution: ${e.message}", e)
117 if (detectFailure(deviceOutput)) {
121 val commandResult = CommandResult(command, deviceOutput, isSuccessful)
122 if (basicAuthSshClientProperties.logging) {
123 log.info("Command Response: ({}) $newLine", commandResult)
128 private fun waitForPrompt(timeOut: Long): String {
129 val waitMask = channel!!.waitFor(
130 Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)), timeOut
132 if (channel!!.out.toString().indexOfAny(arrayListOf("$", ">", "#")) <= 0 && waitMask.contains(ClientChannelEvent.TIMEOUT)) {
133 throw BlueprintProcessorException("Timeout: Failed to retrieve commands result in $timeOut ms")
135 val outputResult = channel!!.out.toString()
136 channel!!.out.flush()
140 override suspend fun closeSessionNB() {
141 if (channel != null) {
145 if (clientSession.isOpen && !clientSession.isClosing) {
146 clientSession.close()
149 if (sshClient.isStarted) {
152 log.debug("SSH Client Service stopped successfully")
155 // TODO filter output to check error message
156 private fun detectFailure(output: String): Boolean {
157 if (output.isNotBlank()) {
158 // Output can be multiline, need to check if any of the line starts with %
159 Scanner(output).use { scanner ->
160 while (scanner.hasNextLine()) {
161 val temp = scanner.nextLine()
162 if (temp.isNotBlank() && (
163 temp.trim { it <= ' ' }.startsWith("%") ||
164 temp.trim { it <= ' ' }.startsWith("syntax error")
176 data class CommandResult(val command: String, val deviceOutput: String, val successful: Boolean)