import os
import re
import socket
import subprocess
import sys

from autotest.client.shared import utils, error
from autotest.server.hosts import remote


class NetconsoleHost(remote.RemoteHost):

    def _initialize(self, console_log="netconsole.log", *args, **dargs):
        super(NetconsoleHost, self)._initialize(*args, **dargs)

        self.__logger = None
        self.__console_log = console_log

        # get a socket for us to listen on
        self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.__socket.bind(('', 0))
        self.__port = self.__socket.getsockname()[1]

    @classmethod
    def host_is_supported(cls, run_func):
        local_ip = socket.gethostbyname(socket.gethostname())
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(1)
        s.bind((local_ip, 0))
        local_port = s.getsockname()[1]
        send_udp_packet = (
            """python -c "from socket import *; """
            """s = socket(AF_INET, SOCK_DGRAM); """
            """s.sendto('ping', ('%s', %d))" """ % (local_ip, local_port))
        run_func(send_udp_packet)
        try:
            msg = s.recv(4)
        except Exception:
            supported = False
        else:
            supported = (msg == "ping")
        s.close()
        return supported

    def start_loggers(self):
        super(NetconsoleHost, self).start_loggers()

        if not self.__console_log:
            return

        self.__netconsole_params = self.__determine_netconsole_params()
        if self.__netconsole_params is None:
            return

        r, w = os.pipe()
        script_path = os.path.join(self.monitordir, "console.py")
        cmd = [sys.executable, script_path, self.__console_log, str(w)]

        self.__warning_stream = os.fdopen(r, "r", 0)
        if self.job:
            self.job.warning_loggers.add(self.__warning_stream)

        stdin = self.__socket.fileno()
        stdout = stderr = open(os.devnull, "w")
        self.__logger = subprocess.Popen(cmd, stdin=stdin, stdout=stdout,
                                         stderr=stderr)
        os.close(w)

        self.__unload_netconsole_module()
        self.__load_netconsole_module()

    def stop_loggers(self):
        super(NetconsoleHost, self).stop_loggers()

        if self.__logger:
            utils.nuke_subprocess(self.__logger)
            self.__logger = None
            if self.job:
                self.job.warning_loggers.discard(self.__warning_stream)
            self.__warning_stream.close()

    def reboot_setup(self, *args, **dargs):
        super(NetconsoleHost, self).reboot_setup(*args, **dargs)

        if self.__netconsole_params is not None:
            label = dargs.get("label", None)
            if not label:
                label = self.bootloader.get_default_title()
            args = "debug " + self.__netconsole_params
            self.bootloader.add_args(label, args)
        self.__unload_netconsole_module()

    def reboot_followup(self, *args, **dargs):
        super(NetconsoleHost, self).reboot_followup(*args, **dargs)
        self.__load_netconsole_module()

    def __determine_netconsole_params(self):
        """
        Connect to the remote machine and determine the values to use for the
        required netconsole parameters.
        """
        # determine the IP addresses of the local and remote machine
        # PROBLEM: on machines with multiple IPs this may not make any sense
        # It also doesn't work with IPv6
        remote_ip = socket.gethostbyname(self.hostname)
        local_ip = socket.gethostbyname(socket.gethostname())

        # Get the gateway of the remote machine
        try:
            traceroute = self.run('traceroute -n %s' % local_ip)
        except error.AutoservRunError:
            return
        first_node = traceroute.stdout.split("\n")[0]
        match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
        if match:
            router_ip = match.group(1)
        else:
            return

        # Look up the MAC address of the gateway
        try:
            self.run('ping -c 1 %s' % router_ip)
            arp = self.run('arp -n -a %s' % router_ip)
        except error.AutoservRunError:
            return
        match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
        if match:
            gateway_mac = match.group(1)
        else:
            return None

        return 'netconsole=@%s/,%s@%s/%s' % (remote_ip, self.__port, local_ip,
                                             gateway_mac)

    def __load_netconsole_module(self):
        """
        Make a best effort to load the netconsole module.

        Note that loading the module can fail even when the remote machine is
        working correctly if netconsole is already compiled into the kernel
        and started.
        """
        if self.__netconsole_params is None:
            return

        try:
            self.run('dmesg -n 8')
            self.run('modprobe netconsole %s' % self.__netconsole_params)
        except error.AutoservRunError, e:
            # if it fails there isn't much we can do, just keep going
            print "ERROR occurred while loading netconsole: %s" % e

    def __unload_netconsole_module(self):
        try:
            self.run('modprobe -r netconsole')
        except error.AutoservRunError:
            pass
