#    Licensed 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 re
import time

from oslo_log import log as logging

from tempest import config
from tempest.lib.common.utils.linux import remote_client
import tempest.lib.exceptions

CONF = config.CONF

LOG = logging.getLogger(__name__)


class RemoteClient(remote_client.RemoteClient):

    # TODO(oomichi): Make this class deprecated after migrating
    #                necessary methods to tempest.lib and cleaning
    #                unnecessary methods up from this class.
    def __init__(self, ip_address, username, password=None, pkey=None,
                 server=None, servers_client=None):
        """Executes commands in a VM over ssh

        :param ip_address: IP address to ssh to
        :param username: ssh username
        :param password: ssh password (optional)
        :param pkey: ssh public key (optional)
        :param server: server dict, used for debugging purposes
        :param servers_client: servers client, used for debugging purposes
        """
        super(RemoteClient, self).__init__(
            ip_address, username, password=password, pkey=pkey,
            server=server, servers_client=servers_client,
            ssh_timeout=CONF.validation.ssh_timeout,
            connect_timeout=CONF.validation.connect_timeout,
            console_output_enabled=CONF.compute_feature_enabled.console_output,
            ssh_shell_prologue=CONF.validation.ssh_shell_prologue,
            ping_count=CONF.validation.ping_count,
            ping_size=CONF.validation.ping_size,
            ssh_key_type=CONF.validation.ssh_key_type)

    # Note that this method will not work on SLES11 guests, as they do
    # not support the TYPE column on lsblk
    def get_disks(self):
        # Select root disk devices as shown by lsblk
        command = 'lsblk -lb --nodeps'
        output = self.exec_command(command)
        selected = []
        pos = None
        for line in output.splitlines():
            if pos is None and line.find("TYPE") > 0:
                pos = line.find("TYPE")
                # Show header line too
                selected.append(line)
            # lsblk lists disk type in a column right-aligned with TYPE
            elif pos is not None and pos > 0 and line[pos:pos + 4] == "disk":
                selected.append(line)

        if selected:
            return "\n".join(selected)
        else:
            msg = "'TYPE' column is required but the output doesn't have it: "
            raise tempest.lib.exceptions.TempestException(msg + output)

    def list_disks(self):
        disks_list = self.get_disks()
        disks_list = [line[0] for line in
                      [device_name.split()
                       for device_name in disks_list.splitlines()][1:]]
        return disks_list

    def get_boot_time(self):
        cmd = 'cut -f1 -d. /proc/uptime'
        boot_secs = self.exec_command(cmd)
        boot_time = time.time() - int(boot_secs)
        return time.localtime(boot_time)

    def write_to_console(self, message):
        message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
        # usually to /dev/ttyS0
        cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
        return self.exec_command(cmd)

    def get_mac_address(self, nic=""):
        show_nic = "show {nic} ".format(nic=nic) if nic else ""
        cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic
        return self.exec_command(cmd).strip().lower()

    def get_nic_name_by_mac(self, address):
        cmd = "ip -o link | awk '/%s/ {print $2}'" % address
        nic = self.exec_command(cmd)
        return nic.strip().strip(":").split('@')[0].lower()

    def get_nic_name_by_ip(self, address):
        cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
        nic = self.exec_command(cmd)
        LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
        return nic.strip().strip(":").split('@')[0].lower()

    def get_nic_ip_addresses(self, nic_name, ip_version=None):
        cmd = "ip "
        if ip_version:
            cmd += "-%s " % ip_version
        cmd += "-o addr | awk '/%s/ {print $4}'" % nic_name
        ip_addresses = self.exec_command(cmd)
        LOG.debug('(get_nic_ip_address): Command result: %s', ip_addresses)
        return ip_addresses.strip().split()

    def _get_dns_servers(self):
        cmd = 'cat /etc/resolv.conf'
        resolve_file = self.exec_command(cmd).strip().split('\n')
        entries = (line.split() for line in resolve_file)
        dns_servers = [line[1] for line in entries
                       if len(line) and line[0] == 'nameserver']
        return dns_servers

    def get_dns_servers(self, timeout=5):
        start_time = int(time.time())
        dns_servers = []
        while True:
            dns_servers = self._get_dns_servers()
            if dns_servers:
                break
            LOG.debug("DNS Servers list empty.")
            if int(time.time()) - start_time >= timeout:
                LOG.debug("DNS Servers list empty after %s.", timeout)
                break
        return dns_servers

    def _renew_lease_udhcpc(self, fixed_ip=None):
        """Renews DHCP lease via udhcpc client. """
        file_path = '/var/run/udhcpc.'
        nic_name = self.get_nic_name_by_ip(fixed_ip)
        pid = self.exec_command('cat {path}{nic}.pid'.
                                format(path=file_path, nic=nic_name))
        pid = pid.strip()
        cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig='USR1')
        self.exec_command(cmd)

    def _renew_lease_dhclient(self, fixed_ip=None):
        """Renews DHCP lease via dhclient client. """
        cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
        self.exec_command(cmd)

    def _renew_lease_dhcpcd(self, fixed_ip=None):
        """Renews DHCP lease via dhcpcd client. """
        cmd = "sudo /sbin/dhcpcd --rebind"
        self.exec_command(cmd)

    def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'):
        """Wrapper method for renewing DHCP lease via given client

        Supporting:
        * udhcpc
        * dhclient
        * dhcpcd
        """
        supported_clients = ['udhcpc', 'dhclient', 'dhcpcd']
        if dhcp_client not in supported_clients:
            raise tempest.lib.exceptions.InvalidConfiguration(
                '%s DHCP client unsupported' % dhcp_client)
        if dhcp_client == 'udhcpc' and not fixed_ip:
            raise ValueError("need to set 'fixed_ip' for udhcpc client")
        return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip)

    def mount(self, dev_name, mount_path='/mnt'):
        cmd_mount = 'sudo mount /dev/%s %s' % (dev_name, mount_path)
        self.exec_command(cmd_mount)

    def umount(self, mount_path='/mnt'):
        self.exec_command('sudo umount %s' % mount_path)

    def mkdir(self, dir_path):
        self.exec_command('sudo mkdir -p %s' % dir_path)

    def make_fs(self, dev_name, fs='ext4'):
        cmd_mkfs = 'sudo mkfs -t %s /dev/%s' % (fs, dev_name)
        try:
            self.exec_command(cmd_mkfs)
        except tempest.lib.exceptions.SSHExecCommandFailed:
            LOG.error("Couldn't mke2fs")
            cmd_why = 'sudo ls -lR /dev'
            LOG.info("Contents of /dev: %s", self.exec_command(cmd_why))
            raise

    def nc_listen_host(self, port=80, protocol='tcp'):
        """Creates persistent nc server listening on the given TCP / UDP port

        :port: the port to start listening on.
        :protocol: the protocol used by the server. TCP by default.
        """
        udp = '-u' if protocol.lower() == 'udp' else ''
        cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo foolish &" % {
            'udp': udp, 'port': port}
        return self.exec_command(cmd)

    def nc_host(self, host, port=80, protocol='tcp', expected_response=None):
        """Check connectivity to TCP / UDP port at host via nc

        :host: an IP against which the connectivity will be tested.
        :port: the port to check connectivity against.
        :protocol: the protocol used by nc to send packets. TCP by default.
        :expected_response: string representing the expected response
            from server.
        :raises SSHExecCommandFailed: if an expected response is given and it
            does not match the actual server response.
        """
        udp = '-u' if protocol.lower() == 'udp' else ''
        cmd = 'echo "bar" | nc -w 1 %(udp)s %(host)s %(port)s' % {
            'udp': udp, 'host': host, 'port': port}
        response = self.exec_command(cmd)

        # sending an UDP packet will always succeed. we need to check
        # the response.
        if (expected_response is not None and
                expected_response != response.strip()):
            raise tempest.lib.exceptions.SSHExecCommandFailed(
                command=cmd, exit_status=0, stdout=response, stderr='')
        return response

    def icmp_check(self, host, nic=None):
        """Wrapper for icmp connectivity checks"""
        return self.ping_host(host, nic=nic)

    def udp_check(self, host, **kwargs):
        """Wrapper for udp connectivity checks."""
        kwargs.pop('nic', None)
        return self.nc_host(host, protocol='udp', expected_response='foolish',
                            **kwargs)

    def tcp_check(self, host, **kwargs):
        """Wrapper for tcp connectivity checks."""
        kwargs.pop('nic', None)
        return self.nc_host(host, **kwargs)
