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.Collections
35 import java.util.EnumSet
36 import java.util.Scanner
37 import java.util.ArrayList
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).verify(basicAuthSshClientProperties.connectionTimeOut).session
60 clientSession.addPasswordIdentity(basicAuthSshClientProperties.password)
61 clientSession.auth().verify(basicAuthSshClientProperties.connectionTimeOut)
64 log.info("SSH client session($clientSession) created")
68 private fun startChannel() {
70 channel = clientSession.createShellChannel()
71 val pipedIn = PipedOutputStream()
72 channel!!.setIn(PipedInputStream(pipedIn))
73 teeOutput = TeeOutputStream(ByteArrayOutputStream(), pipedIn)
74 channel!!.out = ByteArrayOutputStream()
75 channel!!.err = ByteArrayOutputStream()
77 } catch (e: Exception) {
78 throw BluePrintProcessorException("Failed to start Shell channel: ${e.message}")
82 override suspend fun executeCommandsNB(commands: List <String>, timeOut: Long): List<CommandResult> {
83 val response = ArrayList<CommandResult>()
86 val commandsIterator = commands.iterator()
87 while (commandsIterator.hasNext() && !stopLoop) {
88 val command = commandsIterator.next()
89 log.debug("Executing host command($command) \n")
90 val result = executeCommand(command, timeOut)
92 // Once a command in the template has failed break out of the loop to stop executing further commands
93 if (!result.successful) {
94 log.debug("Template execution will stop because command ({}) has failed.", command)
98 } catch (e: Exception) {
99 throw BluePrintProcessorException("Failed to execute commands, below the error message : ${e.message}")
104 override suspend fun executeCommandNB(command: String, timeOut: Long): CommandResult {
105 val deviceOutput: String
106 var isSuccessful = true
108 teeOutput!!.write(command.toByteArray())
109 teeOutput!!.write(newLine)
111 deviceOutput = waitForPrompt(timeOut)
112 } catch (e: IOException) {
113 throw BluePrintProcessorException("Exception during command execution: ${e.message}", e)
116 if (detectFailure(deviceOutput)) {
120 val commandResult = CommandResult(command, deviceOutput, isSuccessful)
121 if (basicAuthSshClientProperties.logging) {
122 log.info("Command Response: ({}) $newLine", commandResult)
127 private fun waitForPrompt(timeOut: Long): String {
128 val waitMask = channel!!.waitFor(
129 Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)), timeOut)
130 if (channel!!.out.toString().indexOfAny(arrayListOf("$", ">", "#")) <= 0 && waitMask.contains(ClientChannelEvent.TIMEOUT)) {
131 throw BluePrintProcessorException("Timeout: Failed to retrieve commands result in $timeOut ms")
133 val outputResult = channel!!.out.toString()
134 channel!!.out.flush()
138 override suspend fun closeSessionNB() {
139 if (channel != null) {
143 if (clientSession.isOpen && !clientSession.isClosing) {
144 clientSession.close()
147 if (sshClient.isStarted) {
150 log.debug("SSH Client Service stopped successfully")
153 // TODO filter output to check error message
154 private fun detectFailure(output: String): Boolean {
155 if (output.isNotBlank()) {
156 // Output can be multiline, need to check if any of the line starts with %
157 Scanner(output).use { scanner ->
158 while (scanner.hasNextLine()) {
159 val temp = scanner.nextLine()
160 if (temp.isNotBlank() && (temp.trim { it <= ' ' }.startsWith("%") ||
161 temp.trim { it <= ' ' }.startsWith("syntax error"))) {
171 data class CommandResult(val command: String, val deviceOutput: String, val successful: Boolean)