autoupdate_EndToEndTest.py revision 585cbd6a5cc32db99c6745aa4333471b6b095b43
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
17b9d7adf07aec05db6a3f1a8b74f45abd7a87c74aPrathmesh Prabhufrom autotest_lib.server import afe_utils, autotest, hosts, test
182f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosafrom autotest_lib.server.cros.dynamic_suite import tools
190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnolddef _wait(secs, desc=None):
220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Emits a log message and sleeps for a given number of seconds."""
230338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    msg = 'Waiting %s seconds' % secs
240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    if desc:
250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        msg += ' (%s)' % desc
260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    logging.info(msg)
270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    time.sleep(secs)
280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
30ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosaclass ExpectedUpdateEventChainFailed(error.TestFail):
31ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    """Raised if we fail to receive an expected event in a chain."""
32ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
330f27cafd72cd7c6aee752d0ec90de1b58e3b97ddGilad Arnoldclass RequiredArgumentMissing(error.TestError):
340f27cafd72cd7c6aee752d0ec90de1b58e3b97ddGilad Arnold    """Raised if the test is missing a required argument."""
35f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
36ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
370338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold# Update event types.
380338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_COMPLETE = '1'
390338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_INSTALL_COMPLETE = '2'
400338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_UPDATE_COMPLETE = '3'
410338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_STARTED = '13'
420338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_FINISHED = '14'
43fac9a5d238337da91939328386c2fa4fdfb6d957Alex DeymoEVENT_TYPE_REBOOTED_AFTER_UPDATE = '54'
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
51a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold# Omaha event types/results, from update_engine/omaha_request_action.h
52a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold# These are stored in dict form in order to easily print out the keys.
53a0ca5707ed10a6575ed290f341294331455f7769Gilad ArnoldEVENT_TYPE_DICT = {
54a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_DOWNLOAD_COMPLETE: 'download_complete',
55a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_INSTALL_COMPLETE: 'install_complete',
56a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_UPDATE_COMPLETE: 'update_complete',
57a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_DOWNLOAD_STARTED: 'download_started',
58fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        EVENT_TYPE_DOWNLOAD_FINISHED: 'download_finished',
59fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        EVENT_TYPE_REBOOTED_AFTER_UPDATE: 'rebooted_after_update'
60a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold}
61a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
62a0ca5707ed10a6575ed290f341294331455f7769Gilad ArnoldEVENT_RESULT_DICT = {
63a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_ERROR: 'error',
64a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_SUCCESS: 'success',
65a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_SUCCESS_REBOOT: 'success_reboot',
66a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_UPDATE_DEFERRED: 'update_deferred'
67a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold}
68a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
690338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
70fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnolddef snippet(text):
71fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    """Returns the text with start/end snip markers around it.
72fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
73fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    @param text: The snippet text.
74fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
75fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    @return The text with start/end snip markers around it.
76fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    """
77fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    snip = '---8<---' * 10
78fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    start = '-- START -'
79fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    end = '-- END -'
80fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    return ('%s%s\n%s\n%s%s' %
81fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold            (start, snip[len(start):], text, end, snip[len(end):]))
82fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
83fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEvent(object):
85248108c1c925791d926105f42212b1033213a4dcGilad Arnold    """Defines an expected event in an update process."""
8645f02ae47134953169805d281992c9edf0019250Chris Sosa
870338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _ATTR_NAME_DICT_MAP = {
88a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            'event_type': EVENT_TYPE_DICT,
89a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            'event_result': EVENT_RESULT_DICT,
900338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    }
910338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
92a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _VALID_TYPES = set(EVENT_TYPE_DICT.keys())
93a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _VALID_RESULTS = set(EVENT_RESULT_DICT.keys())
9445f02ae47134953169805d281992c9edf0019250Chris Sosa
950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __init__(self, event_type=None, event_result=None, version=None,
96248108c1c925791d926105f42212b1033213a4dcGilad Arnold                 previous_version=None, on_error=None):
97248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """Initializes an event expectation.
98248108c1c925791d926105f42212b1033213a4dcGilad Arnold
99248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param event_type: Expected event type.
100248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param event_result: Expected event result code.
101248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param version: Expected reported image version.
102248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param previous_version: Expected reported previous image version.
103248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param on_error: This is either an object to be returned when a received
104248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         event mismatches the expectation, or a callable used
105248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         for generating one. In the latter case, takes as
106248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         input two attribute dictionaries (expected and actual)
107248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         and an iterable of mismatched keys. If None, a generic
108248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         message is returned.
109248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """
1100338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if event_type and event_type not in self._VALID_TYPES:
11145f02ae47134953169805d281992c9edf0019250Chris Sosa            raise ValueError('event_type %s is not valid.' % event_type)
11245f02ae47134953169805d281992c9edf0019250Chris Sosa
1130338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if event_result and event_result not in self._VALID_RESULTS:
11445f02ae47134953169805d281992c9edf0019250Chris Sosa            raise ValueError('event_result %s is not valid.' % event_result)
11545f02ae47134953169805d281992c9edf0019250Chris Sosa
1160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._expected_attrs = {
1170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_type': event_type,
1180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_result': event_result,
1190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'version': version,
1200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'previous_version': previous_version,
1210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        }
122248108c1c925791d926105f42212b1033213a4dcGilad Arnold        self._on_error = on_error
1230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1250338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    @staticmethod
1260338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    def _attr_val_str(attr_val, helper_dict, default=None):
1270338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """Returns an enriched attribute value string, or default."""
1280338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if not attr_val:
1290338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            return default
1300338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1310338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        s = str(attr_val)
1320338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if helper_dict:
1330338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            s += ':%s' % helper_dict.get(attr_val, 'unknown')
1340338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1350338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return s
1360338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1370338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1380338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    def _attr_name_and_values(self, attr_name, expected_attr_val,
1390338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                              actual_attr_val=None):
1400338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """Returns an attribute name, expected and actual value strings.
1410338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1420338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        This will return (name, expected, actual); the returned value for
1430338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        actual will be None if its respective input is None/empty.
1440338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1450338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """
1460338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        helper_dict = self._ATTR_NAME_DICT_MAP.get(attr_name)
1470338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        expected_attr_val_str = self._attr_val_str(expected_attr_val,
1480338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                   helper_dict,
1490338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                   default='any')
1500338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        actual_attr_val_str = self._attr_val_str(actual_attr_val, helper_dict)
1510338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1520338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return attr_name, expected_attr_val_str, actual_attr_val_str
1530338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1540338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
155248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def _attrs_to_str(self, attrs_dict):
1560338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return ' '.join(['%s=%s' %
1570338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                         self._attr_name_and_values(attr_name, attr_val)[0:2]
158248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         for attr_name, attr_val in attrs_dict.iteritems()])
159248108c1c925791d926105f42212b1033213a4dcGilad Arnold
160248108c1c925791d926105f42212b1033213a4dcGilad Arnold
161248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def __str__(self):
162248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return self._attrs_to_str(self._expected_attrs)
1630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, actual_event):
1660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify the attributes of an actual event.
1670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
168ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param actual_event: a dictionary containing event attributes
1690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
170248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @return An error message, or None if all attributes as expected.
1710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
173248108c1c925791d926105f42212b1033213a4dcGilad Arnold        mismatched_attrs = [
174248108c1c925791d926105f42212b1033213a4dcGilad Arnold                attr_name for attr_name, expected_attr_val
175248108c1c925791d926105f42212b1033213a4dcGilad Arnold                in self._expected_attrs.iteritems()
176248108c1c925791d926105f42212b1033213a4dcGilad Arnold                if (expected_attr_val and
177248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    not self._verify_attr(attr_name, expected_attr_val,
178248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                          actual_event.get(attr_name)))]
179248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if not mismatched_attrs:
180248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return None
181248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if callable(self._on_error):
182248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return self._on_error(self._expected_attrs, actual_event,
183248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                  mismatched_attrs)
184248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if self._on_error is None:
185248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return ('Received event (%s) does not match expectation (%s)' %
186248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    (self._attrs_to_str(actual_event), self))
187248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return self._on_error
1880ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _verify_attr(self, attr_name, expected_attr_val, actual_attr_val):
1910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual log event attributes matches expected on.
1920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param attr_name: name of the attribute to verify
1940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_attr_val: expected attribute value
1950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param actual_attr_val: actual attribute value
1960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if actual value is present and matches, False otherwise.
1980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
20045f02ae47134953169805d281992c9edf0019250Chris Sosa        # None values are assumed to be missing and non-matching.
201f014ab424450fd595c347d905ac06ccf3d6faaddGilad Arnold        if actual_attr_val is None:
2020338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.error('No value found for %s (expected %s)',
2030338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                          *self._attr_name_and_values(attr_name,
2040338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                      expected_attr_val)[0:2])
20545f02ae47134953169805d281992c9edf0019250Chris Sosa            return False
20645f02ae47134953169805d281992c9edf0019250Chris Sosa
20716e76893c34739649a751f42b3881b57620d665cGilad Arnold        # We allow expected version numbers (e.g. 2940.0.0) to be contained in
20816e76893c34739649a751f42b3881b57620d665cGilad Arnold        # actual values (2940.0.0-a1); this is necessary for the test to pass
20916e76893c34739649a751f42b3881b57620d665cGilad Arnold        # with developer / non-release images.
21016e76893c34739649a751f42b3881b57620d665cGilad Arnold        if (actual_attr_val == expected_attr_val or
21116e76893c34739649a751f42b3881b57620d665cGilad Arnold            ('version' in attr_name and expected_attr_val in actual_attr_val)):
21216e76893c34739649a751f42b3881b57620d665cGilad Arnold            return True
2130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
21416e76893c34739649a751f42b3881b57620d665cGilad Arnold        return False
2150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
217248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def get_attrs(self):
218248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """Returns a dictionary of expected attributes."""
219248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return dict(self._expected_attrs)
220248108c1c925791d926105f42212b1033213a4dcGilad Arnold
221248108c1c925791d926105f42212b1033213a4dcGilad Arnold
2220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEventChain(object):
2230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Defines a chain of expected update events."""
224248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def __init__(self):
225fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        self._expected_events_chain = []
2260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
228fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def add_event(self, expected_events, timeout, on_timeout=None):
229248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """Adds an expected event to the chain.
230248108c1c925791d926105f42212b1033213a4dcGilad Arnold
231fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        @param expected_events: The ExpectedEvent, or a list thereof, to wait
232fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                                for. If a list is passed, it will wait for *any*
233fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                                of the provided events, but only one of those.
234248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param timeout: A timeout (in seconds) to wait for the event.
235248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param on_timeout: An error string to use if the event times out. If
236248108c1c925791d926105f42212b1033213a4dcGilad Arnold                           None, a generic message is used.
2370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
238fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        if isinstance(expected_events, ExpectedUpdateEvent):
23916e76893c34739649a751f42b3881b57620d665cGilad Arnold            expected_events = [expected_events]
240fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        self._expected_events_chain.append(
241fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                (expected_events, timeout, on_timeout))
2420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
244cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
245fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def _format_event_with_timeout(expected_events, timeout):
246cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """Returns a string representation of the event, with timeout."""
247248108c1c925791d926105f42212b1033213a4dcGilad Arnold        until = 'within %s seconds' % timeout if timeout else 'indefinitely'
248fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        return '%s, %s' % (' OR '.join(map(str, expected_events)), until)
2490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __str__(self):
2520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ('[%s]' %
2530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                ', '.join(
254fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                    [self._format_event_with_timeout(expected_events, timeout)
255fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                     for expected_events, timeout, _
256fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                     in self._expected_events_chain]))
2570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __repr__(self):
260fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        return str(self._expected_events_chain)
2610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, get_next_event):
2640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual stream of events complies.
2650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: a function returning the next event
2670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
268ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify an event.
2690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
271fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        for expected_events, timeout, on_timeout in self._expected_events_chain:
2720338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Expecting %s',
273fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                         self._format_event_with_timeout(expected_events,
274248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                                         timeout))
275248108c1c925791d926105f42212b1033213a4dcGilad Arnold            err_msg = self._verify_event_with_timeout(
276fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                    expected_events, timeout, on_timeout, get_next_event)
277248108c1c925791d926105f42212b1033213a4dcGilad Arnold            if err_msg is not None:
278248108c1c925791d926105f42212b1033213a4dcGilad Arnold                logging.error('Failed expected event: %s', err_msg)
279248108c1c925791d926105f42212b1033213a4dcGilad Arnold                raise ExpectedUpdateEventChainFailed(err_msg)
2800ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
282cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
283fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def _verify_event_with_timeout(expected_events, timeout, on_timeout,
284248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                   get_next_event):
2850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify an expected event occurs within a given timeout.
2860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
287fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        @param expected_events: the list of possible events expected next
288248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param timeout: specified in seconds
289248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param on_timeout: A string to return if timeout occurs, or None.
2900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: function returning the next event in a stream
2910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
292248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @return None if event complies, an error string otherwise.
2930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
2950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        base_timestamp = curr_timestamp = time.time()
2960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        expired_timestamp = base_timestamp + timeout
2970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        while curr_timestamp <= expired_timestamp:
2980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            new_event = get_next_event()
2990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            if new_event:
3000338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.info('Event received after %s seconds',
3010338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             round(curr_timestamp - base_timestamp, 1))
30216e76893c34739649a751f42b3881b57620d665cGilad Arnold                results = [event.verify(new_event) for event in expected_events]
30316e76893c34739649a751f42b3881b57620d665cGilad Arnold                return None if None in results else ' AND '.join(results)
3040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # No new events, sleep for one second only (so we don't miss
3060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # events at the end of the allotted timeout).
3070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            time.sleep(1)
3080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            curr_timestamp = time.time()
3090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3100338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.error('Timeout expired')
311248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if on_timeout is None:
312248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return ('Waiting for event %s timed out after %d seconds' %
313fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                    (' OR '.join(map(str, expected_events)), timeout))
314248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return on_timeout
3150ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass UpdateEventLogVerifier(object):
3180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Verifies update event chains on a devserver update log."""
31903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    def __init__(self, event_log_url, url_request_timeout=None):
3200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log_url = event_log_url
32103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        self._url_request_timeout = url_request_timeout
3220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log = []
3230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._num_consumed_events = 0
3240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
326fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def verify_expected_events_chain(self, expected_event_chain):
327ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        """Verify a given event chain.
328ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa
329ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param expected_event_chain: instance of expected event chain.
330ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
331ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify the an
332ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                event.
333ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        """
334ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        expected_event_chain.verify(self._get_next_log_event)
3350ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _get_next_log_event(self):
3380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Returns the next event in an event log.
3390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        Uses the URL handed to it during initialization to obtain the host log
3410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        from a devserver. If new events are encountered, the first of them is
3420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        consumed and returned.
3430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return The next new event in the host log, as reported by devserver;
34503901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                None if no such event was found or an error occurred.
3460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
3480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # (Re)read event log from devserver, if necessary.
3490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) <= self._num_consumed_events:
35003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            try:
35103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                if self._url_request_timeout:
35203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url,
35303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                                           timeout=self._url_request_timeout)
35403901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                else:
35503901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url)
35603901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            except urllib2.URLError, e:
3570338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.warning('Failed to read event log url: %s', e)
35803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                return None
359a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold            except socket.timeout, e:
360a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold                logging.warning('Timed out reading event log url: %s', e)
361a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold                return None
36203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
3630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            event_log_resp = conn.read()
3640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            conn.close()
3650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._event_log = json.loads(event_log_resp)
3660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
36703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        # Return next new event, if one is found.
3680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) > self._num_consumed_events:
3697572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold            new_event = {
3707572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold                    key: str(val) for key, val
3717572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold                    in self._event_log[self._num_consumed_events].iteritems()
3727572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold            }
3730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._num_consumed_events += 1
3740338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Consumed new event: %s', new_event)
3750ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            return new_event
3760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
378793344b359304c31d78197788b55cfbbe2636025Chris Sosaclass OmahaDevserverFailedToStart(error.TestError):
379793344b359304c31d78197788b55cfbbe2636025Chris Sosa    """Raised when a omaha devserver fails to start."""
380793344b359304c31d78197788b55cfbbe2636025Chris Sosa
381793344b359304c31d78197788b55cfbbe2636025Chris Sosa
3820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass OmahaDevserver(object):
3830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Spawns a test-private devserver instance."""
384793344b359304c31d78197788b55cfbbe2636025Chris Sosa    # How long to wait for a devserver to start.
38552c35724507ec105053d2af792fe161a627e05c1Alex Deymo    _WAIT_FOR_DEVSERVER_STARTED_SECONDS = 30
386793344b359304c31d78197788b55cfbbe2636025Chris Sosa
387260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    # How long to sleep (seconds) between checks to see if a devserver is up.
38803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    _WAIT_SLEEP_INTERVAL = 1
3890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3906f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    # Max devserver execution time (seconds); used with timeout(1) to ensure we
3916f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    # don't have defunct instances hogging the system.
3926f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    _DEVSERVER_TIMELIMIT_SECONDS = 12 * 60 * 60
393793344b359304c31d78197788b55cfbbe2636025Chris Sosa
394793344b359304c31d78197788b55cfbbe2636025Chris Sosa
395260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def __init__(self, omaha_host, devserver_dir, update_payload_staged_url):
3960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Starts a private devserver instance, operating at Omaha capacity.
3970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param omaha_host: host address where the devserver is spawned.
39903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        @param devserver_dir: path to the devserver source directory
400ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param update_payload_staged_url: URL to provision for update requests.
4010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
403ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        if not update_payload_staged_url:
4040338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('Missing update payload url')
4050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4066c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._omaha_host = omaha_host
407260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_pid = 0
408260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_port = 0  # Determined later from devserver portfile.
4096c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_dir = devserver_dir
410ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._update_payload_staged_url = update_payload_staged_url
4116c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
4126c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh = hosts.SSHHost(self._omaha_host,
4136c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                            user=os.environ['USER'])
414260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
4153563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        # Temporary files for various devserver outputs.
4163563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_logfile = None
417238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        self._devserver_stdoutfile = None
4183563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_portfile = None
4193563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_pidfile = None
4203563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_static_dir = None
4213563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4223563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4233563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa    def _cleanup_devserver_files(self):
4243563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Cleans up the temporary devserver files."""
425238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        for filename in (self._devserver_logfile, self._devserver_stdoutfile,
426238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                         self._devserver_portfile, self._devserver_pidfile):
427238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo            if filename:
428238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                self._devserver_ssh.run('rm -f %s' % filename,
429238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                                        ignore_status=True)
4303563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4313563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        if self._devserver_static_dir:
4323563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa            self._devserver_ssh.run('rm -rf %s' % self._devserver_static_dir,
4333563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                                    ignore_status=True)
4343563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
435260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
4363563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa    def _create_tempfile_on_devserver(self, label, dir=False):
4373563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Creates a temporary file/dir on the devserver and returns its path.
438260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
439260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @param label: Identifier for the file context (string, no whitespaces).
4403563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        @param dir: If True, create a directory instead of a file.
441260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
442260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @raises test.TestError: If we failed to invoke mktemp on the server.
443260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @raises OmahaDevserverFailedToStart: If tempfile creation failed.
444260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """
445260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        remote_cmd = 'mktemp --tmpdir devserver-%s.XXXXXX' % label
4463563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        if dir:
4473563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa            remote_cmd += ' --directory'
4483563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
449260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
450260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            result = self._devserver_ssh.run(remote_cmd, ignore_status=True)
451260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except error.AutoservRunError as e:
452260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._log_and_raise_remote_ssh_error(e)
453260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if result.exit_status != 0:
454260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            raise OmahaDevserverFailedToStart(
455260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'Could not create a temporary %s file on the devserver, '
456a7412a90ffefac2b6314726b284af1b31d6bd797Alex Deymo                    'error output: "%s"' % (label, result.stderr))
457260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return result.stdout.strip()
458260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
459260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    @staticmethod
460260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _log_and_raise_remote_ssh_error(e):
461260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Logs failure to ssh remote, then raises a TestError."""
462260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.debug('Failed to ssh into the devserver: %s', e)
463260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.error('If you are running this locally it means you did not '
464260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                      'configure ssh correctly.')
465260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        raise error.TestError('Failed to ssh into the devserver: %s' % e)
466260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
467260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
468260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _read_int_from_devserver_file(self, filename):
469260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Reads and returns an integer value from a file on the devserver."""
470260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return int(self._get_devserver_file_content(filename).strip())
4710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4726c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
473793344b359304c31d78197788b55cfbbe2636025Chris Sosa    def _wait_for_devserver_to_start(self):
474793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """Waits until the devserver starts within the time limit.
475793344b359304c31d78197788b55cfbbe2636025Chris Sosa
476260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        Infers and sets the devserver PID and serving port.
477260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
478793344b359304c31d78197788b55cfbbe2636025Chris Sosa        Raises:
479793344b359304c31d78197788b55cfbbe2636025Chris Sosa            OmahaDevserverFailedToStart: If the time limit is reached and we
480793344b359304c31d78197788b55cfbbe2636025Chris Sosa                                         cannot connect to the devserver.
481793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """
482260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # Compute the overall timeout.
483260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        deadline = time.time() + self._WAIT_FOR_DEVSERVER_STARTED_SECONDS
484260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
485260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # First, wait for port file to be filled and determine the server port.
48694b9ad4983cc735e162a3549c1628d08241b0d57Gilad Arnold        logging.warning('Waiting for devserver to start up.')
487260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        while time.time() < deadline:
488260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            try:
489260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                self._devserver_pid = self._read_int_from_devserver_file(
490260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        self._devserver_pidfile)
491260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                self._devserver_port = self._read_int_from_devserver_file(
492260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        self._devserver_portfile)
493260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                logging.info('Devserver pid is %d, serving on port %d',
494260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                             self._devserver_pid, self._devserver_port)
495260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
496260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            except Exception:  # Couldn't read file or corrupt content.
497260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                time.sleep(self._WAIT_SLEEP_INTERVAL)
498260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        else:
49952c35724507ec105053d2af792fe161a627e05c1Alex Deymo            try:
50052c35724507ec105053d2af792fe161a627e05c1Alex Deymo                self._devserver_ssh.run_output('uptime')
50152c35724507ec105053d2af792fe161a627e05c1Alex Deymo            except error.AutoservRunError as e:
50252c35724507ec105053d2af792fe161a627e05c1Alex Deymo                logging.debug('Failed to run uptime on the devserver: %s', e)
50394b9ad4983cc735e162a3549c1628d08241b0d57Gilad Arnold            raise OmahaDevserverFailedToStart(
504260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'The test failed to find the pid/port of the omaha '
50552c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    'devserver after %d seconds. Check the dumped devserver '
50652c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    'logs and devserver load for more information.' %
50752c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    self._WAIT_FOR_DEVSERVER_STARTED_SECONDS)
508260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
509260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # Check that the server is reponsding to network requests.
510260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.warning('Waiting for devserver to accept network requests.')
511260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        url = 'http://%s' % self.get_netloc()
512260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        while time.time() < deadline:
5139e2c98d8d0c61a816e736a387591950af9505504xixuan            if dev_server.ImageServer.devserver_healthy(url, timeout_min=0.1):
514260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
515793344b359304c31d78197788b55cfbbe2636025Chris Sosa
516793344b359304c31d78197788b55cfbbe2636025Chris Sosa            # TODO(milleral): Refactor once crbug.com/221626 is resolved.
517793344b359304c31d78197788b55cfbbe2636025Chris Sosa            time.sleep(self._WAIT_SLEEP_INTERVAL)
518793344b359304c31d78197788b55cfbbe2636025Chris Sosa        else:
519793344b359304c31d78197788b55cfbbe2636025Chris Sosa            raise OmahaDevserverFailedToStart(
520793344b359304c31d78197788b55cfbbe2636025Chris Sosa                    'The test failed to establish a connection to the omaha '
521793344b359304c31d78197788b55cfbbe2636025Chris Sosa                    'devserver it set up on port %d. Check the dumped '
522260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'devserver logs for more information.' %
523260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    self._devserver_port)
524793344b359304c31d78197788b55cfbbe2636025Chris Sosa
525793344b359304c31d78197788b55cfbbe2636025Chris Sosa
5266c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def start_devserver(self):
527793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """Starts the devserver and confirms it is up.
528793344b359304c31d78197788b55cfbbe2636025Chris Sosa
529793344b359304c31d78197788b55cfbbe2636025Chris Sosa        Raises:
530260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            test.TestError: If we failed to spawn the remote devserver.
531793344b359304c31d78197788b55cfbbe2636025Chris Sosa            OmahaDevserverFailedToStart: If the time limit is reached and we
532793344b359304c31d78197788b55cfbbe2636025Chris Sosa                                         cannot connect to the devserver.
5336c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """
5342f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        update_payload_url_base, update_payload_path = self._split_url(
535ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                self._update_payload_staged_url)
5363563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
5373563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        # Allocate temporary files for various server outputs.
5383563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_logfile = self._create_tempfile_on_devserver('log')
539238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        self._devserver_stdoutfile = self._create_tempfile_on_devserver(
540238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                'stdout')
5413563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_portfile = self._create_tempfile_on_devserver('port')
5423563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_pidfile = self._create_tempfile_on_devserver('pid')
5433563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_static_dir = self._create_tempfile_on_devserver(
5443563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                'static', dir=True)
5453563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
5466f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # Invoke the Omaha/devserver on the remote server. Will attempt to kill
5476f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # it with a SIGTERM after a predetermined timeout has elapsed, followed
5486f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # by SIGKILL if not dead within 30 seconds from the former signal.
5490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        cmdlist = [
5506f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold                'timeout', '-s', 'TERM', '-k', '30',
5516f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold                str(self._DEVSERVER_TIMELIMIT_SECONDS),
5526c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                '%s/devserver.py' % self._devserver_dir,
5530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--payload=%s' % update_payload_path,
554260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--port=0',
555260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--pidfile=%s' % self._devserver_pidfile,
556260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--portfile=%s' % self._devserver_portfile,
557260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--logfile=%s' % self._devserver_logfile,
5580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--remote_payload',
5590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--urlbase=%s' % update_payload_url_base,
5600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--max_updates=1',
5610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--host_log',
5623563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                '--static_dir=%s' % self._devserver_static_dir,
5631fd5e3042c6d8de3bdbb8cc169b5e0c5bd61af93Kevin Cernekee                '--critical_update',
5640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        ]
565238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        remote_cmd = '( %s ) </dev/null >%s 2>&1 &' % (
566238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                ' '.join(cmdlist), self._devserver_stdoutfile)
5676c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
568260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.info('Starting devserver with %r', remote_cmd)
569260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
570260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._devserver_ssh.run_output(remote_cmd)
571260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except error.AutoservRunError as e:
572260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._log_and_raise_remote_ssh_error(e)
5736c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
574260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
575260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._wait_for_devserver_to_start()
576260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except OmahaDevserverFailedToStart:
577260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._kill_remote_process()
578260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._dump_devserver_log()
579299e7788f7054dccab196dd7274f75ad41b66606Alex Deymo            self._cleanup_devserver_files()
580260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            raise
58138ba6b79b19b5bdd9cfe71b26efd0c267768527aGilad Arnold
5826c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
583260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _kill_remote_process(self):
584260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Kills the devserver and verifies it's down; clears the remote pid."""
585260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        def devserver_down():
586cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """Ensure that the devserver process is down."""
587260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            return not self._remote_process_alive()
5886c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
589260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if devserver_down():
5906c55bdb98e967675456a71a0971b81058536cac8Chris Sosa            return
5916c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
592260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        for signal in 'SIGTERM', 'SIGKILL':
593260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            remote_cmd = 'kill -s %s %s' % (signal, self._devserver_pid)
594260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._devserver_ssh.run(remote_cmd)
595260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            try:
596260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                client_utils.poll_for_condition(
597260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        devserver_down, sleep_interval=1, desc='devserver down')
598260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
599260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            except client_utils.TimeoutError:
600260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                logging.warning('Could not kill devserver with %s.', signal)
601260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        else:
602260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            logging.warning('Failed to kill devserver, giving up.')
603260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
604260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_pid = None
6056c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
6066c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
607260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _remote_process_alive(self):
608260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Tests whether the remote devserver process is running."""
609260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if not self._devserver_pid:
610260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            return False
611260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        remote_cmd = 'test -e /proc/%s' % self._devserver_pid
612260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        result = self._devserver_ssh.run(remote_cmd, ignore_status=True)
613260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return result.exit_status == 0
6146c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
6156c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
6166c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def get_netloc(self):
6176c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """Returns the netloc (host:port) of the devserver."""
618260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if not (self._devserver_pid and self._devserver_port):
6190338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('No running omaha/devserver')
6206c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
621260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return '%s:%s' % (self._omaha_host, self._devserver_port)
6220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6236c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
6242f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    def get_update_url(self):
6252f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Returns the update_url you can use to update via this server."""
6262f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        return urlparse.urlunsplit(('http', self.get_netloc(), '/update',
6272f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                    '', ''))
6282f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
6292f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
630260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _get_devserver_file_content(self, filename):
631260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Returns the content of a file on the devserver."""
6321391ae87d9941601dfc53377e2c187edcb0071dcGilad Arnold        return self._devserver_ssh.run_output('cat %s' % filename,
6331391ae87d9941601dfc53377e2c187edcb0071dcGilad Arnold                                              stdout_tee=None)
634260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
635260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
636260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _get_devserver_log(self):
637260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Obtain the devserver output."""
638260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return self._get_devserver_file_content(self._devserver_logfile)
639260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
640260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
641238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo    def _get_devserver_stdout(self):
642238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        """Obtain the devserver output in stdout and stderr."""
643238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        return self._get_devserver_file_content(self._devserver_stdoutfile)
644238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo
645238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo
646260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _dump_devserver_log(self, logging_level=logging.ERROR):
6473563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Dump the devserver log to the autotest log.
64819426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa
64919426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa        @param logging_level: logging level (from logging) to log the output.
65019426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa        """
6511391ae87d9941601dfc53377e2c187edcb0071dcGilad Arnold        logging.log(logging_level, "Devserver stdout and stderr:\n" +
6521391ae87d9941601dfc53377e2c187edcb0071dcGilad Arnold                    snippet(self._get_devserver_stdout()))
6531391ae87d9941601dfc53377e2c187edcb0071dcGilad Arnold        logging.log(logging_level, "Devserver log file:\n" +
6541391ae87d9941601dfc53377e2c187edcb0071dcGilad Arnold                    snippet(self._get_devserver_log()))
6550ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6560ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    @staticmethod
6580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _split_url(url):
6592f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Splits a URL into the URL base and path."""
6600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        split_url = urlparse.urlsplit(url)
6610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        url_base = urlparse.urlunsplit(
6622f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                (split_url.scheme, split_url.netloc, '', '', ''))
6632f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        url_path = split_url.path
6642f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        return url_base, url_path.lstrip('/')
6650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
667260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def stop_devserver(self):
668260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Kill remote process and wait for it to die, dump its output."""
6696c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        if not self._devserver_pid:
670ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            logging.error('No running omaha/devserver.')
671ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            return
6726c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
6730338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Killing omaha/devserver')
674260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._kill_remote_process()
67519426059c61c1b318b5d4bd04d70dacb63ec827dChris Sosa        logging.debug('Final devserver log before killing')
676260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._dump_devserver_log(logging.DEBUG)
6773563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._cleanup_devserver_files()
6780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
6790ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
680f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnoldclass TestPlatform(object):
681f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    """An interface and factory for platform-dependent functionality."""
68215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
6832f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # Named tuple containing urls for staged urls needed for test.
6842f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # source_url: url to find the update payload for the source image.
6852f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # source_stateful_url: url to find the stateful payload for the source
6862f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    #                      image.
6872f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # target_url: url to find the update payload for the target image.
6882f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    # target_stateful_url: url to find the stateful payload for the target
6892f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa    #                      image.
690f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    StagedURLs = collections.namedtuple(
691f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            'StagedURLs',
6922f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            ['source_url', 'source_stateful_url', 'target_url',
6932f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa             'target_stateful_url'])
6942f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
6950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
696f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def __init__(self):
697f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        assert False, 'Cannot instantiate this interface'
698f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
699f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
700f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    @staticmethod
701f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def create(host):
702f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Returns a TestPlatform implementation based on the host type.
703f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
704f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        *DO NOT* override this method.
705f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
706f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param host: a host object representing the DUT
707f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
708f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return A TestPlatform implementation.
709f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
710f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        os_type = host.get_os_type()
711585cbd6a5cc32db99c6745aa4333471b6b095b43Keith Haddow        if os_type in ('cros', 'moblab'):
712f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            return ChromiumOSTestPlatform(host)
713b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        if os_type == 'brillo':
714b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold            return BrilloTestPlatform(host)
715f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
716f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise error.TestError('Unknown OS type reported by host: %s' % os_type)
717f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
7180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
719f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def initialize(self, autotest_devserver, devserver_dir):
720f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Initialize the object.
7210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
722f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param autotest_devserver: Instance of client.common_lib.dev_server to
723f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                   use to reach the devserver instance for this
724f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                   build.
725f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param devserver_dir: Path to devserver source tree.
7260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
727f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
7280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
7290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
730f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def prep_artifacts(self, test_conf):
731f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Prepares update artifacts for the test.
73209706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
733f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        The test config must include 'source_payload_uri' and
734f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        'target_payload_uri'. In addition, it may include platform-specific
735f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        values as determined by the test control file.
73609706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
737f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param test_conf: Dictionary containing the test configuration.
73809706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
739f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return A tuple of staged URLs.
740f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
741f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @raise error.TestError on failure.
74209706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold        """
743f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
74409706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
74509706f18ea3878f1b173a63fdc92f2fc6450bb4dGilad Arnold
746f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def reboot_device(self):
747f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Reboots the device."""
748f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
749f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
750f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
751f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def prep_device_for_update(self, source_release):
752f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Prepares the device for update.
753f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
7547ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        @param source_release: Source release version (string), or None.
755f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
756f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @raise error.TestError on failure.
757f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
758f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
759f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
760f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
761f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def get_active_slot(self):
762f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Returns the active boot slot of the device."""
763f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
764f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
765f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
766f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def start_update_perf(self, bindir):
767f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Starts performance monitoring (if available).
768f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
769f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param bindir: Directory containing test binary files.
770f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
771f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
772f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
773f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
774f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def stop_update_perf(self):
775f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Stops performance monitoring and returns data (if available).
776f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
777f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return Dictionary containing performance attributes.
778f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
779f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
780f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
781f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
782f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def trigger_update(self, omaha_devserver):
783f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Kicks off an update.
784f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
785f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param omaha_devserver: OmahaDevserver instance.
786f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
787f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
788f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
789f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
790f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def finalize_update(self):
791f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Performs post-update procedures."""
792f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
793f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
794f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
795f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def get_update_log(self, num_lines):
796f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Returns the update log.
797f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
798f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param num_lines: Number of log lines to return (tail), zero for all.
799f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
800f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return String containing the last |num_lines| from the update log.
801f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
802f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
803f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
804f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
805f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def check_device_after_update(self, target_release):
806f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Runs final sanity checks.
807f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
8087ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        @param target_release: Target release version (string), or None.
809f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
810f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @raise error.TestError on failure.
811f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """
812f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        raise NotImplementedError
813f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
814f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
815f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnoldclass ChromiumOSTestPlatform(TestPlatform):
816f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    """A TestPlatform implementation for Chromium OS."""
817f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
818f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _STATEFUL_UPDATE_FILENAME = 'stateful.tgz'
819f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _LOGINABLE_MINIMUM_RELEASE = 5110
820f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
821f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def __init__(self, host):
822f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._host = host
823f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._autotest_devserver = None
824f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._devserver_dir = None
825f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._staged_urls = None
826f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._perf_mon_pid = None
827f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
828f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
829f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _stage_payload(self, devserver_label, filename, archive_url=None):
8302f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Stage the given payload onto the devserver.
8310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8322f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        Works for either a stateful or full/delta test payload. Expects the
8332f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        gs_path or a combo of devserver_label + filename.
8340ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8352f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param devserver_label: The build name e.g. x86-mario-release/<version>.
8362f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                If set, assumes default gs archive bucket and
8372f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                                requires filename to be specified.
8382f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param filename: In conjunction with devserver_label, if just specifying
8392f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                         the devserver label name, this is which file are you
8402f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                         downloading.
84115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @param archive_url: An optional GS archive location, if not using the
84215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                            devserver's default.
843ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa
8440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return URL of the staged payload on the server.
8450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8460ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @raise error.TestError if there's a problem with staging.
8470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
8492f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        try:
850f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._autotest_devserver.stage_artifacts(
85172f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett                    image=devserver_label, files=[filename],
85272f48be6c3f7e30edc6ff9a785f9c4cb53521af4Don Garrett                    archive_url=archive_url)
853f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            return self._autotest_devserver.get_staged_file_url(filename,
854f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                                                devserver_label)
8552f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        except dev_server.DevServerException, e:
8560338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('Failed to stage payload: %s' % e)
8570ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
8580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
859f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _stage_payload_by_uri(self, payload_uri):
86015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """Stage a payload based on its GS URI.
86115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
86215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        This infers the build's label, filename and GS archive from the
86315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        provided GS URI.
86415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
86515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @param payload_uri: The full GS URI of the payload.
86615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
86715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @return URL of the staged payload on the server.
86815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
86915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        @raise error.TestError if there's a problem with staging.
87015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
87115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """
87215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        archive_url, _, filename = payload_uri.rpartition('/')
87315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        devserver_label = urlparse.urlsplit(archive_url).path.strip('/')
87494694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold        return self._stage_payload(devserver_label, filename,
87594694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                                   archive_url=archive_url)
87615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
87715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
878cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
879cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    def _payload_to_update_url(payload_url):
8802f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Given a update or stateful payload url, returns the update url."""
8817231260da421abdf5ceceff6ab60155936dca21fChris Sosa        # We want to transform it to the correct omaha url which is
8827231260da421abdf5ceceff6ab60155936dca21fChris Sosa        # <hostname>/update/...LABEL.
8832f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        base_url = payload_url.rpartition('/')[0]
8842f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        return base_url.replace('/static/', '/update/')
8852f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
8862f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
88715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold    def _get_stateful_uri(self, build_uri):
88815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """Returns a complete GS URI of a stateful update given a build path."""
88915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return '/'.join([build_uri.rstrip('/'), self._STATEFUL_UPDATE_FILENAME])
89015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
89115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
89215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold    def _payload_to_stateful_uri(self, payload_uri):
89315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        """Given a payload GS URI, returns the corresponding stateful URI."""
89415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        build_uri = payload_uri.rpartition('/')[0]
895d8b9093f06cb882f7f46ca2f51d06630c9004f6dGwendal Grignou        if build_uri.endswith('payloads'):
896d8b9093f06cb882f7f46ca2f51d06630c9004f6dGwendal Grignou            build_uri = build_uri.rpartition('/')[0]
89715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return self._get_stateful_uri(build_uri)
8982f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
8992f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
900f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _update_via_test_payloads(self, omaha_host, payload_url, stateful_url,
901f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                  clobber):
902cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """Given the following update and stateful urls, update the DUT.
903cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
904cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        Only updates the rootfs/stateful if the respective url is provided.
905cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
906cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param omaha_host: If updating rootfs, redirect updates through this
907cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            host. Should be None iff payload_url is None.
908cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param payload_url: If set, the specified url to find the update
909cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            payload.
910cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param stateful_url: If set, the specified url to find the stateful
911cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            payload.
912cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param clobber: If True, do a clean install of stateful.
913cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """
914cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        def perform_update(url, is_stateful):
915cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """Perform a rootfs/stateful update using given URL.
916cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
917cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            @param url: URL to update from.
918cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            @param is_stateful: Whether this is a stateful or rootfs update.
919cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """
920cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if url:
921cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                updater = autoupdater.ChromiumOSUpdater(url, host=self._host)
922cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                if is_stateful:
923cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                    updater.update_stateful(clobber=clobber)
924cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                else:
9259e30d201214e586d185b497dea9a13a7e8d0495eGilad Arnold                    updater.update_image()
926cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
927cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        # We create a OmahaDevserver to redirect blah.bin to update/. This
928cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        # allows us to use any payload filename to serve an update.
929cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        temp_devserver = None
930cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        try:
9316273f5a63be4a2695b19cc76189607def69f5b27Gwendal Grignou            if omaha_host:
932cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                temp_devserver = OmahaDevserver(
9336273f5a63be4a2695b19cc76189607def69f5b27Gwendal Grignou                        omaha_host, self._devserver_dir,
9346273f5a63be4a2695b19cc76189607def69f5b27Gwendal Grignou                        payload_url or stateful_url)
935cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                temp_devserver.start_devserver()
9366273f5a63be4a2695b19cc76189607def69f5b27Gwendal Grignou            if payload_url:
937cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                payload_url = temp_devserver.get_update_url()
938cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
939cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            stateful_url = self._payload_to_update_url(stateful_url)
940cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
941cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            perform_update(payload_url, False)
942cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            perform_update(stateful_url, True)
943cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        finally:
944cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if temp_devserver:
945260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                temp_devserver.stop_devserver()
9462f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9472f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
948f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _install_source_version(self, devserver_hostname, image_url,
949f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                stateful_url):
9502f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Prepare the specified host with the image given by the urls.
9512f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
952ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param devserver_hostname: If updating rootfs, redirect updates
953ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   through this host. Should be None iff
954ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   image_url is None.
9552f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param image_url: If set, the specified url to find the source image
9562f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                          or full payload for the source image.
9572f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param stateful_url: If set, the specified url to find the stateful
9582f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                             payload.
9592f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """
96003286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou        # Reboot to get us into a clean state.
96103286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou        self._host.reboot()
9624cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        try:
9634cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # Since we are installing the source image of the test, clobber
9644cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # stateful.
965f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._update_via_test_payloads(devserver_hostname, image_url,
96694694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                                           stateful_url, True)
9671b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold        except OmahaDevserverFailedToStart as e:
9681b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            logging.fatal('Failed to start private devserver for installing '
9691b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                          'the source image (%s) on the DUT', image_url)
9701b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            raise error.TestError(
9711b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                    'Failed to start private devserver for installing the '
9721b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                    'source image on the DUT: %s' % e)
9731b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold        except error.AutoservRunError as e:
97403286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou            logging.fatal('Error re-imaging the DUT with '
97503286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou                          'the source image from %s', image_url)
97603286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou            raise error.TestError('Failed to install '
97703286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou                          'the source image DUT: %s' % e)
97803286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou        self._host.reboot()
97903286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou
98003286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou        # If powerwashed, need to reinstall stateful_url
98103286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou        if not self._host.check_rsync():
98203286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou            logging.warn('Device has been powerwashed, need to reinstall '
98303286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou                         'stateful from %s', stateful_url)
98403286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou            self._update_via_test_payloads(devserver_hostname, None,
98503286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou                                           stateful_url, True)
98603286f0f93ff59fc1744099378201f85db9c1883Gwendal Grignou            self._host.reboot()
987f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
988f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
989f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _stage_artifacts_onto_devserver(self, test_conf):
9902f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Stages artifacts that will be used by the test onto the devserver.
9912f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9922f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param test_conf: a dictionary containing test configuration values
9932f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
994f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return a StagedURLs tuple containing the staged urls.
995f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """
9960338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Staging images onto autotest devserver (%s)',
997f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     self._autotest_devserver.url())
998f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
99915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_source_url = None
100015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_source_stateful_url = None
10019cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold        try:
10029cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            source_payload_uri = test_conf['source_payload_uri']
10039cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold        except KeyError:
10049cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            # TODO(garnold) Remove legacy key support once control files on all
10059cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            # release branches have caught up.
10069cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            source_payload_uri = test_conf['source_image_uri']
10074cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        if source_payload_uri:
100894694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold            staged_source_url = self._stage_payload_by_uri(source_payload_uri)
10094cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold
10104cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # In order to properly install the source image using a full
10114cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # payload we'll also need the stateful update that comes with it.
10124cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # In general, tests may have their source artifacts in a different
10134cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # location than their payloads. This is determined by whether or
10144cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # not the source_archive_uri attribute is set; if it isn't set,
10154cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # then we derive it from the dirname of the source payload.
10164cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            source_archive_uri = test_conf.get('source_archive_uri')
10174cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            if source_archive_uri:
10184cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                source_stateful_uri = self._get_stateful_uri(source_archive_uri)
10192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            else:
10204cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                source_stateful_uri = self._payload_to_stateful_uri(
10214cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                        source_payload_uri)
1022fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold
10234cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            staged_source_stateful_url = self._stage_payload_by_uri(
102494694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                    source_stateful_uri)
102515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
10264cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # Log source image URLs.
10274cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            logging.info('Source full payload from %s staged at %s',
10284cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                         source_payload_uri, staged_source_url)
10294cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            if staged_source_stateful_url:
10304cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                logging.info('Source stateful update from %s staged at %s',
10314cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                             source_stateful_uri, staged_source_stateful_url)
103215384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
103315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_payload_uri = test_conf['target_payload_uri']
103494694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold        staged_target_url = self._stage_payload_by_uri(target_payload_uri)
103515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_stateful_uri = None
10360e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold        staged_target_stateful_url = None
103715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_archive_uri = test_conf.get('target_archive_uri')
10380e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold        if target_archive_uri:
10390e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            target_stateful_uri = self._get_stateful_uri(target_archive_uri)
10402f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        else:
10410e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            # Attempt to get the job_repo_url to find the stateful payload for
10420e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            # the target image.
1043b9d7adf07aec05db6a3f1a8b74f45abd7a87c74aPrathmesh Prabhu            job_repo_url = afe_utils.get_host_attribute(
1044b9d7adf07aec05db6a3f1a8b74f45abd7a87c74aPrathmesh Prabhu                    self._host, self._host.job_repo_url_attribute)
1045d8b9093f06cb882f7f46ca2f51d06630c9004f6dGwendal Grignou            if not job_repo_url:
104615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                target_stateful_uri = self._payload_to_stateful_uri(
104715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                    target_payload_uri)
10480e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            else:
10490e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                _, devserver_label = tools.get_devserver_build_from_package_url(
10500e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                        job_repo_url)
10510e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                staged_target_stateful_url = self._stage_payload(
105294694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                        devserver_label, self._STATEFUL_UPDATE_FILENAME)
1053f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
10540e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold        if not staged_target_stateful_url and target_stateful_uri:
105515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold            staged_target_stateful_url = self._stage_payload_by_uri(
105694694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                    target_stateful_uri)
10572f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
1058fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold        # Log target payload URLs.
105915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        logging.info('%s test payload from %s staged at %s',
106015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     test_conf['update_type'], target_payload_uri,
106115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     staged_target_url)
10620338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Target stateful update from %s staged at %s',
106315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     target_stateful_uri or 'standard location',
106415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     staged_target_stateful_url)
106515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
1066f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self.StagedURLs(staged_source_url, staged_source_stateful_url,
1067f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                               staged_target_url, staged_target_stateful_url)
1068f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1069f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1070c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen    def _run_login_test(self, release_string):
1071c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        """Runs login_LoginSuccess test if it is supported by the release."""
1072c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # Only do login tests with recent builds, since they depend on
1073c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # some binary compatibility with the build itself.
1074c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # '5116.0.0' -> ('5116', '0', '0') -> 5116
10757ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        if not release_string:
10767ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold            logging.info('No release provided, skipping login test.')
10777ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        elif int(release_string.split('.')[0]) > self._LOGINABLE_MINIMUM_RELEASE:
1078c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            # Login, to prove we can before/after the update.
1079c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            logging.info('Attempting to login (release %s).', release_string)
1080c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1081c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            client_at = autotest.Autotest(self._host)
10820c88356d674fbc2545000483603ea3bdb9d13529David Haddock            client_at.run_test('login_LoginSuccess', arc_mode='enabled')
1083c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        else:
1084c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            logging.info('Not attempting login test because %s is older than '
1085c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen                         '%d.', release_string, self._LOGINABLE_MINIMUM_RELEASE)
1086c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1087c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1088f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _start_perf_mon(self, bindir):
108931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Starts monitoring performance and resource usage on a DUT.
109031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
109131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        Call _stop_perf_mon() with the returned PID to stop monitoring
109231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        and collect the results.
109331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1094f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param bindir: Directoy containing monitoring script.
1095f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1096f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return The PID of the newly created DUT monitoring process.
109731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
109831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # We can't assume much about the source image so we copy the
109931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # performance monitoring script to the DUT directly.
1100f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        path = os.path.join(bindir, 'update_engine_performance_monitor.py')
110131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        self._host.send_file(path, '/tmp')
110231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        cmd = 'python /tmp/update_engine_performance_monitor.py --start-bg'
110331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        return int(self._host.run(cmd).stdout)
110431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
110531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
110631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _stop_perf_mon(self, perf_mon_pid):
110731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Stops monitoring performance and resource usage on a DUT.
110831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
110931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        @param perf_mon_pid: the PID returned from _start_perf_mon().
111031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1111f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return Dictionary containing performance attributes, or None if
1112f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                unavailable.
111331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
111431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # Gracefully handle problems with performance monitoring by
111531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # just returning None.
111631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        try:
111731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            cmd = ('python /tmp/update_engine_performance_monitor.py '
111831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                   '--stop-bg=%d') % perf_mon_pid
111931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            perf_json_txt = self._host.run(cmd).stdout
112031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            return json.loads(perf_json_txt)
112131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        except Exception as e:
112231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.warning('Failed to parse output from '
112331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                            'update_engine_performance_monitor.py: %s', e)
112431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        return None
112531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
112631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1127f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # Interface overrides.
1128f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    #
1129f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def initialize(self, autotest_devserver, devserver_dir):
1130f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._autotest_devserver = autotest_devserver
1131f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._devserver_dir = devserver_dir
1132f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1133f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1134f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def reboot_device(self):
1135f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._host.reboot()
1136f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1137f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1138f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def prep_artifacts(self, test_conf):
1139f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._staged_urls = self._stage_artifacts_onto_devserver(test_conf)
1140f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self._staged_urls
1141f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1142f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1143f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def prep_device_for_update(self, source_release):
1144f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Install the source version onto the DUT.
1145f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._staged_urls.source_url:
11469b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.info('Installing a source image on the DUT')
1147f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            devserver_hostname = urlparse.urlparse(
1148f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                    self._autotest_devserver.url()).hostname
1149f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._install_source_version(devserver_hostname,
1150f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                         self._staged_urls.source_url,
1151f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                         self._staged_urls.source_stateful_url)
1152f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1153f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Make sure we can login before the update.
1154f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._run_login_test(source_release)
1155f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1156f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1157f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def get_active_slot(self):
1158f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self._host.run('rootdev -s').stdout.strip()
1159f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1160f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1161f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def start_update_perf(self, bindir):
1162f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._perf_mon_pid is None:
1163f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._perf_mon_pid = self._start_perf_mon(bindir)
1164f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1165f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1166f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def stop_update_perf(self):
1167f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        perf_data = None
1168f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._perf_mon_pid is not None:
1169f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            perf_data = self._stop_perf_mon(self._perf_mon_pid)
1170f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._perf_mon_pid = None
1171f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1172f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return perf_data
1173f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1174f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1175f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def trigger_update(self, omaha_devserver):
1176f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        updater = autoupdater.ChromiumOSUpdater(
1177f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                omaha_devserver.get_update_url(), host=self._host)
1178f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        updater.trigger_update()
1179f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1180f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1181f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def finalize_update(self):
1182f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._update_via_test_payloads(
118394694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                None, None, self._staged_urls.target_stateful_url, False)
1184f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1185f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1186f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def get_update_log(self, num_lines):
1187f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self._host.run_output(
1188fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                'tail -n %d /var/log/update_engine.log' % num_lines,
1189fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                stdout_tee=None)
1190f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1191f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1192f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def check_device_after_update(self, target_release):
1193f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Make sure we can login after update.
1194f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._run_login_test(target_release)
1195f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1196f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1197b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnoldclass BrilloTestPlatform(TestPlatform):
1198b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    """A TestPlatform implementation for Brillo."""
1199b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1200b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    _URL_DEFAULT_PORT = 80
1201b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    _DUT_LOCALHOST = '127.0.0.1'
1202b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1203b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def __init__(self, host):
1204b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        self._host = host
12059b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._autotest_devserver = None
12069b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._devserver_dir = None
12079b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._staged_urls = None
1208b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        self._forwarding_ports = set()
1209b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1210b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1211b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    @classmethod
1212b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _get_host_port(cls, url):
1213b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Returns the host and port values from a given URL.
1214b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1215b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param url: The URL from which the values are extracted.
1216b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1217b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @return A pair consisting of the host and port strings.
1218b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        """
1219b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        host, _, port = urlparse.urlsplit(url).netloc.partition(':')
1220b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        return host, port or str(cls._URL_DEFAULT_PORT)
1221b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1222b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1223b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _install_rev_forwarding(self, port=None):
1224b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Installs reverse forwarding rules via ADB.
12259b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
1226b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param port: The TCP port we want forwarded; if None, installs all
1227b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                     previously configured ports.
1228b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """
1229b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        ports = self._forwarding_ports if port is None else [port]
1230b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        for port in ports:
1231b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            port_spec = 'tcp:%s' % port
1232b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            self._host.add_forwarding(port_spec, port_spec, reverse=True)
1233b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1234b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1235b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _add_rev_forwarding(self, url):
1236b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Configures reverse port forwarding and adjusts the given URL.
1237b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1238b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        This extracts the port from the URL, adds it to the set of configured
1239b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        forwarding ports, installs it to the DUT, then returns the adjusted URL
1240b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        for use by the DUT.
1241b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1242b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param url: The URL for which we need to establish forwarding.
1243b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1244b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @return: The adjusted URL for use on the DUT.
12459b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        """
12469b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        if url:
1247b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            host, port = self._get_host_port(url)
1248b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            if port not in self._forwarding_ports:
1249b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._forwarding_ports.add(port)
1250b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._install_rev_forwarding(port=port)
1251b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            url = url.replace(host, self._DUT_LOCALHOST, 1)
1252b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        return url
12539b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
12549b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
1255b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _remove_rev_forwarding(self, url=None):
1256b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Removes a reverse port forwarding.
1257b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1258b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param url: The URL for which forwarding was established; if None,
1259b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                    removes all previously configured ports.
1260b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """
1261b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        ports = set()
1262b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        if url is None:
1263b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            ports.update(self._forwarding_ports)
1264b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        else:
1265b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            _, port = self._get_host_port(url)
1266b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            if port in self._forwarding_ports:
1267b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                ports.add(port)
1268b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1269b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # TODO(garnold) Enable once ADB port removal is fixed (b/24771474):
1270b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # for port in ports:
1271b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        #     self._host.remove_forwarding(src='tcp:%s' % port, reverse=True)
1272b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1273b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        self._forwarding_ports.difference_update(ports)
1274b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1275b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
12769b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold    def _install_source_version(self, devserver_hostname, payload_url):
12779b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        """Installs a source version onto the test device.
12789b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
12799b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        @param devserver_hostname: Redirect updates through this host.
12809b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        @param payload_url: URL of staged payload for installing a source image.
12819b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        """
12829b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        try:
12839b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            # Start a private Omaha server and update the DUT.
12849b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            temp_devserver = None
1285b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            url = None
12869b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            try:
12879b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                temp_devserver = OmahaDevserver(
12889b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                        devserver_hostname, self._devserver_dir, payload_url)
12899b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                temp_devserver.start_devserver()
1290b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                url = self._add_rev_forwarding(temp_devserver.get_update_url())
12919b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                updater = autoupdater.BrilloUpdater(url, host=self._host)
12929b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                updater.update_image()
12939b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            finally:
1294b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                if url:
1295b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                    self._remove_rev_forwarding(url)
12969b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                if temp_devserver:
12979b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    temp_devserver.stop_devserver()
12989b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
12999b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            # Reboot the DUT.
13009b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            self.reboot_device()
13019b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        except OmahaDevserverFailedToStart as e:
13029b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.fatal('Failed to start private devserver for installing '
13039b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                          'the source payload (%s) on the DUT', payload_url)
13049b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            raise error.TestError(
13059b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    'Failed to start private devserver for installing the '
13069b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    'source image on the DUT: %s' % e)
13079b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        except error.AutoservRunError as e:
13089b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.fatal('Error re-imaging or rebooting the DUT with the '
13099b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                          'source image from %s', payload_url)
13109b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            raise error.TestError('Failed to install the source image or '
13119b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                                  'reboot the DUT: %s' % e)
13129b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
13139b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
1314b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    # Interface overrides.
1315b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    #
1316b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def initialize(self, autotest_devserver, devserver_dir):
13179b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._autotest_devserver = autotest_devserver
13189b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._devserver_dir = devserver_dir
1319b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1320b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1321b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def reboot_device(self):
1322b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        self._host.reboot()
1323b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        self._install_rev_forwarding()
1324b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1325b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1326b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def prep_artifacts(self, test_conf):
1327b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        # TODO(garnold) Currently we don't stage anything and assume that the
1328b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        # provided URLs are of pre-staged payloads. We should implement staging
1329b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        # support once a release scheme for Brillo is finalized.
13309b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._staged_urls = self.StagedURLs(
1331b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._add_rev_forwarding(test_conf['source_payload_uri']), None,
1332b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._add_rev_forwarding(test_conf['target_payload_uri']), None)
13339b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        return self._staged_urls
1334b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1335b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1336b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def prep_device_for_update(self, source_release):
13379b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        # Install the source version onto the DUT.
13389b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        if self._staged_urls.source_url:
13399b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.info('Installing a source image on the DUT')
13409b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            devserver_hostname = urlparse.urlparse(
13419b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    self._autotest_devserver.url()).hostname
13429b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            self._install_source_version(devserver_hostname,
13439b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                                         self._staged_urls.source_url)
1344b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1345b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1346b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def get_active_slot(self):
1347b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        return self._host.run('rootdev -s /system').stdout.strip()
1348b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1349b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1350b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def start_update_perf(self, bindir):
1351b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        pass
1352b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1353b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1354b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def stop_update_perf(self):
1355b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        pass
1356b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1357b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1358b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def trigger_update(self, omaha_devserver):
1359b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        url = self._add_rev_forwarding(omaha_devserver.get_update_url())
1360b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        updater = autoupdater.BrilloUpdater(url, host=self._host)
1361b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        updater.trigger_update()
1362b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1363b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1364b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def finalize_update(self):
1365b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        pass
1366b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1367b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1368b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def get_update_log(self, num_lines):
1369b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        return self._host.run_output(
1370fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                'logcat -d -s update_engine | tail -n %d' % num_lines,
1371fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                stdout_tee=None)
1372b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1373b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1374b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def check_device_after_update(self, target_release):
1375b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        self._remove_rev_forwarding()
1376b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # TODO(garnold) Port forwarding removal is broken in ADB (b/24771474).
1377b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # Instead we reboot the device, which has the side-effect of flushing
1378b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # all forwarding rules. Once fixed, the following should be removed.
137903322d32f30c95159e0651709a87132fa8b12bb4Gilad Arnold        self.reboot_device()
1380b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1381b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1382f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnoldclass autoupdate_EndToEndTest(test.test):
1383f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    """Complete update test between two Chrome OS releases.
1384f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1385f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    Performs an end-to-end test of updating a ChromeOS device from one version
1386f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    to another. The test performs the following steps:
1387f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1388f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      1. Stages the source (full) and target update payload on the central
1389f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold         devserver.
1390f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      2. Spawns a private Omaha-like devserver instance, configured to return
1391f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold         the target (update) payload URL in response for an update check.
1392f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      3. Reboots the DUT.
1393f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      4. Installs a source image on the DUT (if provided) and reboots to it.
1394f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      5. Triggers an update check at the DUT.
1395f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      6. Watches as the DUT obtains an update and applies it.
1396f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      7. Reboots and repeats steps 5-6, ensuring that the next update check
1397f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold         shows the new image version.
1398f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1399f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    Some notes on naming:
1400f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      devserver: Refers to a machine running the Chrome OS Update Devserver.
1401f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      autotest_devserver: An autotest wrapper to interact with a devserver.
1402f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          Can be used to stage artifacts to a devserver. While
1403f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          this can also be used to update a machine, we do not
1404f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          use it for that purpose in this test as we manage
1405f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          updates with out own devserver instances (see below).
1406f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      omaha_devserver: This test's wrapper of a devserver running for the
1407f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       purposes of emulating omaha. This test controls the
1408f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       lifetime of this devserver instance and is separate
1409f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       from the autotest lab's devserver's instances which are
1410f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       only used for staging and hosting artifacts (because they
1411f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       scale). These are run on the same machines as the actual
1412f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       autotest devservers which are used for staging but on
1413f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       different ports.
1414f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      *staged_url's: In this case staged refers to the fact that these items
1415f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     are available to be downloaded statically from these urls
1416f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     e.g. 'localhost:8080/static/my_file.gz'. These are usually
1417f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     given after staging an artifact using a autotest_devserver
1418f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     though they can be re-created given enough assumptions.
1419f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      *update_url's: Urls refering to the update RPC on a given omaha devserver.
1420f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     Since we always use an instantiated omaha devserver to run
1421f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     updates, these will always reference an existing instance
1422f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     of an omaha devserver that we just created for the purposes
1423f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     of updating.
1424f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      devserver_hostname: At the start of each test, we choose a devserver
1425f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          machine in the lab for the test. We use the devserver
1426f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          instance there (access by autotest_devserver) to stage
1427f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          artifacts. However, we also use the same host to start
1428f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          omaha devserver instances for updating machines with
1429f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          (that reference the staged paylaods on the autotest
1430f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          devserver instance). This hostname refers to that
1431f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          machine we are using (since it's always the same for
1432f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          both staging/omaha'ing).
1433f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1434f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    """
1435f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    version = 1
1436f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1437f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # Timeout periods, given in seconds.
1438f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_AFTER_SHUTDOWN_SECONDS = 10
1439f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_AFTER_UPDATE_SECONDS = 20
1440f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_USB_INSTALL_SECONDS = 4 * 60
1441f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_MP_RECOVERY_SECONDS = 8 * 60
1442f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS = 12 * 60
1443f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # TODO(sosa): Investigate why this needs to be so long (this used to be
1444f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # 120 and regressed).
1445f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_DOWNLOAD_STARTED_SECONDS = 4 * 60
1446f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS = 10 * 60
1447f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_UPDATE_COMPLETED_SECONDS = 4 * 60
1448f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS = 15 * 60
1449f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS = 30
1450f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1451a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    # Logs and their whereabouts.
1452a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _WHERE_UPDATE_LOG = ('update_engine log (in sysinfo or on the DUT, also '
1453a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                         'included in the test log)')
1454a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _WHERE_OMAHA_LOG = 'Omaha-devserver log (included in the test log)'
1455a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1456f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1457f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def initialize(self):
1458f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Sets up variables that will be used by test."""
1459f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._host = None
1460f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._omaha_devserver = None
1461a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        self._source_image_installed = False
1462f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1463f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._devserver_dir = global_config.global_config.get_config_value(
1464f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                'CROS', 'devserver_dir', default=None)
1465f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._devserver_dir is None:
1466f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            raise error.TestError(
1467f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                    'Path to devserver source tree not provided; please define '
1468f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                    'devserver_dir under [CROS] in your shadow_config.ini')
1469f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1470f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1471f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def cleanup(self):
1472f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Kill the omaha devserver if it's still around."""
1473f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._omaha_devserver:
1474f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._omaha_devserver.stop_devserver()
1475f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1476f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._omaha_devserver = None
1477f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1478f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1479f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _dump_update_engine_log(self, test_platform):
1480f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Dumps relevant AU error log."""
1481f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        try:
1482fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold            error_log = test_platform.get_update_log(80)
1483fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold            logging.error('Dumping snippet of update_engine log:\n%s',
1484fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                          snippet(error_log))
1485f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        except Exception:
1486f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            # Mute any exceptions we get printing debug logs.
1487f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            pass
1488f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1489f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
149031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _report_perf_data(self, perf_data):
149131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Reports performance and resource data.
149231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1493f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        Currently, performance attributes are expected to include 'rss_peak'
1494f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        (peak memory usage in bytes).
1495f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1496f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param perf_data: A dictionary containing performance attributes.
149731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
149831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        rss_peak = perf_data.get('rss_peak')
149931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        if rss_peak:
150031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            rss_peak_kib = rss_peak / 1024
150131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.info('Peak memory (RSS) usage on DUT: %d KiB', rss_peak_kib)
150231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            self.output_perf_value(description='mem_usage_peak',
150331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   value=int(rss_peak_kib),
150431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   units='KiB',
150531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   higher_is_better=False)
150631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        else:
150731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.warning('No rss_peak key in JSON returned by '
150831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                            'update_engine_performance_monitor.py')
150931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
151031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1511a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_initial_check(self, expected, actual, mismatched_attrs):
1512a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'version' in mismatched_attrs:
1513a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            err_msg = ('Initial update check was received but the reported '
1514a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                       'version is different from what was expected.')
1515a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            if self._source_image_installed:
1516a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                err_msg += (' The source payload we installed was probably '
1517a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'incorrect or corrupt.')
1518a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            else:
1519a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                err_msg += (' The DUT is probably not running the correct '
1520a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'source image.')
1521a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return err_msg
1522a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1523a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return 'A test bug occurred; inspect the test log.'
1524a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1525a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1526a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_intermediate(self, expected, actual, mismatched_attrs, action,
1527a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            problem):
1528a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_result' in mismatched_attrs:
15290ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_result = actual.get('event_result')
15300ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = (('different than expected (%s)' %
15310ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                         EVENT_RESULT_DICT[event_result])
15320ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_result else 'missing')
15330ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            return ('The updater reported result code is %s. This could be an '
15340ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'updater bug or a connectivity problem; check the %s. For '
15350ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'a detailed log of update events, check the %s.' %
15360ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (reported, self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1537a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_type' in mismatched_attrs:
15380ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_type = actual.get('event_type')
15390ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = ('different (%s)' % EVENT_TYPE_DICT[event_type]
15400ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_type else 'missing')
15410ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            return ('Expected the updater to %s (%s) but received event type '
15420ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'is %s. This could be an updater %s; check the '
1543a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    '%s. For a detailed log of update events, check the %s.' %
15440ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (action, EVENT_TYPE_DICT[expected['event_type']], reported,
15450ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                     problem, self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1546a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'version' in mismatched_attrs:
1547a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The updater reported an unexpected version despite '
1548a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'previously reporting the correct one. This is most likely '
1549a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'a bug in update engine; check the %s.' %
1550a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WHERE_UPDATE_LOG)
1551a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1552a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return 'A test bug occurred; inspect the test log.'
1553a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1554a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1555a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_download_started(self, expected, actual, mismatched_attrs):
1556a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return self._error_intermediate(expected, actual, mismatched_attrs,
1557a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'begin downloading',
1558a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'bug, crash or provisioning error')
1559a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1560a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1561a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_download_finished(self, expected, actual, mismatched_attrs):
1562a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return self._error_intermediate(expected, actual, mismatched_attrs,
1563a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'finish downloading', 'bug or crash')
1564a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1565a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1566a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_update_complete(self, expected, actual, mismatched_attrs):
1567a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return self._error_intermediate(expected, actual, mismatched_attrs,
1568a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'complete the update', 'bug or crash')
1569a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1570a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
157116e76893c34739649a751f42b3881b57620d665cGilad Arnold    def _error_reboot_after_update(self, expected, actual, mismatched_attrs):
1572a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_result' in mismatched_attrs:
15730ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_result = actual.get('event_result')
15740ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = ('different (%s)' % EVENT_RESULT_DICT[event_result]
15750ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_result else 'missing')
1576a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The updater was expected to reboot (%s) but reported '
15770ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'result code is %s. This could be a failure to reboot, an '
15780ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'updater bug or a connectivity problem; check the %s and '
15790ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'the system log. For a detailed log of update events, '
15800ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'check the %s.' %
15810ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (EVENT_RESULT_DICT[expected['event_result']], reported,
1582a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                     self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1583a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_type' in mismatched_attrs:
15840ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_type = actual.get('event_type')
15850ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = ('different (%s)' % EVENT_TYPE_DICT[event_type]
15860ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_type else 'missing')
1587a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('Expected to successfully reboot into the new image (%s) '
15880ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'but received event type is %s. This probably means that '
15890ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'the new image failed to verify after reboot, possibly '
15900ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'because the payload is corrupt. This might also be an '
15910ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'updater bug or crash; check the %s. For a detailed log of '
15920ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'update events, check the %s.' %
15930ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (EVENT_TYPE_DICT[expected['event_type']], reported,
1594a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                     self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1595a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'version' in mismatched_attrs:
1596a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The DUT rebooted after the update but reports a different '
1597a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'image version than the one expected. This probably means '
1598a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'that the payload we applied was incorrect or corrupt.')
1599a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'previous_version' in mismatched_attrs:
1600a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The DUT rebooted after the update and reports the '
1601a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'expected version. However, it reports a previous version '
1602a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'that is different from the one previously reported. This '
1603a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'is most likely a bug in update engine; check the %s.' %
1604a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WHERE_UPDATE_LOG)
1605a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1606a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return 'A test bug occurred; inspect the test log.'
1607a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1608a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1609a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _timeout_err(self, desc, timeout, event_type=None):
1610a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if event_type is not None:
1611a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            desc += ' (%s)' % EVENT_TYPE_DICT[event_type]
1612a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return ('Failed to receive %s within %d seconds. This could be a '
1613a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                'problem with the updater or a connectivity issue. For more '
1614a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                'details, check the %s.' %
1615a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                (desc, timeout, self._WHERE_UPDATE_LOG))
1616a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1617a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1618f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def run_update_test(self, test_platform, test_conf):
1619ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Runs the actual update test once preconditions are met.
16200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1621f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param test_platform: TestPlatform implementation.
1622ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param test_conf: A dictionary containing test configuration values
16230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1624ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify an update
1625ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                event.
16260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1627c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1628f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Record the active root partition.
1629f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        source_active_slot = test_platform.get_active_slot()
1630f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        logging.info('Source active slot: %s', source_active_slot)
16310ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
16327ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        source_release = test_conf['source_release']
16337ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        target_release = test_conf['target_release']
16347ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold
163531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # Start the performance monitoring process on the DUT.
1636f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.start_update_perf(self.bindir)
163731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        try:
163831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Trigger an update.
1639f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            test_platform.trigger_update(self._omaha_devserver)
16400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
164131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Track update progress.
164231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            omaha_netloc = self._omaha_devserver.get_netloc()
164331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            omaha_hostlog_url = urlparse.urlunsplit(
164431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    ['http', omaha_netloc, '/api/hostlog',
164531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     'ip=' + self._host.ip, ''])
164631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.info('Polling update progress from omaha/devserver: %s',
164731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         omaha_hostlog_url)
164831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            log_verifier = UpdateEventLogVerifier(
164931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    omaha_hostlog_url,
165031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    self._DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS)
165131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
165231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Verify chain of events in a successful update process.
1653248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain = ExpectedUpdateEventChain()
1654248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1655248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
16567ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1657a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_initial_check),
1658a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS,
1659a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1660a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'an initial update check',
1661a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS))
1662248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1663248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
1664248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_type=EVENT_TYPE_DOWNLOAD_STARTED,
1665248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_result=EVENT_RESULT_SUCCESS,
16667ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1667a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_download_started),
1668a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_DOWNLOAD_STARTED_SECONDS,
1669a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1670a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'a download started notification',
1671a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_DOWNLOAD_STARTED_SECONDS,
1672a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            event_type=EVENT_TYPE_DOWNLOAD_STARTED))
1673248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1674248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
1675248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_type=EVENT_TYPE_DOWNLOAD_FINISHED,
1676248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_result=EVENT_RESULT_SUCCESS,
16777ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1678a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_download_finished),
1679a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS,
1680a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1681a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'a download finished notification',
1682a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS,
1683a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            event_type=EVENT_TYPE_DOWNLOAD_FINISHED))
1684248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1685248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
1686248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_type=EVENT_TYPE_UPDATE_COMPLETE,
1687248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_result=EVENT_RESULT_SUCCESS,
16887ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1689a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_update_complete),
1690a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_UPDATE_COMPLETED_SECONDS,
1691a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1692a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'an update complete notification',
1693a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_UPDATE_COMPLETED_SECONDS,
1694a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            event_type=EVENT_TYPE_UPDATE_COMPLETE))
169531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1696fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            log_verifier.verify_expected_events_chain(chain)
169731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
169831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Wait after an update completion (safety margin).
169931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            _wait(self._WAIT_AFTER_UPDATE_SECONDS, 'after update completion')
170031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        finally:
170131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Terminate perf monitoring process and collect its output.
1702f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            perf_data = test_platform.stop_update_perf()
170331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            if perf_data:
170431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                self._report_perf_data(perf_data)
1705f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
17064cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        # Only update the stateful partition (the test updated the rootfs).
1707f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.finalize_update()
17084cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold
1709f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Reboot the DUT after the update.
1710bacc35bdb2fb44b0a760c05d364854cd92fb3f98Gilad Arnold        test_platform.reboot_device()
17110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1712f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Trigger a second update check (again, test vs MP).
1713f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.trigger_update(self._omaha_devserver)
17140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1715f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Observe post-reboot update check, which should indicate that the
1716f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # image version has been updated.
1717248108c1c925791d926105f42212b1033213a4dcGilad Arnold        chain = ExpectedUpdateEventChain()
1718fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        expected_events = [
1719fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            ExpectedUpdateEvent(
1720fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_type=EVENT_TYPE_UPDATE_COMPLETE,
1721fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_result=EVENT_RESULT_SUCCESS_REBOOT,
1722fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                version=target_release,
1723fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                previous_version=source_release,
1724fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                on_error=self._error_reboot_after_update),
1725fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            # Newer versions send a "rebooted_after_update" message after reboot
1726fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            # with the previous version instead of another "update_complete".
1727fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            ExpectedUpdateEvent(
1728fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_type=EVENT_TYPE_REBOOTED_AFTER_UPDATE,
1729fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_result=EVENT_RESULT_SUCCESS,
1730fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                version=target_release,
1731fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                previous_version=source_release,
1732fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                on_error=self._error_reboot_after_update),
1733fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        ]
1734248108c1c925791d926105f42212b1033213a4dcGilad Arnold        chain.add_event(
1735fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                expected_events,
1736a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                self._WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS,
1737a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                on_timeout=self._timeout_err(
1738a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        'a successful reboot notification',
1739a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        self._WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS,
1740a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        event_type=EVENT_TYPE_UPDATE_COMPLETE))
1741ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1742fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        log_verifier.verify_expected_events_chain(chain)
1743f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1744f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Make sure we're using a different slot after the update.
1745f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        target_active_slot = test_platform.get_active_slot()
1746f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if target_active_slot == source_active_slot:
17471b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            err_msg = 'The active image slot did not change after the update.'
17487ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold            if None in (source_release, target_release):
17497ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                err_msg += (' The DUT likely rebooted into the old image, which '
17507ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                            'probably means that the payload we applied was '
17517ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                            'corrupt. But since we did not check the source '
17527ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                            'and/or target version we cannot say for sure.')
17537ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold            elif source_release == target_release:
17541b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                err_msg += (' Given that the source and target versions are '
17551b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'identical, the DUT likely rebooted into the old '
17561b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'image. This probably means that the payload we '
17571b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'applied was corrupt.')
17581b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            else:
17591b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                err_msg += (' This is strange since the DUT reported the '
17601b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'correct target version. This is probably a system '
17611b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'bug; check the DUT system log.')
17621b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            raise error.TestFail(err_msg)
17630338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1764f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        logging.info('Target active slot changed as expected: %s',
1765f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     target_active_slot)
1766eb300ac8af429e51f751b95ce375636fbb649e37Gilad Arnold
17670338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Update successful, test completed')
17680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1769ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
17709cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold    # TODO(garnold) Remove the use_servo argument once control files on all
17719cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold    # release branches have caught up.
17724cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold    def run_once(self, host, test_conf, use_servo=False):
1773ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Performs a complete auto update test.
1774ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1775ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param host: a host object representing the DUT
1776ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param test_conf: a dictionary containing test configuration values
17774cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        @param use_servo: DEPRECATED
1778ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1779ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raise error.TestError if anything went wrong with setting up the test;
1780ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa               error.TestFail if any part of the test has failed.
1781ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1782ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """
1783f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1784ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._host = host
1785ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1786009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # Find a devserver to use. We first try to pick a devserver with the
1787009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # least load. In case all devservers' load are higher than threshold,
1788009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # fall back to the old behavior by picking a devserver based on the
1789009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # payload URI, with which ImageServer.resolve will return a random
1790009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # devserver based on the hash of the URI.
179101b28a630230336c3277dd7b9d375ee012931b77Dan Shi        # The picked devserver needs to respect the location of the host if
179201b28a630230336c3277dd7b9d375ee012931b77Dan Shi        # `prefer_local_devserver` is set to True or `restricted_subnets` is
179301b28a630230336c3277dd7b9d375ee012931b77Dan Shi        # set.
179401b28a630230336c3277dd7b9d375ee012931b77Dan Shi        hostname = self._host.hostname if self._host else None
179501b28a630230336c3277dd7b9d375ee012931b77Dan Shi        least_loaded_devserver = dev_server.get_least_loaded_devserver(
179601b28a630230336c3277dd7b9d375ee012931b77Dan Shi                hostname=hostname)
17974e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        if least_loaded_devserver:
17984e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi            logging.debug('Choose the least loaded devserver: %s',
17994e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi                          least_loaded_devserver)
18004e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi            autotest_devserver = dev_server.ImageServer(least_loaded_devserver)
18014e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        else:
1802009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi            logging.warning('No devserver meets the maximum load requirement. '
1803009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi                            'Pick a random devserver to use.')
1804009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi            autotest_devserver = dev_server.ImageServer.resolve(
180569b9b975fccab1ede1e8849ed17e7e655a9d6b48Dan Shi                    test_conf['target_payload_uri'], host.hostname)
1806ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        devserver_hostname = urlparse.urlparse(
1807ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                autotest_devserver.url()).hostname
1808ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1809f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Obtain a test platform implementation.
1810f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform = TestPlatform.create(host)
1811f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.initialize(autotest_devserver, self._devserver_dir)
1812f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1813ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # Stage source images and update payloads onto a devserver.
1814f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        staged_urls = test_platform.prep_artifacts(test_conf)
1815a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        self._source_image_installed = bool(staged_urls.source_url)
1816ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1817f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Prepare the DUT (install source version etc).
1818f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.prep_device_for_update(test_conf['source_release'])
1819ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1820ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._omaha_devserver = OmahaDevserver(
1821f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                devserver_hostname, self._devserver_dir,
1822f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                staged_urls.target_url)
1823ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._omaha_devserver.start_devserver()
1824c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1825ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        try:
1826f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self.run_update_test(test_platform, test_conf)
1827ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        except ExpectedUpdateEventChainFailed:
1828f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._dump_update_engine_log(test_platform)
1829ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            raise
1830f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1831f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.check_device_after_update(test_conf['target_release'])
1832