autoupdate_EndToEndTest.py revision fea7e766e59f8356dad6f70e46c0ee25997863a5
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
52f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosaimport collections
60ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport json
70ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport logging
86c55bdb98e967675456a71a0971b81058536cac8Chris Sosaimport os
9a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnoldimport socket
1015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnoldimport time
110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport urllib2
120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport urlparse
1303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
146c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.client.bin import utils as client_utils
156c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.client.common_lib import error, global_config
160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldfrom autotest_lib.client.common_lib.cros import autoupdater, dev_server
17d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shifrom autotest_lib.client.common_lib.cros.graphite import autotest_stats
188ddeb9c8d6df7eb2e033651e39f434ebdf8af3ccSimran Basifrom autotest_lib.server import autotest, hosts, test
192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosafrom autotest_lib.server.cros.dynamic_suite import tools
200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnolddef _wait(secs, desc=None):
230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Emits a log message and sleeps for a given number of seconds."""
240338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    msg = 'Waiting %s seconds' % secs
250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    if desc:
260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        msg += ' (%s)' % desc
270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    logging.info(msg)
280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    time.sleep(secs)
290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
31ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosaclass ExpectedUpdateEventChainFailed(error.TestFail):
32ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    """Raised if we fail to receive an expected event in a chain."""
33ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
34f789cf3a52c720344062f0a6c782bb758f08b189Don Garrettclass RequiredArgumentMissing(error.TestFail):
35f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett    """Raised if we fail to receive an expected event in a chain."""
36f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
37ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
380338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold# Update event types.
390338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_COMPLETE = '1'
400338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_INSTALL_COMPLETE = '2'
410338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_UPDATE_COMPLETE = '3'
420338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_STARTED = '13'
430338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_FINISHED = '14'
440338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
450338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold# Update event results.
460338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_ERROR = '0'
470338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_SUCCESS = '1'
480338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_SUCCESS_REBOOT = '2'
490338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_UPDATE_DEFERRED = '9'
500338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
510338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEvent(object):
53ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    """Defines an expected event in a host update process.
54ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
55ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    Attrs:
56ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        _expected_attrs: Dictionary of attributes that should match events
57ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                         received. If attribute is not provided, assumes match.
58ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        error_message: What we should error out with if we fail to verify this
59ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       expected event.
60ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    """
6145f02ae47134953169805d281992c9edf0019250Chris Sosa
6245f02ae47134953169805d281992c9edf0019250Chris Sosa    # Omaha event types/results, from update_engine/omaha_request_action.h
6345f02ae47134953169805d281992c9edf0019250Chris Sosa    # These are stored in dict form in order to easily print out the keys.
640338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _EVENT_TYPE_DICT = {
650338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_TYPE_DOWNLOAD_COMPLETE: 'download_complete',
660338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_TYPE_INSTALL_COMPLETE: 'install_complete',
670338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_TYPE_UPDATE_COMPLETE: 'update_complete',
680338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_TYPE_DOWNLOAD_STARTED: 'download_started',
690338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_TYPE_DOWNLOAD_FINISHED: 'download_finished'
700338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    }
710338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
720338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _EVENT_RESULT_DICT = {
730338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_RESULT_ERROR: 'error',
740338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_RESULT_SUCCESS: 'success',
750338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_RESULT_SUCCESS_REBOOT: 'success_reboot',
760338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            EVENT_RESULT_UPDATE_DEFERRED: 'update_deferred'
770338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    }
780338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
790338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _ATTR_NAME_DICT_MAP = {
800338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            'event_type': _EVENT_TYPE_DICT,
810338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            'event_result': _EVENT_RESULT_DICT,
820338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    }
830338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
840338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _VALID_TYPES = set(_EVENT_TYPE_DICT.keys())
850338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _VALID_RESULTS = set(_EVENT_RESULT_DICT.keys())
8645f02ae47134953169805d281992c9edf0019250Chris Sosa
870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __init__(self, event_type=None, event_result=None, version=None,
88ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                 previous_version=None, error_message=None):
890338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if event_type and event_type not in self._VALID_TYPES:
9045f02ae47134953169805d281992c9edf0019250Chris Sosa            raise ValueError('event_type %s is not valid.' % event_type)
9145f02ae47134953169805d281992c9edf0019250Chris Sosa
920338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if event_result and event_result not in self._VALID_RESULTS:
9345f02ae47134953169805d281992c9edf0019250Chris Sosa            raise ValueError('event_result %s is not valid.' % event_result)
9445f02ae47134953169805d281992c9edf0019250Chris Sosa
950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._expected_attrs = {
960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_type': event_type,
970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_result': event_result,
980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'version': version,
990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'previous_version': previous_version,
1000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        }
101ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self.error_message = error_message
1020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1040338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    @staticmethod
1050338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    def _attr_val_str(attr_val, helper_dict, default=None):
1060338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """Returns an enriched attribute value string, or default."""
1070338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if not attr_val:
1080338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            return default
1090338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1100338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        s = str(attr_val)
1110338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if helper_dict:
1120338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            s += ':%s' % helper_dict.get(attr_val, 'unknown')
1130338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1140338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return s
1150338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1160338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1170338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    def _attr_name_and_values(self, attr_name, expected_attr_val,
1180338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                              actual_attr_val=None):
1190338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """Returns an attribute name, expected and actual value strings.
1200338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1210338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        This will return (name, expected, actual); the returned value for
1220338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        actual will be None if its respective input is None/empty.
1230338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1240338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """
1250338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        helper_dict = self._ATTR_NAME_DICT_MAP.get(attr_name)
1260338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        expected_attr_val_str = self._attr_val_str(expected_attr_val,
1270338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                   helper_dict,
1280338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                   default='any')
1290338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        actual_attr_val_str = self._attr_val_str(actual_attr_val, helper_dict)
1300338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1310338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return attr_name, expected_attr_val_str, actual_attr_val_str
1320338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1330338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __str__(self):
1350338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return ' '.join(['%s=%s' %
1360338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                         self._attr_name_and_values(attr_name, attr_val)[0:2]
1370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                         for attr_name, attr_val
1380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                         in self._expected_attrs.iteritems()])
1390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, actual_event):
1420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify the attributes of an actual event.
1430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
144ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param actual_event: a dictionary containing event attributes
1450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if all attributes as expected, False otherwise.
1470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return all([self._verify_attr(attr_name, expected_attr_val,
1500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                                      actual_event.get(attr_name))
1510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    for attr_name, expected_attr_val
1520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    in self._expected_attrs.iteritems() if expected_attr_val])
1530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _verify_attr(self, attr_name, expected_attr_val, actual_attr_val):
1560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual log event attributes matches expected on.
1570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param attr_name: name of the attribute to verify
1590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_attr_val: expected attribute value
1600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param actual_attr_val: actual attribute value
1610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if actual value is present and matches, False otherwise.
1630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
16545f02ae47134953169805d281992c9edf0019250Chris Sosa        # None values are assumed to be missing and non-matching.
16645f02ae47134953169805d281992c9edf0019250Chris Sosa        if not actual_attr_val:
1670338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.error('No value found for %s (expected %s)',
1680338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                          *self._attr_name_and_values(attr_name,
1690338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                      expected_attr_val)[0:2])
17045f02ae47134953169805d281992c9edf0019250Chris Sosa            return False
17145f02ae47134953169805d281992c9edf0019250Chris Sosa
1720338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        # Convert actual value to a string.
17345f02ae47134953169805d281992c9edf0019250Chris Sosa        actual_attr_val = str(actual_attr_val)
17445f02ae47134953169805d281992c9edf0019250Chris Sosa
17545f02ae47134953169805d281992c9edf0019250Chris Sosa        if not actual_attr_val == expected_attr_val:
1760338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            # We allow expected version numbers (e.g. 2940.0.0) to be contained
1770338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            # in actual values (2940.0.0-a1); this is necessary for the test to
1780338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            # pass with developer / non-release images.
1790338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            if 'version' in attr_name and expected_attr_val in actual_attr_val:
1800338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.info('Expected %s (%s) contained in actual value (%s) '
1810338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             'but does not match exactly',
1820338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             *self._attr_name_and_values(
1830338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                     attr_name, expected_attr_val,
1840338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                     actual_attr_val=actual_attr_val))
185fec1349a3e70eb41dce8bc07a8c63563d23d64b2Chris Sosa                return True
186fec1349a3e70eb41dce8bc07a8c63563d23d64b2Chris Sosa
1870338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.error('Expected %s (%s) different from actual value (%s)',
1880338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                          *self._attr_name_and_values(
1890338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                  attr_name, expected_attr_val,
1900338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                  actual_attr_val=actual_attr_val))
1910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            return False
1920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return True
1940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEventChain(object):
1970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Defines a chain of expected update events."""
1980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __init__(self, *expected_event_chain_args):
1990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Initialize the chain object.
2000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_event_chain_args: list of tuples arguments, each
2020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               containing a timeout (in seconds) and an ExpectedUpdateEvent
2030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               object.
2040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
2060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._expected_event_chain = expected_event_chain_args
2070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
209cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
210cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    def _format_event_with_timeout(timeout, expected_event):
211cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """Returns a string representation of the event, with timeout."""
2120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ('%s %s' %
2130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                (expected_event,
2140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                 ('within %s seconds' % timeout) if timeout
2150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                 else 'indefinitely'))
2160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __str__(self):
2190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ('[%s]' %
2200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                ', '.join(
2210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    [self._format_event_with_timeout(timeout, expected_event)
2220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                     for timeout, expected_event
2230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                     in self._expected_event_chain]))
2240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __repr__(self):
2270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return str(self._expected_event_chain)
2280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, get_next_event):
2310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual stream of events complies.
2320ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2330ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: a function returning the next event
2340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
235ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify an event.
2360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
2380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        for timeout, expected_event in self._expected_event_chain:
2390338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Expecting %s',
240ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                         self._format_event_with_timeout(timeout,
241ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                                         expected_event))
2420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            if not self._verify_event_with_timeout(
2430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    timeout, expected_event, get_next_event):
244ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                logging.error('Failed expected event: %s',
245ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                              expected_event.error_message)
246ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                raise ExpectedUpdateEventChainFailed(
247ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                        expected_event.error_message)
2480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
250cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
251cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    def _verify_event_with_timeout(timeout, expected_event, get_next_event):
2520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify an expected event occurs within a given timeout.
2530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param timeout: specified in seconds
2550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_event: an expected event specification
2560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: function returning the next event in a stream
2570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if event complies, False otherwise.
2590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
2610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        base_timestamp = curr_timestamp = time.time()
2620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        expired_timestamp = base_timestamp + timeout
2630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        while curr_timestamp <= expired_timestamp:
2640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            new_event = get_next_event()
2650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            if new_event:
2660338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.info('Event received after %s seconds',
2670338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             round(curr_timestamp - base_timestamp, 1))
2680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                return expected_event.verify(new_event)
2690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # No new events, sleep for one second only (so we don't miss
2710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # events at the end of the allotted timeout).
2720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            time.sleep(1)
2730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            curr_timestamp = time.time()
2740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2750338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.error('Timeout expired')
2760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return False
2770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass UpdateEventLogVerifier(object):
2800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Verifies update event chains on a devserver update log."""
28103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    def __init__(self, event_log_url, url_request_timeout=None):
2820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log_url = event_log_url
28303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        self._url_request_timeout = url_request_timeout
2840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log = []
2850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._num_consumed_events = 0
2860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2880ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify_expected_event_chain(self, expected_event_chain):
289ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        """Verify a given event chain.
290ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa
291ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param expected_event_chain: instance of expected event chain.
292ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
293ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify the an
294ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                event.
295ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        """
296ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        expected_event_chain.verify(self._get_next_log_event)
2970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _get_next_log_event(self):
3000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Returns the next event in an event log.
3010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        Uses the URL handed to it during initialization to obtain the host log
3030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        from a devserver. If new events are encountered, the first of them is
3040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        consumed and returned.
3050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return The next new event in the host log, as reported by devserver;
30703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                None if no such event was found or an error occurred.
3080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
3100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # (Re)read event log from devserver, if necessary.
3110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) <= self._num_consumed_events:
31203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            try:
31303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                if self._url_request_timeout:
31403901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url,
31503901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                                           timeout=self._url_request_timeout)
31603901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                else:
31703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url)
31803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            except urllib2.URLError, e:
3190338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.warning('Failed to read event log url: %s', e)
32003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                return None
321a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold            except socket.timeout, e:
322a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold                logging.warning('Timed out reading event log url: %s', e)
323a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold                return None
32403901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
3250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            event_log_resp = conn.read()
3260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            conn.close()
3270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._event_log = json.loads(event_log_resp)
3280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
32903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        # Return next new event, if one is found.
3300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) > self._num_consumed_events:
3310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            new_event = self._event_log[self._num_consumed_events]
3320ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._num_consumed_events += 1
3330338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Consumed new event: %s', new_event)
3340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            return new_event
3350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
337793344b359304c31d78197788b55cfbbe2636025Chris Sosaclass OmahaDevserverFailedToStart(error.TestError):
338793344b359304c31d78197788b55cfbbe2636025Chris Sosa    """Raised when a omaha devserver fails to start."""
339793344b359304c31d78197788b55cfbbe2636025Chris Sosa
340793344b359304c31d78197788b55cfbbe2636025Chris Sosa
3410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass OmahaDevserver(object):
3420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Spawns a test-private devserver instance."""
343793344b359304c31d78197788b55cfbbe2636025Chris Sosa    # How long to wait for a devserver to start.
34452c35724507ec105053d2af792fe161a627e05c1Alex Deymo    _WAIT_FOR_DEVSERVER_STARTED_SECONDS = 30
345793344b359304c31d78197788b55cfbbe2636025Chris Sosa
346260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    # How long to sleep (seconds) between checks to see if a devserver is up.
34703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    _WAIT_SLEEP_INTERVAL = 1
3480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3496f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    # Max devserver execution time (seconds); used with timeout(1) to ensure we
3506f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    # don't have defunct instances hogging the system.
3516f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    _DEVSERVER_TIMELIMIT_SECONDS = 12 * 60 * 60
352793344b359304c31d78197788b55cfbbe2636025Chris Sosa
353793344b359304c31d78197788b55cfbbe2636025Chris Sosa
354260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def __init__(self, omaha_host, devserver_dir, update_payload_staged_url):
3550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Starts a private devserver instance, operating at Omaha capacity.
3560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param omaha_host: host address where the devserver is spawned.
35803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        @param devserver_dir: path to the devserver source directory
359ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param update_payload_staged_url: URL to provision for update requests.
3600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
362ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        if not update_payload_staged_url:
3630338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('Missing update payload url')
3640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3656c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._omaha_host = omaha_host
366260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_pid = 0
367260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_port = 0  # Determined later from devserver portfile.
3686c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_dir = devserver_dir
369ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._update_payload_staged_url = update_payload_staged_url
3706c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
3716c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh = hosts.SSHHost(self._omaha_host,
3726c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                            user=os.environ['USER'])
373260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
3743563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        # Temporary files for various devserver outputs.
3753563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_logfile = None
376238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        self._devserver_stdoutfile = None
3773563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_portfile = None
3783563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_pidfile = None
3793563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_static_dir = None
3803563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
3813563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
3823563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa    def _cleanup_devserver_files(self):
3833563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Cleans up the temporary devserver files."""
384238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        for filename in (self._devserver_logfile, self._devserver_stdoutfile,
385238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                         self._devserver_portfile, self._devserver_pidfile):
386238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo            if filename:
387238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                self._devserver_ssh.run('rm -f %s' % filename,
388238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                                        ignore_status=True)
3893563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
3903563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        if self._devserver_static_dir:
3913563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa            self._devserver_ssh.run('rm -rf %s' % self._devserver_static_dir,
3923563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                                    ignore_status=True)
3933563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
394260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
3953563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa    def _create_tempfile_on_devserver(self, label, dir=False):
3963563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Creates a temporary file/dir on the devserver and returns its path.
397260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
398260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @param label: Identifier for the file context (string, no whitespaces).
3993563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        @param dir: If True, create a directory instead of a file.
400260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
401260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @raises test.TestError: If we failed to invoke mktemp on the server.
402260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @raises OmahaDevserverFailedToStart: If tempfile creation failed.
403260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """
404260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        remote_cmd = 'mktemp --tmpdir devserver-%s.XXXXXX' % label
4053563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        if dir:
4063563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa            remote_cmd += ' --directory'
4073563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
408260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
409260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            result = self._devserver_ssh.run(remote_cmd, ignore_status=True)
410260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except error.AutoservRunError as e:
411260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._log_and_raise_remote_ssh_error(e)
412260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if result.exit_status != 0:
413260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            raise OmahaDevserverFailedToStart(
414260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'Could not create a temporary %s file on the devserver, '
415a7412a90ffefac2b6314726b284af1b31d6bd797Alex Deymo                    'error output: "%s"' % (label, result.stderr))
416260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return result.stdout.strip()
417260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
418260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    @staticmethod
419260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _log_and_raise_remote_ssh_error(e):
420260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Logs failure to ssh remote, then raises a TestError."""
421260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.debug('Failed to ssh into the devserver: %s', e)
422260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.error('If you are running this locally it means you did not '
423260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                      'configure ssh correctly.')
424260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        raise error.TestError('Failed to ssh into the devserver: %s' % e)
425260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
426260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
427260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _read_int_from_devserver_file(self, filename):
428260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Reads and returns an integer value from a file on the devserver."""
429260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return int(self._get_devserver_file_content(filename).strip())
4300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4316c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
432793344b359304c31d78197788b55cfbbe2636025Chris Sosa    def _wait_for_devserver_to_start(self):
433793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """Waits until the devserver starts within the time limit.
434793344b359304c31d78197788b55cfbbe2636025Chris Sosa
435260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        Infers and sets the devserver PID and serving port.
436260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
437793344b359304c31d78197788b55cfbbe2636025Chris Sosa        Raises:
438793344b359304c31d78197788b55cfbbe2636025Chris Sosa            OmahaDevserverFailedToStart: If the time limit is reached and we
439793344b359304c31d78197788b55cfbbe2636025Chris Sosa                                         cannot connect to the devserver.
440793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """
441260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # Compute the overall timeout.
442260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        deadline = time.time() + self._WAIT_FOR_DEVSERVER_STARTED_SECONDS
443260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
444260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # First, wait for port file to be filled and determine the server port.
44594b9ad4983cc735e162a3549c1628d08241b0d57Gilad Arnold        logging.warning('Waiting for devserver to start up.')
446260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        while time.time() < deadline:
447260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            try:
448260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                self._devserver_pid = self._read_int_from_devserver_file(
449260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        self._devserver_pidfile)
450260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                self._devserver_port = self._read_int_from_devserver_file(
451260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        self._devserver_portfile)
452260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                logging.info('Devserver pid is %d, serving on port %d',
453260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                             self._devserver_pid, self._devserver_port)
454260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
455260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            except Exception:  # Couldn't read file or corrupt content.
456260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                time.sleep(self._WAIT_SLEEP_INTERVAL)
457260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        else:
45852c35724507ec105053d2af792fe161a627e05c1Alex Deymo            try:
45952c35724507ec105053d2af792fe161a627e05c1Alex Deymo                self._devserver_ssh.run_output('uptime')
46052c35724507ec105053d2af792fe161a627e05c1Alex Deymo            except error.AutoservRunError as e:
46152c35724507ec105053d2af792fe161a627e05c1Alex Deymo                logging.debug('Failed to run uptime on the devserver: %s', e)
46294b9ad4983cc735e162a3549c1628d08241b0d57Gilad Arnold            raise OmahaDevserverFailedToStart(
463260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'The test failed to find the pid/port of the omaha '
46452c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    'devserver after %d seconds. Check the dumped devserver '
46552c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    'logs and devserver load for more information.' %
46652c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    self._WAIT_FOR_DEVSERVER_STARTED_SECONDS)
467260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
468260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # Check that the server is reponsding to network requests.
469260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.warning('Waiting for devserver to accept network requests.')
470260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        url = 'http://%s' % self.get_netloc()
471260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        while time.time() < deadline:
472260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            if dev_server.DevServer.devserver_healthy(url, timeout_min=0.1):
473260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
474793344b359304c31d78197788b55cfbbe2636025Chris Sosa
475793344b359304c31d78197788b55cfbbe2636025Chris Sosa            # TODO(milleral): Refactor once crbug.com/221626 is resolved.
476793344b359304c31d78197788b55cfbbe2636025Chris Sosa            time.sleep(self._WAIT_SLEEP_INTERVAL)
477793344b359304c31d78197788b55cfbbe2636025Chris Sosa        else:
478793344b359304c31d78197788b55cfbbe2636025Chris Sosa            raise OmahaDevserverFailedToStart(
479793344b359304c31d78197788b55cfbbe2636025Chris Sosa                    'The test failed to establish a connection to the omaha '
480793344b359304c31d78197788b55cfbbe2636025Chris Sosa                    'devserver it set up on port %d. Check the dumped '
481260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'devserver logs for more information.' %
482260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    self._devserver_port)
483793344b359304c31d78197788b55cfbbe2636025Chris Sosa
484793344b359304c31d78197788b55cfbbe2636025Chris Sosa
4856c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def start_devserver(self):
486793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """Starts the devserver and confirms it is up.
487793344b359304c31d78197788b55cfbbe2636025Chris Sosa
488793344b359304c31d78197788b55cfbbe2636025Chris Sosa        Raises:
489260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            test.TestError: If we failed to spawn the remote devserver.
490793344b359304c31d78197788b55cfbbe2636025Chris Sosa            OmahaDevserverFailedToStart: If the time limit is reached and we
491793344b359304c31d78197788b55cfbbe2636025Chris Sosa                                         cannot connect to the devserver.
4926c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """
4932f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        update_payload_url_base, update_payload_path = self._split_url(
494ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                self._update_payload_staged_url)
4953563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4963563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        # Allocate temporary files for various server outputs.
4973563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_logfile = self._create_tempfile_on_devserver('log')
498238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        self._devserver_stdoutfile = self._create_tempfile_on_devserver(
499238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                'stdout')
5003563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_portfile = self._create_tempfile_on_devserver('port')
5013563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_pidfile = self._create_tempfile_on_devserver('pid')
5023563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_static_dir = self._create_tempfile_on_devserver(
5033563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                'static', dir=True)
5043563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
5056f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # Invoke the Omaha/devserver on the remote server. Will attempt to kill
5066f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # it with a SIGTERM after a predetermined timeout has elapsed, followed
5076f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # by SIGKILL if not dead within 30 seconds from the former signal.
5080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        cmdlist = [
5096f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold                'timeout', '-s', 'TERM', '-k', '30',
5106f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold                str(self._DEVSERVER_TIMELIMIT_SECONDS),
5116c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                '%s/devserver.py' % self._devserver_dir,
5120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--payload=%s' % update_payload_path,
513260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--port=0',
514260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--pidfile=%s' % self._devserver_pidfile,
515260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--portfile=%s' % self._devserver_portfile,
516260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--logfile=%s' % self._devserver_logfile,
5170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--remote_payload',
5180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--urlbase=%s' % update_payload_url_base,
5190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--max_updates=1',
5200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--host_log',
5213563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                '--static_dir=%s' % self._devserver_static_dir,
5220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        ]
523238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        remote_cmd = '( %s ) </dev/null >%s 2>&1 &' % (
524238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                ' '.join(cmdlist), self._devserver_stdoutfile)
5256c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
526260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.info('Starting devserver with %r', remote_cmd)
527260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
528260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._devserver_ssh.run_output(remote_cmd)
529260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except error.AutoservRunError as e:
530260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._log_and_raise_remote_ssh_error(e)
5316c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
532260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
533260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._wait_for_devserver_to_start()
534260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except OmahaDevserverFailedToStart:
535260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._kill_remote_process()
536260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._dump_devserver_log()
537299e7788f7054dccab196dd7274f75ad41b66606Alex Deymo            self._cleanup_devserver_files()
538260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            raise
53938ba6b79b19b5bdd9cfe71b26efd0c267768527aGilad Arnold
5406c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
541260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _kill_remote_process(self):
542260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Kills the devserver and verifies it's down; clears the remote pid."""
543260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        def devserver_down():
544cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """Ensure that the devserver process is down."""
545260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            return not self._remote_process_alive()
5466c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
547260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if devserver_down():
5486c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            return
5496c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
550260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        for signal in 'SIGTERM', 'SIGKILL':
551260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            remote_cmd = 'kill -s %s %s' % (signal, self._devserver_pid)
552260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._devserver_ssh.run(remote_cmd)
553260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            try:
554260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                client_utils.poll_for_condition(
555260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        devserver_down, sleep_interval=1, desc='devserver down')
556260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
557260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            except client_utils.TimeoutError:
558260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                logging.warning('Could not kill devserver with %s.', signal)
559260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        else:
560260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            logging.warning('Failed to kill devserver, giving up.')
561260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
562260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_pid = None
5636c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
5646c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
565260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _remote_process_alive(self):
566260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Tests whether the remote devserver process is running."""
567260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if not self._devserver_pid:
568260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            return False
569260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        remote_cmd = 'test -e /proc/%s' % self._devserver_pid
570260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        result = self._devserver_ssh.run(remote_cmd, ignore_status=True)
571260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return result.exit_status == 0
5726c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
5736c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
5746c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def get_netloc(self):
5756c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Returns the netloc (host:port) of the devserver."""
576260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if not (self._devserver_pid and self._devserver_port):
5770338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('No running omaha/devserver')
5786c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
579260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return '%s:%s' % (self._omaha_host, self._devserver_port)
5800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
5816c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
5822f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    def get_update_url(self):
5832f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Returns the update_url you can use to update via this server."""
5842f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        return urlparse.urlunsplit(('http', self.get_netloc(), '/update',
5852f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                    '', ''))
5862f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
5872f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
588260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _get_devserver_file_content(self, filename):
589260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Returns the content of a file on the devserver."""
590260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return self._devserver_ssh.run_output('cat %s' % filename)
591260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
592260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
593260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _get_devserver_log(self):
594260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Obtain the devserver output."""
595260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return self._get_devserver_file_content(self._devserver_logfile)
596260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
597260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
598238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo    def _get_devserver_stdout(self):
599238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        """Obtain the devserver output in stdout and stderr."""
600238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        return self._get_devserver_file_content(self._devserver_stdoutfile)
601238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo
602238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo
603260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _dump_devserver_log(self, logging_level=logging.ERROR):
6043563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Dump the devserver log to the autotest log.
60519426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa
60619426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa        @param logging_level: logging level (from logging) to log the output.
60719426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa        """
608238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        logging.log(logging_level, "devserver stdout and stderr:\n" +
6093c18e75cd74416aae9d8e0fbe2719a0b36ace70fAlex Deymo                    self._get_devserver_stdout())
610238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        logging.log(logging_level, "devserver logfile:\n" +
611238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                    self._get_devserver_log())
6120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    @staticmethod
6150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _split_url(url):
6162f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Splits a URL into the URL base and path."""
6170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        split_url = urlparse.urlsplit(url)
6180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        url_base = urlparse.urlunsplit(
6192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                (split_url.scheme, split_url.netloc, '', '', ''))
6202f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        url_path = split_url.path
6212f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        return url_base, url_path.lstrip('/')
6220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
624260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def stop_devserver(self):
625260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Kill remote process and wait for it to die, dump its output."""
6266c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if not self._devserver_pid:
627ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            logging.error('No running omaha/devserver.')
628ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            return
6296c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
6300338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Killing omaha/devserver')
631260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._kill_remote_process()
63219426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa        logging.debug('Final devserver log before killing')
633260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._dump_devserver_log(logging.DEBUG)
6343563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._cleanup_devserver_files()
6350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass autoupdate_EndToEndTest(test.test):
6380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Complete update test between two Chrome OS releases.
6390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    Performs an end-to-end test of updating a ChromeOS device from one version
6410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    to another. This script requires a running (possibly remote) servod
6420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    instance connected to an actual servo board, which controls the DUT. It
6430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    also assumes that a corresponding target (update) image was staged for
644ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    download on a central staging devserver.
6450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    The test performs the following steps:
6470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      0. Stages the source image and target update payload on the central
6490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         Lorry/devserver.
6500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      1. Spawns a private Omaha/devserver instance, configured to return the
6510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         target (update) image URL in response for an update check.
6520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      2. Connects to servod.
6530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         a. Resets the DUT to a known initial state.
6540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         b. Installs a source image on the DUT via recovery.
6550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      3. Reboots the DUT with the new image.
6560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      4. Triggers an update check at the DUT.
6570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      5. Watches as the DUT obtains an update and applies it.
6580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold      6. Repeats 3-5, ensuring that the next update check shows the new image
6590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold         version.
6600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
661ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    Some notes on naming:
662ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa      devserver: Refers to a machine running the Chrome OS Update Devserver.
663ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa      autotest_devserver: An autotest wrapper to interact with a devserver.
664ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          Can be used to stage artifacts to a devserver. While
665ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          this can also be used to update a machine, we do not
666ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          use it for that purpose in this test as we manage
667ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          updates with out own devserver instances (see below).
668ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa      omaha_devserver: This test's wrapper of a devserver running for the
669ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       purposes of emulating omaha. This test controls the
670ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       lifetime of this devserver instance and is separate
671ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       from the autotest lab's devserver's instances which are
672ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       only used for staging and hosting artifacts (because they
673ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       scale). These are run on the same machines as the actual
674ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       autotest devservers which are used for staging but on
675ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                       different ports.
676ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa      *staged_url's: In this case staged refers to the fact that these items
677ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     are available to be downloaded statically from these urls
678ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     e.g. 'localhost:8080/static/my_file.gz'. These are usually
679ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     given after staging an artifact using a autotest_devserver
680ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     though they can be re-created given enough assumptions.
681ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa      *update_url's: Urls refering to the update RPC on a given omaha devserver.
682ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     Since we always use an instantiated omaha devserver to run
683ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     updates, these will always reference an existing instance
684ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     of an omaha devserver that we just created for the purposes
685ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     of updating.
686ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa      devserver_hostname: At the start of each test, we choose a devserver
687ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          machine in the lab for the test. We use the devserver
688ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          instance there (access by autotest_devserver) to stage
689ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          artifacts. However, we also use the same host to start
690ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          omaha devserver instances for updating machines with
691ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          (that reference the staged paylaods on the autotest
692ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          devserver instance). This hostname refers to that
693ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          machine we are using (since it's always the same for
694ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                          both staging/omaha'ing).
695ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
6960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """
6970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    version = 1
6980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    # Timeout periods, given in seconds.
700f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_AFTER_SHUTDOWN_SECONDS = 10
701f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_AFTER_UPDATE_SECONDS = 20
702f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_USB_INSTALL_SECONDS = 4 * 60
703f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_MP_RECOVERY_SECONDS = 8 * 60
7040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    _WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS = 12 * 60
7054f4426a2184828ad195fbb0b66b34f809a619236Chris Sosa    # TODO(sosa): Investigate why this needs to be so long (this used to be
7064f4426a2184828ad195fbb0b66b34f809a619236Chris Sosa    # 120 and regressed).
7074f4426a2184828ad195fbb0b66b34f809a619236Chris Sosa    _WAIT_FOR_DOWNLOAD_STARTED_SECONDS = 4 * 60
7086c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    _WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS = 10 * 60
709f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    _WAIT_FOR_UPDATE_COMPLETED_SECONDS = 4 * 60
7100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    _WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS = 15 * 60
71103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    _DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS = 30
7120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
71315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold    _STATEFUL_UPDATE_FILENAME = 'stateful.tgz'
714c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen    _LOGINABLE_MINIMUM_RELEASE = 5110
71515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
7162f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # Named tuple containing urls for staged urls needed for test.
7172f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # source_url: url to find the update payload for the source image.
7182f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # source_stateful_url: url to find the stateful payload for the source
7192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    #                      image.
7202f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # target_url: url to find the update payload for the target image.
7212f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # target_stateful_url: url to find the stateful payload for the target
7222f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    #                      image.
7232f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    _STAGED_URLS = collections.namedtuple(
7242f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            'StagedUrls',
7252f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            ['source_url', 'source_stateful_url', 'target_url',
7262f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa             'target_stateful_url'])
7272f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
7280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
729f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _servo_dut_power_up(self):
7300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Powers up the DUT, optionally simulating a Ctrl-D key press."""
731f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.power_short_press()
732f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._dev_mode:
733f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.servo.pass_devmode()
7340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
736f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _servo_dut_reboot(self, disconnect_usbkey=False):
7370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Reboots a DUT.
7380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
739e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury        @param disconnect_usbkey: detach USB flash device from the DUT before
740e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury               powering it back up; this is useful when (for example) a USB
741e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury               booted device need not see the attached USB key after the
742e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury               reboot.
7430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestFail if DUT fails to reboot.
7450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
7470338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Rebooting dut')
748f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.power_long_press()
7490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        _wait(self._WAIT_AFTER_SHUTDOWN_SECONDS, 'after shutdown')
750e7bd9369b76e2bbdc230c3c1aeb9afc6e6ec60baVadim Bendebury        if disconnect_usbkey:
751f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.servo.switch_usbkey('host')
752f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
753f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._servo_dut_power_up()
754f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
755f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            if not self._host.wait_up(timeout=self._host.BOOT_TIMEOUT):
7560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                raise error.TestFail(
7570338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                        'DUT %s failed to boot after %d secs' %
758f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                        (self._host.ip, self._host.BOOT_TIMEOUT))
7590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        else:
760cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            # TODO(garnold) chromium-os:33766: implement waiting for MP-signed
761cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            # images; ideas include waiting for a ping reply, or using a GPIO
762cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            # signal.
763cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            pass
7640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
766ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def _install_mp_image(self, staged_image_url):
7670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Installs an MP-signed recovery image on a DUT.
7680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
769ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param staged_image_url: URL of the image on a Lorry/devserver
7700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
7710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Flash DUT with source image version, using recovery.
7720338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Installing source mp-signed image via recovery: %s',
773ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     staged_image_url)
774f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host.servo.install_recovery_image(
775ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                staged_image_url,
7760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                wait_timeout=self._WAIT_FOR_MP_RECOVERY_SECONDS)
7770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Reboot the DUT after installation.
779f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._servo_dut_reboot(disconnect_usbkey=True)
7800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
782ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def _install_test_image_with_servo(self, staged_image_url):
7830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Installs a test image on a DUT, booted via recovery.
7840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
785ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param staged_image_url: URL of the image on the devserver
7860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param is_dev_nmode: whether or not the DUT is in dev mode
7870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7880ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestFail if DUT cannot boot the test image from USB;
7890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               AutotestHostRunError if failed to run the install command on the
7900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold               DUT.
7910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
7930338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Installing source test image via recovery: %s',
794ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     staged_image_url)
795ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._host.servo.install_recovery_image(staged_image_url)
7960338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Waiting for image to boot')
797f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if not self._host.wait_up(timeout=self._host.USB_BOOT_TIMEOUT):
798cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            raise error.TestFail(
799cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                    'DUT %s boot from usb timed out after %d secs' %
800cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                    (self._host, self._host.USB_BOOT_TIMEOUT))
8010338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Installing new image onto ssd')
8020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        try:
803f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            cmd_result = self._host.run(
8040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    'chromeos-install --yes',
8050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    timeout=self._WAIT_FOR_USB_INSTALL_SECONDS,
8060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    stdout_tee=None, stderr_tee=None)
807f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        except error.AutotestHostRunError:
8080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # Dump stdout (with stderr) to the error log.
8090338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.error('Command failed, stderr:\n' + cmd_result.stderr)
8100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            raise
8110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # Reboot the DUT after installation.
813f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._servo_dut_reboot(disconnect_usbkey=True)
8140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8162f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    def _trigger_test_update(self, omaha_devserver):
8170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Trigger an update check on a test image.
8180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param omaha_devserver: Instance of OmahaDevserver that will serve the
8202f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                update.
8210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise RootFSUpdateError if anything went wrong.
8220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
8242f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        updater = autoupdater.ChromiumOSUpdater(
8252f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                omaha_devserver.get_update_url(), host=self._host)
8260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        updater.trigger_update()
8270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
829f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def _get_rootdev(self):
83009706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        """Returns the partition device containing the rootfs on a host.
83109706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
83209706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        @return The rootfs partition device (string).
83309706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
83409706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        @raise AutotestHostRunError if command failed to run on host.
83509706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
83609706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        """
83766d74078caa789f80d4a43c82b61d6016a0cf430Chris Sosa        return self._host.run('rootdev -s').stdout.strip()
83809706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
83909706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
840ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def _stage_image(self, autotest_devserver, image_uri):
841ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Stage a Chrome OS image onto a staging devserver.
8420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
843ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param autotest_devserver: instance of client.common_lib.dev_server to
844ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   use to stage the image.
845ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param image_uri: The uri of the image.
846ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @return URL of the staged image on the staging devserver.
8470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestError if there's a problem with staging.
8490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
851f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
8520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # For this call, we just need the URL path up to the image.zip file
8530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # (exclusive).
8540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            image_uri_path = urlparse.urlsplit(image_uri).path.partition(
8550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                    'image.zip')[0].strip('/')
8560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            try:
857ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                autotest_devserver.stage_artifacts(image_uri_path,
858ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                                   ['test_image'])
859ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                return autotest_devserver.get_test_image_url(image_uri_path)
8600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            except dev_server.DevServerException, e:
8610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                raise error.TestError(
8620338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                        'Failed to stage source test image: %s' % e)
8630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        else:
8640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # TODO(garnold) chromium-os:33766: implement staging of MP-signed
8650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # images.
8662f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            raise NotImplementedError()
8670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
869cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
870cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    def _stage_payload(autotest_devserver, devserver_label, filename,
87172f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett                       archive_url=None):
8722f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Stage the given payload onto the devserver.
8730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8742f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        Works for either a stateful or full/delta test payload. Expects the
8752f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        gs_path or a combo of devserver_label + filename.
8760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
877ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param autotest_devserver: instance of client.common_lib.dev_server to
878ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   use to reach the devserver instance for this
879ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   build.
8802f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param devserver_label: The build name e.g. x86-mario-release/<version>.
8812f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                If set, assumes default gs archive bucket and
8822f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                requires filename to be specified.
8832f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param filename: In conjunction with devserver_label, if just specifying
8842f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                         the devserver label name, this is which file are you
8852f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                         downloading.
88615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @param archive_url: An optional GS archive location, if not using the
88715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                            devserver's default.
888ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa
8890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return URL of the staged payload on the server.
8900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestError if there's a problem with staging.
8920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
8942f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        try:
895ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            autotest_devserver.stage_artifacts(
89672f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett                    image=devserver_label, files=[filename],
89772f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett                    archive_url=archive_url)
898ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            return autotest_devserver.get_staged_file_url(filename,
899ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                                          devserver_label)
9002f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        except dev_server.DevServerException, e:
9010338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('Failed to stage payload: %s' % e)
9020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
9030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
90472f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett    def _stage_payload_by_uri(self, autotest_devserver, payload_uri):
90515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """Stage a payload based on its GS URI.
90615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
90715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        This infers the build's label, filename and GS archive from the
90815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        provided GS URI.
90915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
91015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @param autotest_devserver: instance of client.common_lib.dev_server to
91115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                                   use to reach the devserver instance for this
91215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                                   build.
91315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @param payload_uri: The full GS URI of the payload.
91415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
91515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @return URL of the staged payload on the server.
91615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
91715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @raise error.TestError if there's a problem with staging.
91815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
91915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """
92015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        archive_url, _, filename = payload_uri.rpartition('/')
92115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        devserver_label = urlparse.urlsplit(archive_url).path.strip('/')
92215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return self._stage_payload(autotest_devserver, devserver_label,
92372f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett                                   filename, archive_url=archive_url)
92415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
92515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
926cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
927cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    def _payload_to_update_url(payload_url):
9282f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Given a update or stateful payload url, returns the update url."""
9297231260da421abdf5ceceff6ab60155936dca21fChris Sosa        # We want to transform it to the correct omaha url which is
9307231260da421abdf5ceceff6ab60155936dca21fChris Sosa        # <hostname>/update/...LABEL.
9312f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        base_url = payload_url.rpartition('/')[0]
9322f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        return base_url.replace('/static/', '/update/')
9332f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9342f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
93515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold    def _get_stateful_uri(self, build_uri):
93615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """Returns a complete GS URI of a stateful update given a build path."""
93715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return '/'.join([build_uri.rstrip('/'), self._STATEFUL_UPDATE_FILENAME])
93815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
93915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
94015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold    def _payload_to_stateful_uri(self, payload_uri):
94115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """Given a payload GS URI, returns the corresponding stateful URI."""
94215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        build_uri = payload_uri.rpartition('/')[0]
94315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return self._get_stateful_uri(build_uri)
9442f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9452f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9462f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    def update_via_test_payloads(self, omaha_host, payload_url, stateful_url,
9472f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                 clobber):
948cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """Given the following update and stateful urls, update the DUT.
949cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
950cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        Only updates the rootfs/stateful if the respective url is provided.
951cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
952cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param omaha_host: If updating rootfs, redirect updates through this
953cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            host. Should be None iff payload_url is None.
954cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param payload_url: If set, the specified url to find the update
955cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            payload.
956cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param stateful_url: If set, the specified url to find the stateful
957cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            payload.
958cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param clobber: If True, do a clean install of stateful.
959cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """
960cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        def perform_update(url, is_stateful):
961cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """Perform a rootfs/stateful update using given URL.
962cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
963cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            @param url: URL to update from.
964cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            @param is_stateful: Whether this is a stateful or rootfs update.
965cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """
966cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if url:
967cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                updater = autoupdater.ChromiumOSUpdater(url, host=self._host)
968cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                if is_stateful:
969cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                    updater.update_stateful(clobber=clobber)
970cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                else:
971cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                    updater.update_rootfs()
972cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
973cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        # We create a OmahaDevserver to redirect blah.bin to update/. This
974cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        # allows us to use any payload filename to serve an update.
975cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        temp_devserver = None
976cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        try:
977cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if payload_url:
978cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                temp_devserver = OmahaDevserver(
979260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        omaha_host, self._devserver_dir, payload_url)
980cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                temp_devserver.start_devserver()
981cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                payload_url = temp_devserver.get_update_url()
982cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
983cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            stateful_url = self._payload_to_update_url(stateful_url)
984cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
985cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            perform_update(payload_url, False)
986cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            perform_update(stateful_url, True)
987cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        finally:
988cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if temp_devserver:
989260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                temp_devserver.stop_devserver()
9902f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9912f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
992ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def install_source_version(self, devserver_hostname, image_url,
993ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                               stateful_url):
9942f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Prepare the specified host with the image given by the urls.
9952f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
996ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param devserver_hostname: If updating rootfs, redirect updates
997ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   through this host. Should be None iff
998ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   image_url is None.
9992f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param image_url: If set, the specified url to find the source image
10002f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                          or full payload for the source image.
10012f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param stateful_url: If set, the specified url to find the stateful
10022f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                             payload.
10032f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """
1004f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_servo:
1005f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # Install source image (test vs MP).
1006f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            if self._use_test_image:
1007f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                self._install_test_image_with_servo(image_url)
1008f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            else:
1009f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                self._install_mp_image(image_url)
1010f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1011f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
101245f02ae47134953169805d281992c9edf0019250Chris Sosa            try:
10132f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                # Reboot to get us into a clean state.
10142f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                self._host.reboot()
10152f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                # Since we are installing the source image of the test, clobber
10162f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                # stateful.
1017ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                self.update_via_test_payloads(devserver_hostname, image_url,
10182f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                              stateful_url, clobber=True)
10192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                self._host.reboot()
102045f02ae47134953169805d281992c9edf0019250Chris Sosa            except error.AutoservRunError:
102145f02ae47134953169805d281992c9edf0019250Chris Sosa                logging.fatal('Error re-imaging the machine with the source '
102245f02ae47134953169805d281992c9edf0019250Chris Sosa                              'image %s', image_url)
10238577f8fb064fc8392bdc399fdd7465307d6da03bAlex Miller                raise error.TestError('Could not update to pre-conditions of '
10248577f8fb064fc8392bdc399fdd7465307d6da03bAlex Miller                                      'the test: we failed to start the '
10258577f8fb064fc8392bdc399fdd7465307d6da03bAlex Miller                                      'private devserver, connect to the host '
10268577f8fb064fc8392bdc399fdd7465307d6da03bAlex Miller                                      'and/or make it reboot.')
1027f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1028f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1029ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def stage_artifacts_onto_devserver(self, autotest_devserver, test_conf):
10302f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Stages artifacts that will be used by the test onto the devserver.
10312f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
1032ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param autotest_devserver: instance of client.common_lib.dev_server to
1033ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   use to reach the devserver instance for this
1034ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   build.
10352f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param test_conf: a dictionary containing test configuration values
10362f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
10372f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @return a _STAGED_URLS tuple containing the staged urls.
1038f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """
10390338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Staging images onto autotest devserver (%s)',
1040ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     autotest_devserver.url())
1041f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
104215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_source_url = None
104315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_source_stateful_url = None
1044fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold        source_image_uri = test_conf['source_image_uri']
1045fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold        if source_image_uri:
1046fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold            if self._use_servo:
1047fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                staged_source_url = self._stage_image(
1048fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                        autotest_devserver, source_image_uri)
1049fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # Test image already contains a stateful update, leave
1050fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # staged_source_stateful_url untouhced.
10512f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            else:
1052fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                staged_source_url = self._stage_payload_by_uri(
1053fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                        autotest_devserver, source_image_uri)
1054fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold
1055fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # In order to properly install the source image using a full
1056fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # payload we'll also need the stateful update that comes with it.
1057fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # In general, tests may have their source artifacts in a different
1058fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # location than their payloads. This is determined by whether or
1059fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # not the source_archive_uri attribute is set; if it isn't set,
1060fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # then we derive it from the dirname of the source payload.
1061fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                source_archive_uri = test_conf.get('source_archive_uri')
1062fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                if source_archive_uri:
1063fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                    source_stateful_uri = self._get_stateful_uri(source_archive_uri)
1064fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                else:
1065fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                    source_stateful_uri = self._payload_to_stateful_uri(
1066fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                            source_image_uri)
1067fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold
1068fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                staged_source_stateful_url = self._stage_payload_by_uri(
1069fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                        autotest_devserver, source_stateful_uri)
107015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
1071fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                # Log source image URLs.
1072fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                logging.info('Source %s from %s staged at %s',
1073fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                             'image' if self._use_servo else 'full payload',
1074fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                             source_image_uri, staged_source_url)
1075fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                if staged_source_stateful_url:
1076fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                    logging.info('Source stateful update from %s staged at %s',
1077fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                                 source_stateful_uri, staged_source_stateful_url)
107815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
107915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_payload_uri = test_conf['target_payload_uri']
108015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_target_url = self._stage_payload_by_uri(
108115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                autotest_devserver, target_payload_uri)
108215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_stateful_uri = None
108315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_archive_uri = test_conf.get('target_archive_uri')
108415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        if not target_archive_uri and self._job_repo_url:
10852f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            _, devserver_label = tools.get_devserver_build_from_package_url(
10862f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                    self._job_repo_url)
108715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold            staged_target_stateful_url = self._stage_payload(
108815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                    autotest_devserver, devserver_label,
108915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                    self._STATEFUL_UPDATE_FILENAME)
10902f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        else:
109115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold            if target_archive_uri:
109215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                target_stateful_uri = self._get_stateful_uri(target_archive_uri)
109315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold            else:
109415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                target_stateful_uri = self._payload_to_stateful_uri(
109515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                    target_payload_uri)
1096f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
109715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold            staged_target_stateful_url = self._stage_payload_by_uri(
109815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                    autotest_devserver, target_stateful_uri)
10992f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
1100fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold        # Log target payload URLs.
110115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        logging.info('%s test payload from %s staged at %s',
110215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     test_conf['update_type'], target_payload_uri,
110315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     staged_target_url)
11040338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Target stateful update from %s staged at %s',
110515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     target_stateful_uri or 'standard location',
110615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     staged_target_stateful_url)
110715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
110815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return self._STAGED_URLS(staged_source_url, staged_source_stateful_url,
110915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                                 staged_target_url, staged_target_stateful_url)
1110f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1111f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1112f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def initialize(self):
1113f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Sets up variables that will be used by test."""
1114f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._host = None
1115f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._use_servo = False
1116f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._dev_mode = False
1117f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._omaha_devserver = None
1118f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1119f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._use_test_image = True
11202f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        self._job_repo_url = None
1121f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._devserver_dir = global_config.global_config.get_config_value(
1122f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                'CROS', 'devserver_dir', default=None)
1123f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._devserver_dir is None:
1124f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.TestError(
11250338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                    'Path to devserver source tree not provided; please define '
1126f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                    'devserver_dir under [CROS] in your shadow_config.ini')
1127f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1128f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1129f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa    def cleanup(self):
1130f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Kill the omaha devserver if it's still around."""
1131f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._omaha_devserver:
1132260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._omaha_devserver.stop_devserver()
1133f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1134f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        self._omaha_devserver = None
1135f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1136f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
113745f02ae47134953169805d281992c9edf0019250Chris Sosa    def _verify_preconditions(self):
1138f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """Validate input args make sense."""
1139f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_servo and not self._host.servo:
1140f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            raise error.AutotestError('Servo use specified but no servo '
1141f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                                      'attached to host object.')
1142f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1143f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if not self._use_test_image and not self._use_servo:
11440338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('Cannot install mp image without servo.')
1145f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1146f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1147c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen    def _run_login_test(self, release_string):
1148c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        """Runs login_LoginSuccess test if it is supported by the release."""
1149c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # Only do login tests with recent builds, since they depend on
1150c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # some binary compatibility with the build itself.
1151c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # '5116.0.0' -> ('5116', '0', '0') -> 5116
1152c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        if int(release_string.split('.')[0]) > self._LOGINABLE_MINIMUM_RELEASE:
1153c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            # Login, to prove we can before/after the update.
1154c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            logging.info('Attempting to login (release %s).', release_string)
1155c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1156c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            client_at = autotest.Autotest(self._host)
1157c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            client_at.run_test('login_LoginSuccess')
1158c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        else:
1159c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            logging.info('Not attempting login test because %s is older than '
1160c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen                         '%d.', release_string, self._LOGINABLE_MINIMUM_RELEASE)
1161c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1162c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1163ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def _dump_update_engine_log(self):
1164ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Dumps relevant AU error log."""
116545f02ae47134953169805d281992c9edf0019250Chris Sosa        if not self._use_servo:
116645f02ae47134953169805d281992c9edf0019250Chris Sosa            logging.error('Test failed -- dumping snippet of update_engine log')
1167ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            try:
1168ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                error_log = self._host.run_output(
1169ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                        'tail -n 40 /var/log/update_engine.log')
1170ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                logging.error(error_log)
1171ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            except Exception:
1172ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                # Mute any exceptions we get printing debug logs.
1173ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                pass
117445f02ae47134953169805d281992c9edf0019250Chris Sosa
11750ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
117631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _start_perf_mon(self):
117731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Starts monitoring performance and resource usage on a DUT.
117831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
117931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        Call _stop_perf_mon() with the returned PID to stop monitoring
118031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        and collect the results.
118131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
118231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        @return  the PID of the newly created DUT monitoring process.
118331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
118431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # We can't assume much about the source image so we copy the
118531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # performance monitoring script to the DUT directly.
118631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        path = os.path.join(self.bindir, 'update_engine_performance_monitor.py')
118731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        self._host.send_file(path, '/tmp')
118831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        cmd = 'python /tmp/update_engine_performance_monitor.py --start-bg'
118931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        return int(self._host.run(cmd).stdout)
119031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
119131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
119231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _stop_perf_mon(self, perf_mon_pid):
119331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Stops monitoring performance and resource usage on a DUT.
119431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
119531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        The PID returned from _start_perf_mon() should be passed
119631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        in. See update_engine_performance_monitor.py for known
119731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        attributes (e.g. |rss_peak|).
119831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
119931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        @param perf_mon_pid: the PID returned from _start_perf_mon().
120031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
120131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        @return          a Python object created by deserializing JSON or None
120231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         if an error occured.
120331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
120431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # Gracefully handle problems with performance monitoring by
120531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # just returning None.
120631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        try:
120731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            cmd = ('python /tmp/update_engine_performance_monitor.py '
120831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                   '--stop-bg=%d') % perf_mon_pid
120931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            perf_json_txt = self._host.run(cmd).stdout
121031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            return json.loads(perf_json_txt)
121131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        except Exception as e:
121231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.warning('Failed to parse output from '
121331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                            'update_engine_performance_monitor.py: %s', e)
121431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        return None
121531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
121631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
121731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _report_perf_data(self, perf_data):
121831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Reports performance and resource data.
121931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
122031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        @param perf_data: the value obtained from _stop_perf_mon().
122131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
122231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        rss_peak = perf_data.get('rss_peak')
122331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        if rss_peak:
122431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            rss_peak_kib = rss_peak / 1024
122531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.info('Peak memory (RSS) usage on DUT: %d KiB', rss_peak_kib)
122631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            self.output_perf_value(description='mem_usage_peak',
122731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   value=int(rss_peak_kib),
122831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   units='KiB',
122931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   higher_is_better=False)
123031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        else:
123131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.warning('No rss_peak key in JSON returned by '
123231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                            'update_engine_performance_monitor.py')
123331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
123431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1235ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def run_update_test(self, staged_urls, test_conf):
1236ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Runs the actual update test once preconditions are met.
12370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1238ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param staged_urls: A _STAGED_URLS tuple containing the staged urls.
1239ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param test_conf: A dictionary containing test configuration values
12400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1241ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify an update
1242ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                event.
12430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1244c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1245f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # On test images, record the active root partition.
1246f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        source_rootfs_partition = None
1247f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
1248f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            source_rootfs_partition = self._get_rootdev()
12490338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Source image rootfs partition: %s',
1250f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                         source_rootfs_partition)
12510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
125231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # Start the performance monitoring process on the DUT.
125331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        perf_mon_pid = self._start_perf_mon()
125431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        try:
125531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Trigger an update.
125631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            if self._use_test_image:
125731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                self._trigger_test_update(self._omaha_devserver)
125831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            else:
125931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                # TODO(garnold) chromium-os:33766: use GPIOs to trigger an
126031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                # update.
126131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                pass
12620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
126331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Track update progress.
126431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            omaha_netloc = self._omaha_devserver.get_netloc()
126531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            omaha_hostlog_url = urlparse.urlunsplit(
126631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    ['http', omaha_netloc, '/api/hostlog',
126731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     'ip=' + self._host.ip, ''])
126831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.info('Polling update progress from omaha/devserver: %s',
126931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         omaha_hostlog_url)
127031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            log_verifier = UpdateEventLogVerifier(
127131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    omaha_hostlog_url,
127231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    self._DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS)
127331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
127431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Verify chain of events in a successful update process.
127531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            chain = ExpectedUpdateEventChain(
127631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    (self._WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS,
127731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     ExpectedUpdateEvent(
127831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         version=test_conf['source_release'],
127931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         error_message=('Failed to receive initial update '
128031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                        'check. Check Omaha devserver log in '
128131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                        'this output.'))),
128231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    (self._WAIT_FOR_DOWNLOAD_STARTED_SECONDS,
128331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     ExpectedUpdateEvent(
128431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         event_type=EVENT_TYPE_DOWNLOAD_STARTED,
128531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         event_result=EVENT_RESULT_SUCCESS,
128631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         version=test_conf['source_release'],
128731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         error_message=(
128831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'Failed to start the download of the update '
128931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'payload from the staging server. Check both '
129031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'the omaha log and update_engine.log in '
129131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'sysinfo (or on the DUT).'))),
129231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    (self._WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS,
129331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     ExpectedUpdateEvent(
129431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         event_type=EVENT_TYPE_DOWNLOAD_FINISHED,
129531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         event_result=EVENT_RESULT_SUCCESS,
129631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         version=test_conf['source_release'],
129731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         error_message=(
129831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'Failed to finish download from devserver. '
129931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'Check the update_engine.log in sysinfo (or '
130031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'on the DUT).'))),
130131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    (self._WAIT_FOR_UPDATE_COMPLETED_SECONDS,
130231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     ExpectedUpdateEvent(
130331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         event_type=EVENT_TYPE_UPDATE_COMPLETE,
130431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         event_result=EVENT_RESULT_SUCCESS,
130531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         version=test_conf['source_release'],
130631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         error_message=(
130731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'Failed to complete update before reboot. '
130831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'Check the update_engine.log in sysinfo (or '
130931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                 'on the DUT).'))))
131031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
131131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            log_verifier.verify_expected_event_chain(chain)
131231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
131331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Wait after an update completion (safety margin).
131431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            _wait(self._WAIT_AFTER_UPDATE_SECONDS, 'after update completion')
131531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        finally:
131631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Terminate perf monitoring process and collect its output.
131731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            perf_data = self._stop_perf_mon(perf_mon_pid)
131831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            if perf_data:
131931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                self._report_perf_data(perf_data)
1320f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1321f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Reboot the DUT after the update.
1322ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        if self._use_servo:
1323f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._servo_dut_reboot()
1324f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
13252f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            # Only update the stateful partition since the test has updated the
13262f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            # rootfs.
13272f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            self.update_via_test_payloads(
13282f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                    None, None, staged_urls.target_stateful_url, clobber=False)
1329f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            self._host.reboot()
13300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1331f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Trigger a second update check (again, test vs MP).
1332f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
13332f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            self._trigger_test_update(self._omaha_devserver)
1334f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        else:
1335f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # TODO(garnold) chromium-os:33766: use GPIOs to trigger an
1336f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            # update.
1337f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            pass
13380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1339f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Observe post-reboot update check, which should indicate that the
1340f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # image version has been updated.
1341f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        chain = ExpectedUpdateEventChain(
1342f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                (self._WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS,
1343f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                 ExpectedUpdateEvent(
13440338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                     event_type=EVENT_TYPE_UPDATE_COMPLETE,
13450338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                     event_result=EVENT_RESULT_SUCCESS_REBOOT,
1346f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                     version=test_conf['target_release'],
1347ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                     previous_version=test_conf['source_release'],
13480338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                     error_message=(
13490338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             'Failed to reboot into the target version after '
13500338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             'an update. Check the sysinfo logs. This probably '
13510338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             'means that the updated image failed to verify '
13520338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             'after reboot and might mean that the update '
13530338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             'payload is bad'))))
1354ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1355ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        log_verifier.verify_expected_event_chain(chain)
1356f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1357f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # On test images, make sure we're using a different partition after
1358f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # the update.
1359f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        if self._use_test_image:
1360f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            target_rootfs_partition = self._get_rootdev()
1361f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa            if target_rootfs_partition == source_rootfs_partition:
1362f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                raise error.TestFail(
13630338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                        'Rootfs partition did not change (%s)' %
1364f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa                        target_rootfs_partition)
13650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
13660338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info(
13670338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                    'Target image rootfs partition changed as expected: %s',
13680338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                    target_rootfs_partition)
13690338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
13700338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Update successful, test completed')
13710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1372ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1373ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    def run_once(self, host, test_conf, use_servo):
1374ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Performs a complete auto update test.
1375ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1376ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param host: a host object representing the DUT
1377ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param test_conf: a dictionary containing test configuration values
1378ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param use_servo: True whether we should use servo.
1379ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1380ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raise error.TestError if anything went wrong with setting up the test;
1381ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa               error.TestFail if any part of the test has failed.
1382ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1383ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """
1384f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1385f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett        if not test_conf['target_release']:
1386f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett            raise RequiredArgumentMissing(
1387f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett                    'target_release is a required argument.')
1388f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1389ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # Attempt to get the job_repo_url to find the stateful payload for the
1390ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # target image.
1391ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        try:
1392ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            self._job_repo_url = host.lookup_job_repo_url()
1393ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        except KeyError:
1394ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            logging.warning('Job Repo URL not found. Assuming stateful '
1395ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                            'payload can be found along with the target update')
1396ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1397ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._host = host
1398ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._use_test_image = test_conf.get('image_type') != 'mp'
1399ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._use_servo = use_servo
1400ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        if self._use_servo:
1401ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            self._dev_mode = self._host.servo.get('dev_mode') == 'on'
1402ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1403ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # Verify that our arguments are sane.
1404ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._verify_preconditions()
1405ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1406009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # Find a devserver to use. We first try to pick a devserver with the
1407009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # least load. In case all devservers' load are higher than threshold,
1408009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # fall back to the old behavior by picking a devserver based on the
1409009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # payload URI, with which ImageServer.resolve will return a random
1410009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # devserver based on the hash of the URI.
14114e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        least_loaded_devserver = dev_server.get_least_loaded_devserver()
14124e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        if least_loaded_devserver:
14134e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi            logging.debug('Choose the least loaded devserver: %s',
14144e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi                          least_loaded_devserver)
14154e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi            autotest_devserver = dev_server.ImageServer(least_loaded_devserver)
14164e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        else:
1417009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi            logging.warning('No devserver meets the maximum load requirement. '
1418009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi                            'Pick a random devserver to use.')
1419009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi            autotest_devserver = dev_server.ImageServer.resolve(
1420009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi                    test_conf['target_payload_uri'])
1421ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        devserver_hostname = urlparse.urlparse(
1422ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                autotest_devserver.url()).hostname
1423d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        counter_key = dev_server.ImageServer.create_stats_str(
1424d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi                'paygen', devserver_hostname, artifacts=None)
1425d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        metadata = {'devserver': devserver_hostname,
1426d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi                    '_type': 'devserver_paygen'}
1427d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        metadata.update(test_conf)
1428d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        autotest_stats.Counter(counter_key, metadata=metadata).increment()
1429ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1430ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # Stage source images and update payloads onto a devserver.
1431ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        staged_urls = self.stage_artifacts_onto_devserver(
1432ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                autotest_devserver, test_conf)
1433ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1434ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # Install the source version onto the DUT.
1435fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold        if staged_urls.source_url:
1436fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold            self.install_source_version(devserver_hostname,
1437fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                                        staged_urls.source_url,
1438fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold                                        staged_urls.source_stateful_url)
1439ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1440ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._omaha_devserver = OmahaDevserver(
1441260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                devserver_hostname, self._devserver_dir, staged_urls.target_url)
1442ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._omaha_devserver.start_devserver()
1443c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1444c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # Make sure we can login before the update.
1445c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        self._run_login_test(test_conf['source_release'])
1446c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1447ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        try:
1448ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            self.run_update_test(staged_urls, test_conf)
1449ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        except ExpectedUpdateEventChainFailed:
1450ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            self._dump_update_engine_log()
1451ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            raise
1452f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1453c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # And make sure we can login after update.
1454c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        self._run_login_test(test_conf['target_release'])
1455