1# Copyright (c) 2014 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
6import os
7import time
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server import autotest
11from autotest_lib.server import hosts
12from autotest_lib.server import test
13from autotest_lib.server.cros import remote_command
14
15class network_DiskFull(test.test):
16    """Test networking daemons when /var is full."""
17
18    version = 1
19    CLIENT_TEST_LIST = [
20        ('network_DhcpNegotiationSuccess', {}),
21        ('network_DhcpRenew', {}),
22        ('network_RestartShill', {
23                'tag': 'profile_exists',
24                'remove_profile': False}),
25        ('network_RestartShill', {
26                'tag': 'profile_missing',
27                'remove_profile': True}),
28        ]
29    CLIENT_TMP_DIR = '/tmp'
30    DISK_FILL_SCRIPT = 'hog_disk.sh'
31    FILL_TIMEOUT_SECONDS = 5
32    MAX_FREE_KB = 1024
33    STATEFUL_PATH = '/var'
34    TEST_TIMEOUT_SECONDS = 180
35
36    def get_free_kilobytes(self, mount_point):
37        """
38        Get the size of free space on the filesystem mounted at |mount_point|,
39        in kilobytes.
40
41        @return Kilobytes free, as an integer.
42        """
43        # Filesystem              1024-blocks  Used Available Capacity Mount...
44        # /dev/mapper/encstateful      290968 47492    243476      17% /var
45        output = self._client.run('df -P %s' % mount_point).stdout
46        lines = output.splitlines()
47        if len(lines) != 2:
48            raise error.TestFail('Unexpected df output: %s' % lines)
49        _, _, _, free_kb, _, df_mount_point = lines[1].split(None, 5)
50        if df_mount_point != mount_point:
51            raise error.TestFail('Failed to find %s, got %s instead.' %
52                                 (mount_point, df_mount_point))
53        return int(free_kb)
54
55
56    def wait_until_full(self, mount_point, max_free_kilobytes):
57        """
58        Wait until |mount_point| has no more than |max_free_kilobytes| free.
59
60        @param mount_point The path at which the filesystem is mounted.
61        @param max_free_kilobytes Maximum free space permitted, in kilobytes.
62        @return True if the disk is full, else False
63        """
64        start_time = time.time()
65        while time.time() - start_time < self.FILL_TIMEOUT_SECONDS:
66            if (self.get_free_kilobytes(mount_point) <= max_free_kilobytes):
67                return True
68            else:
69                time.sleep(1)
70        return False
71
72
73    def run_once(self, client_addr):
74        """
75        Test main loop.
76
77        @param client_addr DUT hostname or IP address.
78        """
79        self._client = hosts.create_host(client_addr)
80        client_autotest = autotest.Autotest(self._client)
81
82        disk_filler_src = os.path.join(self.bindir, self.DISK_FILL_SCRIPT)
83        disk_filler_dst = os.path.join(self.CLIENT_TMP_DIR,
84                                       os.path.basename(self.DISK_FILL_SCRIPT))
85        self._client.send_file(disk_filler_src, disk_filler_dst)
86
87        disk_filler_command = '%s %s %d' % (
88            disk_filler_dst, self.STATEFUL_PATH, self.TEST_TIMEOUT_SECONDS)
89
90        with remote_command.Command(self._client, disk_filler_command) \
91                as disk_filler_process:
92            if not self.wait_until_full(self.STATEFUL_PATH, self.MAX_FREE_KB):
93                logging.debug(disk_filler_process.result)
94                raise error.TestFail(
95                    'did not fill %s within %d seconds' % (
96                        self.STATEFUL_PATH, self.FILL_TIMEOUT_SECONDS))
97
98            client_autotest.run_test('network_CheckCriticalProcesses',
99                                     tag='before_client_tests')
100            passed_with_failsafe = []
101
102            for name, kwargs in self.CLIENT_TEST_LIST:
103                # Autotest goes to /mnt/stateful_partition/dev_image,
104                # while /var is on /mnt/stateful_partition/encrypted.
105                #
106                # These are separate partitions, so we can copy
107                # the tests onto the DUT even when /var is full.
108                client_autotest.run_test(name, **kwargs)
109
110                if 'tag' in kwargs:
111                    full_test_name = '%s.%s' % (name, kwargs['tag'])
112                else:
113                    full_test_name = name
114
115                # To avoid leaving the system in a bad state, the disk
116                # filler times out eventually. This means a test can
117                # "pass" due to the failsafe. Check if the failsafe
118                # kicked in, by checking if the disk is still full.
119                if (self.get_free_kilobytes(self.STATEFUL_PATH) >
120                    self.MAX_FREE_KB):
121                    passed_with_failsafe.append(full_test_name)
122
123                client_autotest.run_test('network_CheckCriticalProcesses',
124                                         tag='after_%s' % full_test_name)
125
126            if len(passed_with_failsafe):
127                raise error.TestFail(
128                    '%d test(s) triggered the fail-safe: %s. '
129                    'They may be incorrectly listed as passing.' % (
130                        len(passed_with_failsafe),
131                        ', '.join(passed_with_failsafe)))
132