# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

import paramiko
import time
import cloudstackException
import contextlib
import logging
from marvin.codes import (
    SUCCESS, FAIL, INVALID_INPUT, EXCEPTION_OCCURRED
    )
from contextlib import closing


class SshClient(object):
    '''
    Added timeout flag for ssh connect calls.Default to 3.0 seconds
    '''
    def __init__(self, host, port, user, passwd, retries=20, delay=30,
                 log_lvl=logging.INFO, keyPairFiles=None, timeout=10.0):
        self.host = None
        self.port = 22
        self.user = user
        self.passwd = passwd
        self.keyPairFiles = keyPairFiles
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.logger = logging.getLogger('sshClient')
        self.retryCnt = 0
        self.delay = 0
        self.timeout = 3.0
        ch = logging.StreamHandler()
        ch.setLevel(log_lvl)
        self.logger.addHandler(ch)

        #Check invalid host value and raise exception
        #Atleast host is required for connection
        if host is not None and host != '':
            self.host = host
        if retries is not None and retries > 0:
            self.retryCnt = retries
        if delay is not None and delay > 0:
            self.delay = delay
        if timeout is not None and timeout > 0:
            self.timeout = timeout
        if port is not None or port >= 0:
            self.port = port
        if self.createConnection() == FAIL:
            raise cloudstackException.\
                internalError("Connection Failed")

    def execute(self, command):
        stdin, stdout, stderr = self.ssh.exec_command(command)
        output = stdout.readlines()
        errors = stderr.readlines()
        results = []
        if output is not None and len(output) == 0:
            if errors is not None and len(errors) > 0:
                for error in errors:
                    results.append(error.rstrip())

        else:
            for strOut in output:
                results.append(strOut.rstrip())
        self.logger.debug("{Cmd: %s via Host: %s} {returns: %s}" %
                          (command, str(self.host), results))
        return results

    def createConnection(self):
        '''
        @Name: createConnection
        @Desc: Creates an ssh connection for
               retries mentioned,along with sleep mentioned
        @Output: SUCCESS on successful connection
                 FAIL If connection through ssh failed
        '''
        ret = FAIL
        while self.retryCnt >= 0:
            try:
                self.logger.debug("SSH Connection: Host:%s User:%s\
                                   Port:%s" %
                                  (self.host, self.user, str(self.port)
                                   ))
                if self.keyPairFiles is None:
                    self.ssh.connect(hostname=self.host,
                                     port=self.port,
                                     username=self.user,
                                     password=self.passwd,
                                     timeout=self.timeout)
                else:
                    self.ssh.connect(hostname=self.host,
                                     port=self.port,
                                     username=self.user,
                                     password=self.passwd,
                                     key_filename=self.keyPairFiles,
                                     timeout=self.timeout,
                                     look_for_keys=False
                                     )
                ret = SUCCESS
                break
            except Exception as se:
                self.retryCnt = self.retryCnt - 1
                if self.retryCnt == 0:
                    break
                time.sleep(self.delay)
        return ret

    def runCommand(self, command):
        '''
        @Name: runCommand
        @Desc: Runs a command over ssh and
               returns the result along with status code
        @Input: command to execute
        @Output: 1: status of command executed.
                 Default to None
                 SUCCESS : If command execution is successful
                 FAIL    : If command execution has failed
                 EXCEPTION_OCCURRED: Exception occurred while executing
                                     command
                 INVALID_INPUT : If invalid value for command is passed
                 2: stdin,stdout,stderr values of command output
        '''
        excep_msg = ''
        ret = {"status": None, "stdin": None, "stdout": None, "stderr": None}
        if command is None or command == '':
            ret["status"] = INVALID_INPUT
            return ret
        try:
            status_check = 1
            stdin, stdout, stderr = self.ssh.exec_command(command)
            output = stdout.readlines()
            errors = stderr.readlines()
            inp = stdin.readlines()
            ret["stdin"] = inp
            ret["stdout"] = output
            ret["stderr"] = errors
            if stdout is not None:
                status_check = stdout.channel.recv_exit_status()
            if status_check == 0:
                ret["status"] = SUCCESS
            else:
                ret["status"] = FAIL
        except Exception as e:
            excep_msg = str(e)
            ret["status"] = EXCEPTION_OCCURRED
        finally:
            self.logger.debug(" Host: %s Cmd: %s Output:%s Exception: %s" %
                              (self.host, command, str(ret), excep_msg))
            return ret

    def scp(self, srcFile, destPath):
        transport = paramiko.Transport((self.host, int(self.port)))
        transport.connect(username=self.user, password=self.passwd)
        sftp = paramiko.SFTPClient.from_transport(transport)
        try:
            sftp.put(srcFile, destPath)
        except IOError, e:
            raise e

    def close(self):
            if self.ssh is not None:
                self.ssh.close()


if __name__ == "__main__":
    with contextlib.closing(SshClient("10.223.75.10", 22, "root",
                                      "password")) as ssh:
        print ssh.execute("ls -l")
