1# Copyright (c) 2012 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 os, pwd, subprocess, tempfile
6
7from autotest_lib.client.bin import test
8from autotest_lib.client.common_lib import error
9
10class TLSDate:
11    """
12    A single tlsdate invocation. Takes care of setting up a temporary cachedir
13    for it, along with collecting output from both it and its helper processes.
14    """
15    def __init__(self, test_obj):
16        self._proc = None
17        self._testdir = tempfile.mkdtemp(suffix='tlsdate')
18        self._cachedir = self._testdir + '/cache'
19        self._outfile = self._testdir + '/out'
20        self._subprog = '?'
21        self._test_obj = test_obj
22        self._output = None
23        self._tlsdate_uid = pwd.getpwnam('tlsdate').pw_uid
24        os.mkdir(self._cachedir)
25        # Let the tlsdate user (tlsdate) write.
26        os.chown(self._testdir, self._tlsdate_uid, -1)
27        # Allow support shell library to be sourced.
28        os.chown(self._test_obj.srcdir + '/time.sh', self._tlsdate_uid, -1)
29
30
31    def start(self, subprog):
32        print 'running with %s' % self._test_obj.srcdir + '/' + subprog
33        self._subprog = subprog
34        # Make sure the tlsdate user can access the files
35        fake_tlsdate = self._test_obj.srcdir + '/' + subprog
36        os.chown(fake_tlsdate, self._tlsdate_uid, -1)
37        args = ['/usr/bin/tlsdated', '-p',
38                '-f', self._test_obj.srcdir + '/test.conf',
39                '-c', self._cachedir,
40                '-v',
41                fake_tlsdate,
42                self._outfile]
43        self._proc = subprocess.Popen(args, stdin=subprocess.PIPE,
44                                      stderr=subprocess.PIPE)
45
46
47    def route_up(self):
48        self._proc.stdin.write('n')
49        self._proc.stdin.flush()
50
51
52    def kill(self):
53        self._proc.terminate()
54
55
56    def output(self):
57        if not self._output:
58            self._output = self._proc.communicate()[1].split('\n')
59        return self._output
60
61
62    def in_output(self, string):
63        for x in self.output():
64            if string in x:
65                return True
66        return False
67
68
69    def subproc_output(self):
70        with open(self._outfile) as f:
71            return [x.rstrip() for x in f.readlines()]
72
73
74    def ok(self):
75        return 'ok' in self.subproc_output()
76
77
78class platform_TLSDate(test.test):
79    version = 1
80
81    def require_ok(self, t):
82        if not t.ok():
83            raise error.TestFail('Expected success, got:' +
84                                 ';'.join(t.subproc_output()))
85
86
87    def require_output(self, t, string):
88        if not t.in_output(string):
89            raise error.TestFail('Needed "%s" but got "%s"' % (string,
90                                 ';'.join(t.output())))
91
92
93    def require_not_output(self, t, string):
94        if t.in_output(string):
95            raise error.TestFail('Needed no "%s" but got "%s"' % (string,
96                                 ';'.join(t.output())))
97
98
99    def test_delay_subproc(self):
100        """
101        Tests that a subprocess that delays for one second is waited on
102        successfully the second time.
103        """
104        t = TLSDate(self)
105        t.start('delay_subproc')
106        self.require_output(t, 'attempt 1 backoff')
107        self.require_output(t, 'time set from the network')
108        self.require_ok(t)
109
110
111    def test_hang_subproc(self):
112        """
113        Tests that a subprocess that delays for too long is considered hung and
114        killed.
115        """
116        t = TLSDate(self)
117        t.start('hang_subproc')
118        self.require_output(t, 'attempt 1 backoff')
119        self.require_output(t, 'tlsdate timed out')
120        self.require_ok(t)
121
122
123    def test_fail_routes(self):
124        """
125        Tests that if the initial tlsdate call fails, we wait for a route to
126        appear, then rerun tlsdate.
127        """
128        t = TLSDate(self)
129        t.start('fail_routes')
130        t.route_up()
131        self.require_output(t, 'status:2')
132        self.require_output(t, 'stdin')
133        self.require_output(t, 'time set from the network')
134        self.require_ok(t)
135
136
137    def run_once(self):
138        self.test_delay_subproc()
139        self.test_hang_subproc()
140        self.test_fail_routes()
141