Renaming Files having BluePrint to have Blueprint
[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.ArrayList
35 import java.util.Collections
36 import java.util.EnumSet
37 import java.util.Scanner
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
59         ).verify(basicAuthSshClientProperties.connectionTimeOut).session
60
61         clientSession.addPasswordIdentity(basicAuthSshClientProperties.password)
62         clientSession.auth().verify(basicAuthSshClientProperties.connectionTimeOut)
63         startChannel()
64
65         log.info("SSH client session($clientSession) created")
66         return clientSession
67     }
68
69     private fun startChannel() {
70         try {
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()
77             channel!!.open()
78         } catch (e: Exception) {
79             throw BlueprintProcessorException("Failed to start Shell channel: ${e.message}")
80         }
81     }
82
83     override suspend fun executeCommandsNB(commands: List<String>, timeOut: Long): List<CommandResult> {
84         val response = ArrayList<CommandResult>()
85         try {
86             var stopLoop = false
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)
92                 response.add(result)
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)
96                     stopLoop = true
97                 }
98             }
99         } catch (e: Exception) {
100             throw BlueprintProcessorException("Failed to execute commands, below the error message : ${e.message}")
101         }
102         return response
103     }
104
105     override suspend fun executeCommandNB(command: String, timeOut: Long): CommandResult {
106         val deviceOutput: String
107         var isSuccessful = true
108         try {
109             teeOutput!!.write(command.toByteArray())
110             teeOutput!!.write(newLine)
111             teeOutput!!.flush()
112             deviceOutput = waitForPrompt(timeOut)
113         } catch (e: IOException) {
114             throw BlueprintProcessorException("Exception during command execution:  ${e.message}", e)
115         }
116
117         if (detectFailure(deviceOutput)) {
118             isSuccessful = false
119         }
120
121         val commandResult = CommandResult(command, deviceOutput, isSuccessful)
122         if (basicAuthSshClientProperties.logging) {
123             log.info("Command Response: ({}) $newLine", commandResult)
124         }
125         return commandResult
126     }
127
128     private fun waitForPrompt(timeOut: Long): String {
129         val waitMask = channel!!.waitFor(
130             Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)), timeOut
131         )
132         if (channel!!.out.toString().indexOfAny(arrayListOf("$", ">", "#")) <= 0 && waitMask.contains(ClientChannelEvent.TIMEOUT)) {
133             throw BlueprintProcessorException("Timeout: Failed to retrieve commands result in $timeOut ms")
134         }
135         val outputResult = channel!!.out.toString()
136         channel!!.out.flush()
137         return outputResult
138     }
139
140     override suspend fun closeSessionNB() {
141         if (channel != null) {
142             channel!!.close()
143         }
144
145         if (clientSession.isOpen && !clientSession.isClosing) {
146             clientSession.close()
147         }
148
149         if (sshClient.isStarted) {
150             sshClient.stop()
151         }
152         log.debug("SSH Client Service stopped successfully")
153     }
154
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")
165                         )
166                     ) {
167                         return true
168                     }
169                 }
170             }
171         }
172         return false
173     }
174 }
175
176 data class CommandResult(val command: String, val deviceOutput: String, val successful: Boolean)