1# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging, os, socket, subprocess, tempfile, threading, time
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.server import autotest, hosts, site_host_attributes
9from autotest_lib.server import subcommand, test, utils
10
11
12class WolWake(threading.Thread):
13    """Class to allow waking of DUT via Wake-on-LAN capabilities (WOL)."""
14
15
16    def __init__(self, hostname, mac_addr, sleep_secs):
17        """Constructor for waking DUT.
18
19        Args:
20          mac_addr: string of mac address tuple
21          sleep_secs: seconds to sleep prior to attempting WOL
22        """
23        threading.Thread.__init__(self)
24        self._hostname = hostname
25        self._mac_addr = mac_addr
26        self._sleep_secs = sleep_secs
27
28
29    # TODO(tbroch) Borrowed from class ServoTest.  Refactor for code re-use
30    def _ping_test(self, hostname, timeout=5):
31        """Verify whether a host responds to a ping.
32
33        Args:
34          hostname: Hostname to ping.
35          timeout: Time in seconds to wait for a response.
36
37        Returns: True if success False otherwise
38        """
39        with open(os.devnull, 'w') as fnull:
40            ping_good = False
41            elapsed_time = 0
42            while not ping_good and elapsed_time < timeout:
43                ping_good = subprocess.call(
44                    ['ping', '-c', '1', '-W', str(timeout), hostname],
45                    stdout=fnull, stderr=fnull) == 0
46                time.sleep(1)
47                elapsed_time += 1
48            return ping_good
49
50
51    def _send_wol_magic_packet(self):
52        """Perform Wake-on-LAN magic wake.
53
54        WOL magic packet consists of:
55          0xff repeated for 6 bytes
56          <mac addr> repeated 16 times
57
58        Sent as a broadcast packet.
59        """
60        mac_tuple = self._mac_addr.split(':')
61        assert len(mac_tuple) == 6
62        magic = '\xff' * 6
63        submagic = ''.join("%c" % int(value, 16) for value in mac_tuple)
64        magic += submagic * 16
65        assert len(magic) == 102
66
67        sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
68        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
69        sock.sendto(magic, ('<broadcast>', 7))
70        sock.close()
71        logging.info("Wake thread sent WOL wakeup")
72
73
74    def run(self):
75        # ping device to make sure its network is off presumably from suspend
76        # not another malfunction.
77        ping_secs = 0
78        while self._ping_test(self._hostname, timeout=2) and \
79                ping_secs < self._sleep_secs:
80            time.sleep(1)
81            ping_secs += 1
82
83        self._send_wol_magic_packet()
84
85
86class network_EthCapsServer(test.test):
87    version = 1
88
89    def _parse_ifconfig(self, filename):
90        """Retrieve ifconfig information.
91
92        Raises
93          error.TestError if unable to parse mac address
94        """
95        self._mac_addr = None
96
97        fd = open(filename)
98        for ln in fd.readlines():
99            info_str = ln.strip()
100            logging.debug(ln)
101            index = info_str.find('HWaddr ')
102            if index != -1:
103                self._mac_addr = info_str[index + len('HWaddr '):]
104                logging.info("mac addr = %s" % self._mac_addr)
105                break
106        fd.close()
107
108        if not self._mac_addr:
109            raise error.TestError("Unable to find mac addresss")
110
111
112    def _client_cmd(self, cmd, results=None):
113        """Execute a command on the client.
114
115        Args:
116          results: string of filename to save results on client.
117
118        Returns:
119          string of filename on server side with stdout results of command
120        """
121        if results:
122            client_tmpdir = self._client.get_tmp_dir()
123            client_results = os.path.join(client_tmpdir, "%s" % results)
124            cmd = "%s > %s 2>&1" % (cmd, client_results)
125
126        logging.info("Client cmd = %s", cmd)
127        self._client.run(cmd)
128
129        if results:
130            server_tmpfile = tempfile.NamedTemporaryFile(delete=False)
131            server_tmpfile.close()
132            self._client.get_file(client_results, server_tmpfile.name)
133            return server_tmpfile.name
134
135        return None
136
137
138    def run_once(self, client_ip=None, ethname='eth0'):
139        """Run the test.
140
141        Args:
142          client_ip: string of client's ip address
143          ethname: string of ethernet device under test
144        """
145        if not client_ip:
146            error.TestError("Must provide client's IP address to test")
147
148        sleep_secs = 20
149
150        self._ethname = ethname
151        self._client_ip = client_ip
152        self._client = hosts.create_host(client_ip)
153        client_at = autotest.Autotest(self._client)
154
155        # retrieve ifconfig info for mac address of client
156        cmd = "ifconfig %s" % self._ethname
157        ifconfig_filename = self._client_cmd(cmd, results="ifconfig.log")
158        self._parse_ifconfig(ifconfig_filename)
159
160        # thread to wake the device using WOL
161        wol_wake = WolWake(self._client_ip, self._mac_addr, sleep_secs)
162        wol_wake.start()
163
164        # create and run client test to prepare and suspend device
165        client_at.run_test("network_EthCaps", ethname=ethname,
166                           threshold_secs=sleep_secs * 2)
167
168        wol_wake.join()
169