a3c730039ac6d3cb838799bd9b21fa8e865ccf4f
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / ssh-lib / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / ssh / service / BasicAuthSshClientService.kt
1 /*
2  *  Copyright © 2019 IBM.
3  *
4  *  Modifications Copyright © 2018-2020 IBM, Bell Canada.
5  *
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
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  */
18
19 package org.onap.ccsdk.cds.blueprintsprocessor.ssh.service
20
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
38
39 open class BasicAuthSshClientService(private val basicAuthSshClientProperties: BasicAuthSshClientProperties) :
40         BlueprintSshClientService {
41
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
46
47     private lateinit var sshClient: SshClient
48     private lateinit var clientSession: ClientSession
49
50     override suspend fun startSessionNB(): ClientSession {
51         sshClient = SshClient.setUpDefaultClient()
52         sshClient.serverKeyVerifier = AcceptAllServerKeyVerifier.INSTANCE
53         sshClient.start()
54         log.debug("SSH Client Service started successfully")
55
56         clientSession = sshClient.connect(
57                 basicAuthSshClientProperties.username, basicAuthSshClientProperties.host,
58                 basicAuthSshClientProperties.port).verify(basicAuthSshClientProperties.connectionTimeOut).session
59
60         clientSession.addPasswordIdentity(basicAuthSshClientProperties.password)
61         clientSession.auth().verify(basicAuthSshClientProperties.connectionTimeOut)
62         startChannel()
63
64         log.info("SSH client session($clientSession) created")
65         return clientSession
66     }
67
68     private fun startChannel() {
69         try {
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()
76             channel!!.open()
77         } catch (e: Exception) {
78             throw BluePrintProcessorException("Failed to start Shell channel: ${e.message}")
79         }
80     }
81
82     override suspend fun executeCommandsNB(commands: List <String>, timeOut: Long): List<CommandResult> {
83         val response = ArrayList<CommandResult>()
84         try {
85             var stopLoop = false
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)
91                 response.add(result)
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)
95                     stopLoop = true
96                 }
97             }
98         } catch (e: Exception) {
99             throw BluePrintProcessorException("Failed to execute commands, below the error message : ${e.message}")
100         }
101         return response
102     }
103
104     override suspend fun executeCommandNB(command: String, timeOut: Long): CommandResult {
105         val deviceOutput: String
106         var isSuccessful = true
107         try {
108             teeOutput!!.write(command.toByteArray())
109             teeOutput!!.write(newLine)
110             teeOutput!!.flush()
111             deviceOutput = waitForPrompt(timeOut)
112         } catch (e: IOException) {
113             throw BluePrintProcessorException("Exception during command execution:  ${e.message}", e)
114         }
115
116         if (detectFailure(deviceOutput)) {
117             isSuccessful = false
118         }
119
120         val commandResult = CommandResult(command, deviceOutput, isSuccessful)
121         if (basicAuthSshClientProperties.logging) {
122             log.info("Command Response: ({}) $newLine", commandResult)
123         }
124         return commandResult
125     }
126
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")
132         }
133         val outputResult = channel!!.out.toString()
134         channel!!.out.flush()
135         return outputResult
136     }
137
138     override suspend fun closeSessionNB() {
139         if (channel != null) {
140             channel!!.close()
141         }
142
143         if (clientSession.isOpen && !clientSession.isClosing) {
144             clientSession.close()
145         }
146
147         if (sshClient.isStarted) {
148             sshClient.stop()
149         }
150         log.debug("SSH Client Service stopped successfully")
151     }
152
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"))) {
162                         return true
163                     }
164                 }
165             }
166         }
167         return false
168     }
169 }
170
171 data class CommandResult(val command: String, val deviceOutput: String, val successful: Boolean)