• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
10import logging
11import os
12import re
13import time
14
15import common
16from autotest_lib.client.common_lib import error, global_config
17from autotest_lib.client.common_lib.cros import retry
18from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
19from autotest_lib.server.hosts import cros_host
20from autotest_lib.server.hosts import cros_repair
21
22from chromite.lib import timeout_util
23import six
24
25AUTOTEST_INSTALL_DIR = global_config.global_config.get_config_value(
26        'SCHEDULER', 'drone_installation_directory')
27
28#'/usr/local/autotest'
29SHADOW_CONFIG_PATH = '%s/shadow_config.ini' % AUTOTEST_INSTALL_DIR
30ATEST_PATH = '%s/cli/atest' % AUTOTEST_INSTALL_DIR
31
32# Sample output of fping that we are matching against, the fping command
33# will return 10 lines but they will be one of these two formats.
34# We want to get the IP address for the first line and not match the
35# second line that has a non 0 %loss.
36#192.168.231.100 : xmt/rcv/%loss = 10/10/0%, min/avg/max = 0.68/0.88/1.13
37#192.168.231.102 : xmt/rcv/%loss = 10/0/100%
38SUBNET_DUT_SEARCH_RE = (r'(?P<ip>192.168.231.1[0-1][0-9]) : '
39                        'xmt\/rcv\/%loss = [0-9]+\/[0-9]+\/0%')
40
41MOBLAB_HOME = '/home/moblab'
42MOBLAB_BOTO_LOCATION = '%s/.boto' % MOBLAB_HOME
43MOBLAB_LAUNCH_CONTROL_KEY_LOCATION = '%s/.launch_control_key' % MOBLAB_HOME
44MOBLAB_SERVICE_ACCOUNT_LOCATION = '%s/.service_account.json' % MOBLAB_HOME
45MOBLAB_AUTODIR = '/usr/local/autodir'
46DHCPD_LEASE_FILE = '/var/lib/dhcp/dhcpd.leases'
47MOBLAB_SERVICES = ['moblab-scheduler-init',
48                   'moblab-database-init',
49                   'moblab-devserver-init',
50                   'moblab-gsoffloader-init',
51                   'moblab-gsoffloader_s-init']
52MOBLAB_PROCESSES = ['apache2', 'dhcpd']
53DUT_VERIFY_SLEEP_SECS = 5
54DUT_VERIFY_TIMEOUT = 15 * 60
55MOBLAB_TMP_DIR = '/mnt/moblab/tmp'
56MOBLAB_PORT = 80
57
58
59class UpstartServiceNotRunning(error.AutoservError):
60    """An expected upstart service was not in the expected state."""
61
62    def __init__(self, service_name):
63        """Create us.
64        @param service_name: Name of the service_name that was in the worng
65                state.
66        """
67        super(UpstartServiceNotRunning, self).__init__(
68                'Upstart service %s not in running state. Most likely this '
69                'means moblab did not boot correctly, check the boot logs '
70                'for detailed error messages as to see why this service was '
71                'not started.' %
72                service_name)
73
74
75class MoblabHost(cros_host.CrosHost):
76    """Moblab specific host class."""
77
78
79    def _initialize_frontend_rpcs(self, timeout_min):
80        """Initialize frontends for AFE and TKO for a moblab host.
81
82        We tunnel all communication to the frontends through an SSH tunnel as
83        many testing environments block everything except SSH access to the
84        moblab DUT.
85
86        @param timeout_min: The timeout minuties for AFE services.
87        """
88        web_address = self.rpc_server_tracker.tunnel_connect(MOBLAB_PORT)
89        # Pass timeout_min to self.afe
90        self.afe = frontend_wrappers.RetryingAFE(timeout_min=timeout_min,
91                                                 user='moblab',
92                                                 server=web_address)
93        # Use default timeout_min of MoblabHost for self.tko
94        self.tko = frontend_wrappers.RetryingTKO(timeout_min=self.timeout_min,
95                                                 user='moblab',
96                                                 server=web_address)
97
98
99    def _initialize(self, *args, **dargs):
100        super(MoblabHost, self)._initialize(*args, **dargs)
101        # TODO(jrbarnette):  Our superclass already initialized
102        # _repair_strategy, and now we're re-initializing it here.
103        # That's awkward, if not actually wrong.
104        self._repair_strategy = cros_repair.create_moblab_repair_strategy()
105        self.timeout_min = dargs.get('rpc_timeout_min', 1)
106        self._initialize_frontend_rpcs(self.timeout_min)
107
108
109    @staticmethod
110    def check_host(host, timeout=10):
111        """
112        Check if the given host is an moblab host.
113
114        @param host: An ssh host representing a device.
115        @param timeout: The timeout for the run command.
116
117
118        @return: True if the host device has adb.
119
120        @raises AutoservRunError: If the command failed.
121        @raises AutoservSSHTimeout: Ssh connection has timed out.
122        """
123        try:
124            result = host.run(
125                    'grep -q moblab /etc/lsb-release',
126                    ignore_status=True, timeout=timeout)
127        except (error.AutoservRunError, error.AutoservSSHTimeout):
128            return False
129        return result.exit_status == 0
130
131
132    def install_boto_file(self, boto_path=''):
133        """Install a boto file on the Moblab device.
134
135        @param boto_path: Path to the boto file to install. If None, sends the
136                          boto file in the current HOME directory.
137
138        @raises error.TestError if the boto file does not exist.
139        """
140        if not boto_path:
141            boto_path = os.path.join(os.getenv('HOME'), '.boto')
142        if not os.path.exists(boto_path):
143            raise error.TestError('Boto File:%s does not exist.' % boto_path)
144        self.send_file(boto_path, MOBLAB_BOTO_LOCATION)
145        self.run('chown moblab:moblab %s' % MOBLAB_BOTO_LOCATION)
146
147
148    def get_autodir(self):
149        """Return the directory to install autotest for client side tests."""
150        return self.autodir or MOBLAB_AUTODIR
151
152
153    def run_as_moblab(self, command, **kwargs):
154        """Moblab commands should be ran as the moblab user not root.
155
156        @param command: Command to run as user moblab.
157        """
158        command = "su - moblab -c '%s'" % command
159        return self.run(command, **kwargs)
160
161
162    def wait_afe_up(self, timeout_min=5):
163        """Wait till the AFE is up and loaded.
164
165        Attempt to reach the Moblab's AFE and database through its RPC
166        interface.
167
168        @param timeout_min: Minutes to wait for the AFE to respond. Default is
169                            5 minutes.
170
171        @raises urllib2.HTTPError if AFE does not respond within the timeout.
172        """
173        # Use moblabhost's own AFE object with a longer timeout to wait for the
174        # AFE to load. Also re-create the ssh tunnel for connections to moblab.
175        # Set the timeout_min to be longer than self.timeout_min for rebooting.
176        self._initialize_frontend_rpcs(timeout_min)
177        # Verify the AFE can handle a simple request.
178        self._check_afe()
179        # Reset the timeout_min after rebooting checks for afe services.
180        self.afe.set_timeout(self.timeout_min)
181
182
183    def add_dut(self, hostname):
184        """Add a DUT hostname to the AFE.
185
186        @param hostname: DUT hostname to add.
187        """
188        result = self.run_as_moblab('%s host create %s' % (ATEST_PATH,
189                                                           hostname))
190        logging.debug('atest host create output for host %s:\n%s',
191                      hostname, result.stdout)
192
193
194    def find_and_add_duts(self):
195        """Discover DUTs on the testing subnet and add them to the AFE.
196
197        Pings the range of IP's a DUT might be assigned by moblab, then
198        parses the output to discover connected DUTs, connected means
199        they have 0% dropped pings.
200        If they are not already in the AFE, adds them to AFE.
201        """
202        existing_hosts = [host.hostname for host in self.afe.get_hosts()]
203        fping_result = self.run('fping -g 192.168.231.100 192.168.231.110 '
204                                '-a -c 10 -p 30 -q', ignore_status=True)
205        for line in fping_result.stderr.splitlines():
206            match = re.match(SUBNET_DUT_SEARCH_RE, line)
207            if match:
208                dut_ip = match.group('ip')
209                if dut_ip in existing_hosts:
210                    break
211                if self._check_dut_ssh(dut_ip):
212                    self.add_dut(dut_ip)
213                    existing_hosts.append(dut_ip)
214
215    def _check_dut_ssh(self, dut_ip):
216       is_sshable = False
217       count = 0
218       while not is_sshable and count < 10:
219           cmd = ('ssh  -o ConnectTimeout=30 -o ConnectionAttempts=30'
220                  ' root@%s echo Testing' % dut_ip)
221           result = self.run(cmd)
222           is_sshable = 'Testing' in result.stdout
223           logging.info(is_sshable)
224           count += 1
225       return is_sshable
226
227    def verify_software(self):
228        """Create the autodir then do standard verify."""
229        # In case cleanup or powerwash wiped the autodir, create an empty
230        # directory.
231        # Removing this mkdir command will result in the disk size check
232        # not being performed.
233        self.run('mkdir -p %s' % MOBLAB_AUTODIR)
234        super(MoblabHost, self).verify_software()
235
236
237    def _verify_upstart_service(self, service, timeout_m):
238        """Verify that the given moblab service is running.
239
240        @param service: The upstart service to check for.
241        @timeout_m: Timeout (in minuts) before giving up.
242        @raises TimeoutException or UpstartServiceNotRunning if service isn't
243                running.
244        """
245        @retry.retry(error.AutoservError, timeout_min=timeout_m, delay_sec=10)
246        def _verify():
247            if not self.upstart_status(service):
248                raise UpstartServiceNotRunning(service)
249        _verify()
250
251    def verify_moblab_services(self, timeout_m):
252        """Verify the required Moblab services are up and running.
253
254        @param timeout_m: Timeout (in minutes) for how long to wait for services
255                to start. Actual time taken may be slightly more than this.
256        @raises AutoservError if any moblab service is not running.
257        """
258        if not MOBLAB_SERVICES:
259            return
260
261        service = MOBLAB_SERVICES[0]
262        try:
263            # First service can take a long time to start, especially on first
264            # boot where container setup can take 5-10 minutes, depending on the
265            # device.
266            self._verify_upstart_service(service, timeout_m)
267        except error.TimeoutException:
268            raise UpstartServiceNotRunning(service)
269
270        for service in MOBLAB_SERVICES[1:]:
271            try:
272                # Follow up services should come up quickly.
273                self._verify_upstart_service(service, 0.5)
274            except error.TimeoutException:
275                raise UpstartServiceNotRunning(service)
276
277        for process in MOBLAB_PROCESSES:
278            try:
279                self.run('pgrep %s' % process)
280            except error.AutoservRunError:
281                raise error.AutoservError('Moblab process: %s is not running.'
282                                          % process)
283
284
285    def _check_afe(self):
286        """Verify whether afe of moblab works before verifying its DUTs.
287
288        Verifying moblab sometimes happens after a successful provision, in
289        which case moblab is restarted but tunnel of afe is not re-connected.
290        This func is used to check whether afe is working now.
291
292        @return True if afe works.
293        @raises error.AutoservError if AFE is down; other exceptions are passed
294                through.
295        """
296        try:
297            self.afe.get_hosts()
298        except (error.TimeoutException, timeout_util.TimeoutError) as e:
299            raise error.AutoservError('Moblab AFE is not responding: %s' %
300                                      str(e))
301        except Exception as e:
302            logging.error('Unknown exception when checking moblab AFE: %s', e)
303            raise
304
305        return True
306
307
308    def verify_duts(self):
309        """Verify the Moblab DUTs are up and running.
310
311        @raises AutoservError if no DUTs are in the Ready State.
312        """
313        hosts = self.afe.reverify_hosts()
314        logging.debug('DUTs scheduled for reverification: %s', hosts)
315
316
317    def verify_special_tasks_complete(self):
318        """Wait till the special tasks on the moblab host are complete."""
319        total_time = 0
320        while (self.afe.get_special_tasks(is_complete=False) and
321               total_time < DUT_VERIFY_TIMEOUT):
322            total_time = total_time + DUT_VERIFY_SLEEP_SECS
323            time.sleep(DUT_VERIFY_SLEEP_SECS)
324        if not self.afe.get_hosts(status='Ready'):
325            for host in self.afe.get_hosts():
326                logging.error('DUT: %s Status: %s', host, host.status)
327            raise error.AutoservError('Moblab has 0 Ready DUTs')
328
329
330    def get_platform(self):
331        """Determine the correct platform label for this host.
332
333        For Moblab devices '_moblab' is appended.
334
335        @returns a string representing this host's platform.
336        """
337        return super(MoblabHost, self).get_platform() + '_moblab'
338
339
340    def make_tmp_dir(self, base=MOBLAB_TMP_DIR):
341        """Creates a temporary directory.
342
343        @param base: The directory where it should be created.
344
345        @return Path to a newly created temporary directory.
346        """
347        self.run('mkdir -p %s' % base)
348        return self.run('mktemp -d -p %s' % base).stdout.strip()
349
350
351    def get_os_type(self):
352        return 'moblab'
353