autoupdate_EndToEndTest.py revision 6c55bdb98e967675456a71a0971b81058536cac8
10ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
20ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold# Use of this source code is governed by a BSD-style license that can be
30ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold# found in the LICENSE file.
40ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
50ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport json
60ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport logging
70ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport time
86c55bdb98e967675456a71a0971b81058536cac8Chris Sosaimport os
90ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport urllib2
100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport urlparse
1103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
126c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.client.bin import utils as client_utils
136c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.client.common_lib import error, global_config
140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldfrom autotest_lib.client.common_lib.cros import autoupdater, dev_server
156c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.server import hosts, test
160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnolddef _wait(secs, desc=None):
190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Emits a log message and sleeps for a given number of seconds."""
200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    msg = 'waiting %s seconds' % secs
210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    if desc:
220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        msg += ' (%s)' % desc
230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    logging.info(msg)
240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    time.sleep(secs)
250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEvent(object):
280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Defines an expected event in a host update process."""
290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __init__(self, event_type=None, event_result=None, version=None,
300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                 previous_version=None):
310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._expected_attrs = {
320ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_type': event_type,
330ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_result': event_result,
340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'version': version,
350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'previous_version': previous_version,
360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        }
370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __str__(self):
400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ' '.join(['%s=%s' % (attr_name, attr_val or 'any')
410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                         for attr_name, attr_val
420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                         in self._expected_attrs.iteritems()])
430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, actual_event):
460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify the attributes of an actual event.
470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @params actual_event: a dictionary containing event attributes
490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if all attributes as expected, False otherwise.
510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return all([self._verify_attr(attr_name, expected_attr_val,
540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                                      actual_event.get(attr_name))
550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    for attr_name, expected_attr_val
560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    in self._expected_attrs.iteritems() if expected_attr_val])
570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _verify_attr(self, attr_name, expected_attr_val, actual_attr_val):
600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual log event attributes matches expected on.
610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param attr_name: name of the attribute to verify
630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_attr_val: expected attribute value
640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param actual_attr_val: actual attribute value
650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if actual value is present and matches, False otherwise.
670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if not (actual_attr_val and
700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                str(actual_attr_val) == str(expected_attr_val)):
710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            logging.error(
720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    'actual %s (%s) not as expected (%s)',
730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    attr_name, actual_attr_val, expected_attr_val)
740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            return False
750ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return True
770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEventChain(object):
800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Defines a chain of expected update events."""
810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __init__(self, *expected_event_chain_args):
820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Initialize the chain object.
830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_event_chain_args: list of tuples arguments, each
850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               containing a timeout (in seconds) and an ExpectedUpdateEvent
860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               object.
870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
880ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._expected_event_chain = expected_event_chain_args
900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _format_event_with_timeout(self, timeout, expected_event):
930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ('%s %s' %
940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                (expected_event,
950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                 ('within %s seconds' % timeout) if timeout
960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                 else 'indefinitely'))
970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __str__(self):
1000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ('[%s]' %
1010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                ', '.join(
1020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    [self._format_event_with_timeout(timeout, expected_event)
1030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                     for timeout, expected_event
1040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                     in self._expected_event_chain]))
1050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __repr__(self):
1080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return str(self._expected_event_chain)
1090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, get_next_event):
1120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual stream of events complies.
1130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: a function returning the next event
1150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if chain was satisfied, False otherwise.
1170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        for timeout, expected_event in self._expected_event_chain:
1200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            logging.info(
1210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    'expecting %s',
1220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    self._format_event_with_timeout(timeout, expected_event))
1230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            if not self._verify_event_with_timeout(
1240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    timeout, expected_event, get_next_event):
1250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                return False
1260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return True
1270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _verify_event_with_timeout(self, timeout, expected_event,
1300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                                   get_next_event):
1310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify an expected event occurs within a given timeout.
1320ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1330ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param timeout: specified in seconds
1340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_event: an expected event specification
1350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: function returning the next event in a stream
1360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if event complies, False otherwise.
1380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        base_timestamp = curr_timestamp = time.time()
1410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        expired_timestamp = base_timestamp + timeout
1420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        while curr_timestamp <= expired_timestamp:
1430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            new_event = get_next_event()
1440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            if new_event:
1450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                logging.info('event received after %s seconds',
1460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                             curr_timestamp - base_timestamp)
1470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                return expected_event.verify(new_event)
1480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # No new events, sleep for one second only (so we don't miss
1500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # events at the end of the allotted timeout).
1510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            time.sleep(1)
1520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            curr_timestamp = time.time()
1530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.error('timeout expired')
1550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return False
1560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass UpdateEventLogVerifier(object):
1590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Verifies update event chains on a devserver update log."""
16003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    def __init__(self, event_log_url, url_request_timeout=None):
1610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log_url = event_log_url
16203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        self._url_request_timeout = url_request_timeout
1630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log = []
1640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._num_consumed_events = 0
1650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify_expected_event_chain(self, expected_event_chain):
1680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify a given event chain."""
1690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return expected_event_chain.verify(self._get_next_log_event)
1700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _get_next_log_event(self):
1730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Returns the next event in an event log.
1740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1750ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        Uses the URL handed to it during initialization to obtain the host log
1760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        from a devserver. If new events are encountered, the first of them is
1770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        consumed and returned.
1780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return The next new event in the host log, as reported by devserver;
18003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                None if no such event was found or an error occurred.
1810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # (Re)read event log from devserver, if necessary.
1840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) <= self._num_consumed_events:
18503901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            try:
18603901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                if self._url_request_timeout:
18703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url,
18803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                                           timeout=self._url_request_timeout)
18903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                else:
19003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url)
19103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            except urllib2.URLError, e:
19203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                logging.warning('urlopen failed: %s', e)
19303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                return None
19403901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
1950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            event_log_resp = conn.read()
1960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            conn.close()
1970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._event_log = json.loads(event_log_resp)
1980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
19903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        # Return next new event, if one is found.
2000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) > self._num_consumed_events:
2010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            new_event = self._event_log[self._num_consumed_events]
2020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._num_consumed_events += 1
2030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            logging.info('consumed new event: %s', new_event)
2040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            return new_event
2050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass OmahaDevserver(object):
2080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Spawns a test-private devserver instance."""
2090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    _WAIT_FOR_DEVSERVER_STARTED_SECONDS = 15
21003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    _WAIT_SLEEP_INTERVAL = 1
2110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
21303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    def __init__(self, omaha_host, devserver_dir, dut_ip_addr,
21403901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                 update_payload_lorry_url):
2150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Starts a private devserver instance, operating at Omaha capacity.
2160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param omaha_host: host address where the devserver is spawned.
21803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        @param devserver_dir: path to the devserver source directory
2190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param dut_ip_addr: the IP address of the client DUT, used for deriving
2200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               a unique port number.
2210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param update_payload_lorry_url: URL to provision for update requests.
2220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
2240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if not update_payload_lorry_url:
2250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            raise error.TestError('missing update payload url')
2260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2276c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._omaha_host = omaha_host
2280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._omaha_port = self._get_unique_port(dut_ip_addr)
2296c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_dir = devserver_dir
2306c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._update_payload_lorry_url = update_payload_lorry_url
2316c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2326c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh = hosts.SSHHost(self._omaha_host,
2336c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                            user=os.environ['USER'])
2346c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_output = '/tmp/devserver.%s' % self._omaha_port
2356c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_pid = None
2360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2376c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2386c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def start_devserver(self):
2396c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Starts the devserver and stores the remote pid in self._devserver_pid
2406c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """
2416c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        update_payload_url_base, update_payload_path, _ = self._split_url(
2426c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                self._update_payload_lorry_url)
2436c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        # Invoke the Omaha/devserver on the remote server.
2440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        cmdlist = [
2456c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                '%s/devserver.py' % self._devserver_dir,
2460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--archive_dir=static/',
2470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--payload=%s' % update_payload_path,
2480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--port=%d' % self._omaha_port,
2490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--remote_payload',
2500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--urlbase=%s' % update_payload_url_base,
2510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--max_updates=1',
2520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--host_log',
2530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        ]
2546c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        # In the remote case that a previous devserver is still running,
2556c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        # kill it.
2566c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        devserver_pid = self._remote_devserver_pid()
2576c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if devserver_pid:
2586c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            logging.warning('Previous devserver still running. Killing.')
2596c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            self._kill_devserver_pid(devserver_pid)
2606c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            self._devserver_ssh.run('rm -f %s' % self._devserver_output,
2616c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                    ignore_status=True)
2626c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2636c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        remote_cmd = '( %s ) </dev/null >%s 2>&1 & echo $!' % (
2646c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                ' '.join(cmdlist), self._devserver_output)
2656c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        logging.info('Starting devserver with %r', remote_cmd)
2666c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_pid = self._devserver_ssh.run_output(remote_cmd)
2676c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2686c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2696c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def _kill_devserver_pid(self, pid):
2706c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Kills devserver with given pid and verifies devserver is down.
2716c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2726c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        @param pid: The pid of the devserver to kill.
2736c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2746c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        @raise client_utils.TimeoutError if we are unable to kill the devserver
2756c55bdb98e967675456a71a0971b81058536cac8Chris Sosa               within the default timeouts (11 seconds).
2766c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """
2776c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        def _devserver_down():
2786c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            return self._remote_devserver_pid() == None
2796c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2806c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh.run('kill %s' % pid)
2816c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        try:
2826c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            client_utils.poll_for_condition(_devserver_down,
2836c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                            sleep_interval=1)
2846c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            return
2856c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        except client_utils.TimeoutError:
2866c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            logging.warning('Could not gracefully shut down devserver.')
2876c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2886c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh.run('kill -9 %s' % pid)
2896c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        client_utils.poll_for_condition(_devserver_down, timeout=1)
2906c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2916c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
2926c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def _remote_devserver_pid(self):
2936c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """If a devserver is running on our port, return its pid."""
2946c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        # fuser returns pid in its stdout if found.
2956c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        result = self._devserver_ssh.run('fuser -n tcp %d' % self._omaha_port,
2966c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                         ignore_status=True)
2976c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if result.exit_status == 0:
2986c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            return result.stdout.strip()
2996c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3006c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3016c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def wait_for_devserver_to_start(self):
3026c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Returns True if the devserver has started within the time limit."""
3030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        timeout = self._WAIT_FOR_DEVSERVER_STARTED_SECONDS
3046c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        netloc = self.get_netloc()
3056c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        while(timeout > 0):
3066c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            time.sleep(self._WAIT_SLEEP_INTERVAL)
3076c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            timeout -= self._WAIT_SLEEP_INTERVAL
3086c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            if dev_server.DevServer.devserver_up('http://%s' % netloc):
3096c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                return True
3106c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        else:
3116c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            self.log_output()
3126c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            return False
3136c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3146c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3156c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def get_netloc(self):
3166c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Returns the netloc (host:port) of the devserver."""
3176c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if not self._devserver_pid:
3186c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            raise error.TestError('no running omaha/devserver')
3196c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3206c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3216c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        return '%s:%s' % (self._omaha_host, self._omaha_port)
3220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3236c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3246c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def log_output(self):
3256c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Logs the output of the devserver log to logging.error."""
3266c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if self._devserver_pid:
3276c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            logging.error(self._devserver_ssh.run_output(
3286c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                    'cat %s' % self._devserver_output))
3290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    @staticmethod
3320ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _split_url(url):
3330ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Splits a URL into the URL base, path and file name."""
3340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        split_url = urlparse.urlsplit(url)
3350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        url_base = urlparse.urlunsplit(
3360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                [split_url.scheme, split_url.netloc, '', '', ''])
3370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        url_path = url_file = ''
3380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if split_url.path:
3390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            url_path, url_file = split_url.path.rsplit('/', 1)
3400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return url_base, url_path.lstrip('/'), url_file
3410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    @staticmethod
3440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _get_unique_port(dut_ip_addr):
3450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Compute a unique IP port based on the DUT's IP address.
3460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        We need a mapping that can be mirrored by a DUT running an official
3480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        image, based only on the DUT's own state. Here, we simply take the two
3490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        least significant bytes in the DUT's IPv4 address and bitwise-OR them
3500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        with 0xc0000, resulting in a 16-bit IP port within the
3510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        private/unallocated range. Using the least significant bytes of the IP
3520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        address guarantees (sort of) that we'll have a unique mapping in a
3530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        small lab setting.
3540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
3560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        ip_addr_bytes = [int(byte_str) for byte_str in dut_ip_addr.split('.')]
3570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return (((ip_addr_bytes[2] << 8) | ip_addr_bytes[3] | 0x8000) & ~0x4000)
3580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def kill(self):
3610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Kill private devserver, wait for it to die."""
3626c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if not self._devserver_pid:
3630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            raise error.TestError('no running omaha/devserver')
3646c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.info('killing omaha/devserver')
3666c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._kill_devserver_pid(self._devserver_pid)
3676c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh.run('rm -f %s' % self._devserver_output)
3680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass autoupdate_EndToEndTest(test.test):
3710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Complete update test between two Chrome OS releases.
3720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    Performs an end-to-end test of updating a ChromeOS device from one version
3740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    to another. This script requires a running (possibly remote) servod
3750ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    instance connected to an actual servo board, which controls the DUT. It
3760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    also assumes that a corresponding target (update) image was staged for
3770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    download on the central Lorry/devserver.
3780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    The test performs the following steps:
3800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      0. Stages the source image and target update payload on the central
3820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         Lorry/devserver.
3830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      1. Spawns a private Omaha/devserver instance, configured to return the
3840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         target (update) image URL in response for an update check.
3850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      2. Connects to servod.
3860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         a. Resets the DUT to a known initial state.
3870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         b. Installs a source image on the DUT via recovery.
3880ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      3. Reboots the DUT with the new image.
3890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      4. Triggers an update check at the DUT.
3900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      5. Watches as the DUT obtains an update and applies it.
3910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      6. Repeats 3-5, ensuring that the next update check shows the new image
3920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         version.
3930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """
3950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    version = 1
3960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    # Timeout periods, given in seconds.
398f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_AFTER_SHUTDOWN_SECONDS = 10
399f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_AFTER_UPDATE_SECONDS = 20
400f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_USB_INSTALL_SECONDS = 4 * 60
401f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_MP_RECOVERY_SECONDS = 8 * 60
4020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    _WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS = 12 * 60
403f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_DOWNLOAD_STARTED_SECONDS = 2 * 60
4046c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    _WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS = 10 * 60
405f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_UPDATE_COMPLETED_SECONDS = 4 * 60
4060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    _WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS = 15 * 60
40703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    _DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS = 30
4080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    # Omaha event types/results, from update_engine/omaha_request_action.h
410f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_TYPE_UNKNOWN = 0
4110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    EVENT_TYPE_DOWNLOAD_COMPLETE = 1
412f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_TYPE_INSTALL_COMPLETE = 2
413f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_TYPE_UPDATE_COMPLETE = 3
414f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_TYPE_DOWNLOAD_STARTED = 13
4150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    EVENT_TYPE_DOWNLOAD_FINISHED = 14
416f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_RESULT_ERROR = 0
417f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_RESULT_SUCCESS = 1
418f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    EVENT_RESULT_SUCCESS_REBOOT = 2
4190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    EVENT_RESULT_UPDATE_DEFERRED = 9
4200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
422f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _servo_dut_power_up(self):
4230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Powers up the DUT, optionally simulating a Ctrl-D key press."""
424f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.power_short_press()
425f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._dev_mode:
426f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.servo.pass_devmode()
4270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
429f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _servo_dut_reboot(self, disconnect_usbkey=False):
4300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Reboots a DUT.
4310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
432e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury        @param disconnect_usbkey: detach USB flash device from the DUT before
433e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury               powering it back up; this is useful when (for example) a USB
434e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury               booted device need not see the attached USB key after the
435e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury               reboot.
4360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestFail if DUT fails to reboot.
4380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
4400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.info('rebooting dut')
441f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.power_long_press()
4420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        _wait(self._WAIT_AFTER_SHUTDOWN_SECONDS, 'after shutdown')
443e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury        if disconnect_usbkey:
444f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.servo.switch_usbkey('host')
445f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
446f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._servo_dut_power_up()
447f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
448f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            if not self._host.wait_up(timeout=self._host.BOOT_TIMEOUT):
4490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                raise error.TestFail(
4500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                        'dut %s failed to boot after %d secs' %
451f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                        (self._host.ip, self._host.BOOT_TIMEOUT))
4520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        else:
4530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold          # TODO(garnold) chromium-os:33766: implement waiting for MP-signed
4540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold          # images; ideas include waiting for a ping reply, or using a GPIO
4550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold          # signal.
4560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold          pass
4570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
459f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _install_mp_image(self, lorry_image_url):
4600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Installs an MP-signed recovery image on a DUT.
4610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param lorry_image_url: URL of the image on a Lorry/devserver
4630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
4640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Flash DUT with source image version, using recovery.
4650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.info('installing source mp-signed image via recovery: %s',
4660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                     lorry_image_url)
467f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.install_recovery_image(
4680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                lorry_image_url,
4690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                wait_timeout=self._WAIT_FOR_MP_RECOVERY_SECONDS)
4700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Reboot the DUT after installation.
472f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._servo_dut_reboot(disconnect_usbkey=True)
4730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
475f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _install_test_image_with_servo(self, lorry_image_url):
4760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Installs a test image on a DUT, booted via recovery.
4770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param lorry_image_url: URL of the image on a Lorry/devserver
4790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param is_dev_nmode: whether or not the DUT is in dev mode
4800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestFail if DUT cannot boot the test image from USB;
4820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               AutotestHostRunError if failed to run the install command on the
4830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               DUT.
4840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
4860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.info('installing source test image via recovery: %s',
4870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                     lorry_image_url)
488f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.install_recovery_image(lorry_image_url)
4890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.info('waiting for image to boot')
490f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if not self._host.wait_up(timeout=self._host.USB_BOOT_TIMEOUT):
4910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold          raise error.TestFail(
4920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold              'dut %s boot from usb timed out after %d secs' %
493f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa              (self._host, self._host.USB_BOOT_TIMEOUT))
4940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        logging.info('installing new image onto ssd')
4950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        try:
496f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            cmd_result = self._host.run(
4970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    'chromeos-install --yes',
4980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    timeout=self._WAIT_FOR_USB_INSTALL_SECONDS,
4990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    stdout_tee=None, stderr_tee=None)
500f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        except error.AutotestHostRunError:
5010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # Dump stdout (with stderr) to the error log.
5020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            logging.error('command failed, stderr:\n' + cmd_result.stderr)
5030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            raise
5040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Reboot the DUT after installation.
506f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._servo_dut_reboot(disconnect_usbkey=True)
5070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
509f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _trigger_test_update(self, omaha_netloc):
5100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Trigger an update check on a test image.
5110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        Uses update_engine_client via SSH. This is an async call, hence a very
5130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        short timeout.
5140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param omaha_netloc: the network location of the Omaha/devserver
5160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               (http://host:port)
5170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise RootFSUpdateError if anything went wrong.
5190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
5210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        omaha_update_url = urlparse.urlunsplit(
5220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                ['http', omaha_netloc, '/update', '', ''])
523f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        updater = autoupdater.ChromiumOSUpdater(omaha_update_url,
524f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                                                host=self._host)
5250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        updater.trigger_update()
5260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
528f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _get_rootdev(self):
52909706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        """Returns the partition device containing the rootfs on a host.
53009706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
53109706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        @return The rootfs partition device (string).
53209706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
53309706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        @raise AutotestHostRunError if command failed to run on host.
53409706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
53509706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        """
53609706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        # This command should return immediately, hence the short timeout.
537f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        return self._host.run('rootdev -s', timeout=10).stdout.strip()
53809706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
53909706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
540f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def stage_image(self, lorry_devserver, image_uri, board, release, branch):
5410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Stage a Chrome OS image on Lorry/devserver.
5420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return URL of the staged image on the server.
5440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestError if there's a problem with staging.
5460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
5480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        staged_url = None
549f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
5500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # For this call, we just need the URL path up to the image.zip file
5510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # (exclusive).
5520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            image_uri_path = urlparse.urlsplit(image_uri).path.partition(
5530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    'image.zip')[0].strip('/')
5540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            try:
5550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                lorry_devserver.trigger_test_image_download(image_uri_path)
5560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                staged_url = lorry_devserver.get_test_image_url(
5570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                        board, release, branch)
5580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            except dev_server.DevServerException, e:
5590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                raise error.TestError(
56003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                        'failed to stage source test image: %s' % e)
5610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        else:
5620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # TODO(garnold) chromium-os:33766: implement staging of MP-signed
5630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # images.
5640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            pass
5650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if not staged_url:
5670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            raise error.TestError('staged source test image url missing')
5680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return staged_url
5690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def stage_payload(self, lorry_devserver, payload_uri, board, release,
572f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                      branch, is_delta, is_nton):
5730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Stage an update target payload on Lorry/devserver.
5740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5750ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return URL of the staged payload on the server.
5760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestError if there's a problem with staging.
5780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
5800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        staged_url = None
581f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
5820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # For this call, we'll need the URL path without the payload file
5830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # name.
5840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            payload_uri_path = urlparse.urlsplit(payload_uri).path.rsplit(
5850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    '/', 1)[0].strip('/')
5860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            try:
5870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                lorry_devserver.trigger_download(payload_uri_path)
5880ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                if is_delta:
5890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    staged_url = lorry_devserver.get_delta_payload_url(
5900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                            'nton' if is_nton else 'mton',
5910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                            board, release, branch)
5920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                else:
5930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    staged_url = lorry_devserver.get_full_payload_url(
5940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                            board, release, branch)
5950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            except dev_server.DevServerException, e:
596f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                raise error.TestError('failed to stage test payload: %s' % e)
5970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        else:
5980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # TODO(garnold) chromium-os:33766: implement staging of MP-signed
5990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # images.
6000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            pass
6010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if not staged_url:
603f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.TestError('staged test payload url missing')
604f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
6050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return staged_url
6060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
608f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _install_source_image(self, image_url):
609f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Prepare the specified host with the image."""
610f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_servo:
611f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # Install source image (test vs MP).
612f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            if self._use_test_image:
613f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                self._install_test_image_with_servo(image_url)
614f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            else:
615f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                self._install_mp_image(image_url)
616f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
617f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
618f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # image_url is of the format that is in the devserver i.e.
619f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # <hostname>/static/BRANCH/VERSION/update.gz.
6206c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            # We want to transform it to the correct omaha url which is
621f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # <hostname>/update/BRANCH/VERSION.
622f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            image_url_dir = image_url.rpartition('/update.gz')[0]
623f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            image_url_dir = image_url_dir.replace('/static/', '/update/')
624f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.machine_install(image_url_dir, force_update=True)
625f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
626f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
627f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _stage_images_onto_devserver(self, lorry_devserver, test_conf):
628f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Stages images that will be used by the test onto the devserver.
629f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
630f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        @return a tuple containing the urls of the source and target payloads.
631f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """
632f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        logging.info('staging images onto lorry/devserver (%s)',
633f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     lorry_devserver.url())
634f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
635f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        source_url = None
636f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_servo:
637f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            source_url = self.stage_image(
638f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    lorry_devserver, test_conf['source_image_uri'],
639f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    test_conf['board'], test_conf['source_release'],
640f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    test_conf['source_branch'])
641f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
642f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            source_url = self.stage_payload(
643f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    lorry_devserver, test_conf['source_image_uri'],
644f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    test_conf['board'], test_conf['source_release'],
645f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    test_conf['source_branch'], False, False)
646f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
647f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        return source_url, self.stage_payload(
648f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                lorry_devserver, test_conf['target_payload_uri'],
649f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                test_conf['board'], test_conf['target_release'],
650f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                test_conf['target_branch'],
651f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                test_conf['update_type'] == 'delta',
652f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                test_conf['target_release'] == test_conf['source_release'])
653f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
654f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
655f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def initialize(self):
656f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Sets up variables that will be used by test."""
657f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host = None
658f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._use_servo = False
659f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._dev_mode = False
660f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._omaha_devserver = None
661f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
662f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._use_test_image = True
663f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._devserver_dir = global_config.global_config.get_config_value(
664f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                'CROS', 'devserver_dir', default=None)
665f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._devserver_dir is None:
666f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.TestError(
667f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    'path to devserver source tree not provided; please define '
668f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    'devserver_dir under [CROS] in your shadow_config.ini')
669f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
670f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
671f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def cleanup(self):
672f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Kill the omaha devserver if it's still around."""
673f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._omaha_devserver:
674f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._omaha_devserver.kill()
675f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
676f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._omaha_devserver = None
677f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
678f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
679f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _verify_preconditions(self, test_conf):
680f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Validate input args make sense."""
681f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_servo and not self._host.servo:
682f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.AutotestError('Servo use specified but no servo '
683f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                                      'attached to host object.')
684f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
685f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if not self._use_test_image and not self._use_servo:
686f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.TestError("Can't install mp image without servo.")
687f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
688f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
689f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def run_once(self, host, test_conf, use_servo):
6900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Performs a complete auto update test.
6910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param host: a host object representing the DUT
6930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param test_conf: a dictionary containing test configuration values
694f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        @param use_servo: True whether we should use servo.
6950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestError if anything went wrong with setting up the test;
6970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               error.TestFail if any part of the test has failed.
6980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
700f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host = host
701f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._use_test_image = test_conf.get('image_type') != 'mp'
702f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._use_servo = use_servo
703f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_servo:
704f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._dev_mode = self._host.servo.get('dev_mode') == 'on'
7050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
706f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Verify that our arguments are sane.
707f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._verify_preconditions(test_conf)
708d70e0c8bd47ea824c5a84c5565a46690d64e4aa6Gilad Arnold
7090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Stage source images and update payloads on lorry/devserver. We use
7100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # the payload URI as argument for the lab's devserver load-balancing
7110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # mechanism.
7120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        lorry_devserver = dev_server.ImageServer.resolve(
7130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                test_conf['target_payload_uri'])
714f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        source_url, target_payload_url = self._stage_images_onto_devserver(
715f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                lorry_devserver, test_conf)
7160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
717f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Install the source image onto the DUT.
718f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._install_source_image(source_url)
71903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
720f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # On test images, record the active root partition.
721f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        source_rootfs_partition = None
722f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
723f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            source_rootfs_partition = self._get_rootdev()
724f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            logging.info('source image rootfs partition: %s',
725f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                         source_rootfs_partition)
7260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7276c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        omaha_host = urlparse.urlparse(lorry_devserver.url()).hostname
728f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._omaha_devserver = OmahaDevserver(
729f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                omaha_host, self._devserver_dir, self._host.ip,
730f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                target_payload_url)
73109706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
7326c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._omaha_devserver.start_devserver()
7336c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._omaha_devserver.wait_for_devserver_to_start()
7340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
735f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Trigger an update (test vs MP).
7366c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        omaha_netloc = self._omaha_devserver.get_netloc()
737f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
738f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._trigger_test_update(omaha_netloc)
739f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
740f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # TODO(garnold) chromium-os:33766: use GPIOs to trigger an
741f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # update.
742f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            pass
7430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
744f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Track update progress.
745f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        omaha_hostlog_url = urlparse.urlunsplit(
746f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                ['http', omaha_netloc, '/api/hostlog',
747f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 'ip=' + self._host.ip, ''])
748f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        logging.info('polling update progress from omaha/devserver: %s',
749f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     omaha_hostlog_url)
750f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        log_verifier = UpdateEventLogVerifier(
751f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                omaha_hostlog_url,
752f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                self._DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS)
753f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
754f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Verify chain of events in a successful update process.
755f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        chain = ExpectedUpdateEventChain(
756f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                (self._WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS,
757f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 ExpectedUpdateEvent(
758f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     version=test_conf['source_release'])),
759f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                (self._WAIT_FOR_DOWNLOAD_STARTED_SECONDS,
760f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 ExpectedUpdateEvent(
761f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_type=self.EVENT_TYPE_DOWNLOAD_STARTED,
762f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_result=self.EVENT_RESULT_SUCCESS,
763f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     version=test_conf['source_release'])),
764f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                (self._WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS,
765f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 ExpectedUpdateEvent(
766f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_type=self.EVENT_TYPE_DOWNLOAD_FINISHED,
767f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_result=self.EVENT_RESULT_SUCCESS,
768f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     version=test_conf['source_release'])),
769f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                (self._WAIT_FOR_UPDATE_COMPLETED_SECONDS,
770f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 ExpectedUpdateEvent(
771f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_type=self.EVENT_TYPE_UPDATE_COMPLETE,
772f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_result=self.EVENT_RESULT_SUCCESS,
773f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     version=test_conf['source_release'])))
774f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
775f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if not log_verifier.verify_expected_event_chain(chain):
776f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.TestFail(
777f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    'could not verify that update was successful')
778f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
779f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Wait after an update completion (safety margin).
780f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        _wait(self._WAIT_AFTER_UPDATE_SECONDS, 'after update completion')
781f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
782f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Reboot the DUT after the update.
783f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if use_servo:
784f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._servo_dut_reboot()
785f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
786f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.reboot()
7870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
788f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Trigger a second update check (again, test vs MP).
789f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
790f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._trigger_test_update(omaha_netloc)
791f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
792f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # TODO(garnold) chromium-os:33766: use GPIOs to trigger an
793f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # update.
794f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            pass
7950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
796f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Observe post-reboot update check, which should indicate that the
797f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # image version has been updated.
798f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        chain = ExpectedUpdateEventChain(
799f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                (self._WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS,
800f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 ExpectedUpdateEvent(
801f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_type=self.EVENT_TYPE_UPDATE_COMPLETE,
802f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     event_result=self.EVENT_RESULT_SUCCESS_REBOOT,
803f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     version=test_conf['target_release'],
804f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     previous_version=test_conf['source_release'])))
805f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if not log_verifier.verify_expected_event_chain(chain):
806f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.TestFail('could not verify that machine rebooted '
807f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                                 'after update')
808f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
809f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # On test images, make sure we're using a different partition after
810f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # the update.
811f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
812f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            target_rootfs_partition = self._get_rootdev()
813f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            if target_rootfs_partition == source_rootfs_partition:
814f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                raise error.TestFail(
815f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                        'rootfs partition did not change (%s)' %
816f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                        target_rootfs_partition)
8170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
818f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            logging.info('target image rootfs partition: %s',
819f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                         target_rootfs_partition)
8200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
821