2885d6528ccff55190552a8cdab46b5d44c59df8
[ccsdk/cds.git] /
1 /*
2  *  Copyright © 2019 IBM.
3  *
4  *  Modifications Copyright © 2018-2019 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         log.info("Command Response: ({}) $newLine", commandResult)
122         return commandResult
123     }
124
125     private fun waitForPrompt(timeOut: Long): String {
126         val waitMask = channel!!.waitFor(
127                 Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)), timeOut)
128         if (channel!!.out.toString().indexOfAny(arrayListOf("$", ">", "#")) <= 0 && waitMask.contains(ClientChannelEvent.TIMEOUT)) {
129             throw BluePrintProcessorException("Timeout: Failed to retrieve commands result in $timeOut ms")
130         }
131         val outputResult = channel!!.out.toString()
132         channel!!.out.flush()
133         return outputResult
134     }
135
136     override suspend fun closeSessionNB() {
137         if (channel != null) {
138             channel!!.close()
139         }
140
141         if (clientSession.isOpen && !clientSession.isClosing) {
142             clientSession.close()
143         }
144
145         if (sshClient.isStarted) {
146             sshClient.stop()
147         }
148         log.debug("SSH Client Service stopped successfully")
149     }
150
151     // TODO filter output to check error message
152     private fun detectFailure(output: String): Boolean {
153         if (output.isNotBlank()) {
154             // Output can be multiline, need to check if any of the line starts with %
155             Scanner(output).use { scanner ->
156                 while (scanner.hasNextLine()) {
157                     val temp = scanner.nextLine()
158                     if (temp.isNotBlank() && (temp.trim { it <= ' ' }.startsWith("%") ||
159                                     temp.trim { it <= ' ' }.startsWith("syntax error"))) {
160                         return true
161                     }
162                 }
163             }
164         }
165         return false
166     }
167 }
168
169 data class CommandResult(val command: String, val deviceOutput: String, val successful: Boolean)