autoupdate_EndToEndTest.py revision 0ba4fcd882643427524c35522bf77959b57c6f48
10ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
20ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold# Use of this source code is governed by a BSD-style license that can be
30ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold# found in the LICENSE file.
40ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
52f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosaimport collections
60ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport json
70ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport logging
86c55bdb98e967675456a71a0971b81058536cac8Chris Sosaimport os
9a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnoldimport socket
1015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnoldimport time
110ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport urllib2
120ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldimport urlparse
1303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
146c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.client.bin import utils as client_utils
156c55bdb98e967675456a71a0971b81058536cac8Chris Sosafrom autotest_lib.client.common_lib import error, global_config
160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldfrom autotest_lib.client.common_lib.cros import autoupdater, dev_server
17d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shifrom autotest_lib.client.common_lib.cros.graphite import autotest_stats
188ddeb9c8d6df7eb2e033651e39f434ebdf8af3ccSimran Basifrom autotest_lib.server import autotest, hosts, test
192f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosafrom autotest_lib.server.cros.dynamic_suite import tools
200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnolddef _wait(secs, desc=None):
230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Emits a log message and sleeps for a given number of seconds."""
240338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    msg = 'Waiting %s seconds' % secs
250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    if desc:
260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        msg += ' (%s)' % desc
270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    logging.info(msg)
280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    time.sleep(secs)
290ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
31ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosaclass ExpectedUpdateEventChainFailed(error.TestFail):
32ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa    """Raised if we fail to receive an expected event in a chain."""
33ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
340f27cafd72cd7c6aee752d0ec90de1b58e3b97ddGilad Arnoldclass RequiredArgumentMissing(error.TestError):
350f27cafd72cd7c6aee752d0ec90de1b58e3b97ddGilad Arnold    """Raised if the test is missing a required argument."""
36f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
37ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
380338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold# Update event types.
390338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_COMPLETE = '1'
400338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_INSTALL_COMPLETE = '2'
410338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_UPDATE_COMPLETE = '3'
420338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_STARTED = '13'
430338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_TYPE_DOWNLOAD_FINISHED = '14'
44fac9a5d238337da91939328386c2fa4fdfb6d957Alex DeymoEVENT_TYPE_REBOOTED_AFTER_UPDATE = '54'
450338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
460338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold# Update event results.
470338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_ERROR = '0'
480338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_SUCCESS = '1'
490338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_SUCCESS_REBOOT = '2'
500338ff3b754d52b41ca2d0432164a757daa1112dGilad ArnoldEVENT_RESULT_UPDATE_DEFERRED = '9'
510338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
52a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold# Omaha event types/results, from update_engine/omaha_request_action.h
53a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold# These are stored in dict form in order to easily print out the keys.
54a0ca5707ed10a6575ed290f341294331455f7769Gilad ArnoldEVENT_TYPE_DICT = {
55a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_DOWNLOAD_COMPLETE: 'download_complete',
56a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_INSTALL_COMPLETE: 'install_complete',
57a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_UPDATE_COMPLETE: 'update_complete',
58a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_TYPE_DOWNLOAD_STARTED: 'download_started',
59fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        EVENT_TYPE_DOWNLOAD_FINISHED: 'download_finished',
60fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        EVENT_TYPE_REBOOTED_AFTER_UPDATE: 'rebooted_after_update'
61a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold}
62a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
63a0ca5707ed10a6575ed290f341294331455f7769Gilad ArnoldEVENT_RESULT_DICT = {
64a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_ERROR: 'error',
65a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_SUCCESS: 'success',
66a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_SUCCESS_REBOOT: 'success_reboot',
67a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        EVENT_RESULT_UPDATE_DEFERRED: 'update_deferred'
68a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold}
69a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
700338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
71fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnolddef snippet(text):
72fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    """Returns the text with start/end snip markers around it.
73fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
74fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    @param text: The snippet text.
75fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
76fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    @return The text with start/end snip markers around it.
77fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    """
78fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    snip = '---8<---' * 10
79fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    start = '-- START -'
80fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    end = '-- END -'
81fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold    return ('%s%s\n%s\n%s%s' %
82fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold            (start, snip[len(start):], text, end, snip[len(end):]))
83fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
84fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold
850ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEvent(object):
86248108c1c925791d926105f42212b1033213a4dcGilad Arnold    """Defines an expected event in an update process."""
8745f02ae47134953169805d281992c9edf0019250Chris Sosa
880338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    _ATTR_NAME_DICT_MAP = {
89a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            'event_type': EVENT_TYPE_DICT,
90a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            'event_result': EVENT_RESULT_DICT,
910338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    }
920338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
93a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _VALID_TYPES = set(EVENT_TYPE_DICT.keys())
94a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _VALID_RESULTS = set(EVENT_RESULT_DICT.keys())
9545f02ae47134953169805d281992c9edf0019250Chris Sosa
960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __init__(self, event_type=None, event_result=None, version=None,
97248108c1c925791d926105f42212b1033213a4dcGilad Arnold                 previous_version=None, on_error=None):
98248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """Initializes an event expectation.
99248108c1c925791d926105f42212b1033213a4dcGilad Arnold
100248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param event_type: Expected event type.
101248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param event_result: Expected event result code.
102248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param version: Expected reported image version.
103248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param previous_version: Expected reported previous image version.
104248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param on_error: This is either an object to be returned when a received
105248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         event mismatches the expectation, or a callable used
106248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         for generating one. In the latter case, takes as
107248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         input two attribute dictionaries (expected and actual)
108248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         and an iterable of mismatched keys. If None, a generic
109248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         message is returned.
110248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """
1110338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if event_type and event_type not in self._VALID_TYPES:
11245f02ae47134953169805d281992c9edf0019250Chris Sosa            raise ValueError('event_type %s is not valid.' % event_type)
11345f02ae47134953169805d281992c9edf0019250Chris Sosa
1140338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if event_result and event_result not in self._VALID_RESULTS:
11545f02ae47134953169805d281992c9edf0019250Chris Sosa            raise ValueError('event_result %s is not valid.' % event_result)
11645f02ae47134953169805d281992c9edf0019250Chris Sosa
1170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._expected_attrs = {
1180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_type': event_type,
1190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'event_result': event_result,
1200ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'version': version,
1210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            'previous_version': previous_version,
1220ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        }
123248108c1c925791d926105f42212b1033213a4dcGilad Arnold        self._on_error = on_error
1240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1260338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    @staticmethod
1270338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    def _attr_val_str(attr_val, helper_dict, default=None):
1280338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """Returns an enriched attribute value string, or default."""
1290338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if not attr_val:
1300338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            return default
1310338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1320338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        s = str(attr_val)
1330338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        if helper_dict:
1340338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            s += ':%s' % helper_dict.get(attr_val, 'unknown')
1350338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1360338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return s
1370338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1380338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1390338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold    def _attr_name_and_values(self, attr_name, expected_attr_val,
1400338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                              actual_attr_val=None):
1410338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """Returns an attribute name, expected and actual value strings.
1420338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1430338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        This will return (name, expected, actual); the returned value for
1440338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        actual will be None if its respective input is None/empty.
1450338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1460338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        """
1470338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        helper_dict = self._ATTR_NAME_DICT_MAP.get(attr_name)
1480338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        expected_attr_val_str = self._attr_val_str(expected_attr_val,
1490338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                   helper_dict,
1500338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                   default='any')
1510338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        actual_attr_val_str = self._attr_val_str(actual_attr_val, helper_dict)
1520338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1530338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return attr_name, expected_attr_val_str, actual_attr_val_str
1540338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1550338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
156248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def _attrs_to_str(self, attrs_dict):
1570338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        return ' '.join(['%s=%s' %
1580338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                         self._attr_name_and_values(attr_name, attr_val)[0:2]
159248108c1c925791d926105f42212b1033213a4dcGilad Arnold                         for attr_name, attr_val in attrs_dict.iteritems()])
160248108c1c925791d926105f42212b1033213a4dcGilad Arnold
161248108c1c925791d926105f42212b1033213a4dcGilad Arnold
162248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def __str__(self):
163248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return self._attrs_to_str(self._expected_attrs)
1640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, actual_event):
1670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify the attributes of an actual event.
1680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
169ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param actual_event: a dictionary containing event attributes
1700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
171248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @return An error message, or None if all attributes as expected.
1720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1730ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
174248108c1c925791d926105f42212b1033213a4dcGilad Arnold        mismatched_attrs = [
175248108c1c925791d926105f42212b1033213a4dcGilad Arnold                attr_name for attr_name, expected_attr_val
176248108c1c925791d926105f42212b1033213a4dcGilad Arnold                in self._expected_attrs.iteritems()
177248108c1c925791d926105f42212b1033213a4dcGilad Arnold                if (expected_attr_val and
178248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    not self._verify_attr(attr_name, expected_attr_val,
179248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                          actual_event.get(attr_name)))]
180248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if not mismatched_attrs:
181248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return None
182248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if callable(self._on_error):
183248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return self._on_error(self._expected_attrs, actual_event,
184248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                  mismatched_attrs)
185248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if self._on_error is None:
186248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return ('Received event (%s) does not match expectation (%s)' %
187248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    (self._attrs_to_str(actual_event), self))
188248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return self._on_error
1890ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _verify_attr(self, attr_name, expected_attr_val, actual_attr_val):
1920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual log event attributes matches expected on.
1930ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param attr_name: name of the attribute to verify
1950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param expected_attr_val: expected attribute value
1960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param actual_attr_val: actual attribute value
1970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return True if actual value is present and matches, False otherwise.
1990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
20145f02ae47134953169805d281992c9edf0019250Chris Sosa        # None values are assumed to be missing and non-matching.
202f014ab424450fd595c347d905ac06ccf3d6faaddGilad Arnold        if actual_attr_val is None:
2030338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.error('No value found for %s (expected %s)',
2040338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                          *self._attr_name_and_values(attr_name,
2050338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                                                      expected_attr_val)[0:2])
20645f02ae47134953169805d281992c9edf0019250Chris Sosa            return False
20745f02ae47134953169805d281992c9edf0019250Chris Sosa
20816e76893c34739649a751f42b3881b57620d665cGilad Arnold        # We allow expected version numbers (e.g. 2940.0.0) to be contained in
20916e76893c34739649a751f42b3881b57620d665cGilad Arnold        # actual values (2940.0.0-a1); this is necessary for the test to pass
21016e76893c34739649a751f42b3881b57620d665cGilad Arnold        # with developer / non-release images.
21116e76893c34739649a751f42b3881b57620d665cGilad Arnold        if (actual_attr_val == expected_attr_val or
21216e76893c34739649a751f42b3881b57620d665cGilad Arnold            ('version' in attr_name and expected_attr_val in actual_attr_val)):
21316e76893c34739649a751f42b3881b57620d665cGilad Arnold            return True
2140ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
21516e76893c34739649a751f42b3881b57620d665cGilad Arnold        return False
2160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
218248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def get_attrs(self):
219248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """Returns a dictionary of expected attributes."""
220248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return dict(self._expected_attrs)
221248108c1c925791d926105f42212b1033213a4dcGilad Arnold
222248108c1c925791d926105f42212b1033213a4dcGilad Arnold
2230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass ExpectedUpdateEventChain(object):
2240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Defines a chain of expected update events."""
225248108c1c925791d926105f42212b1033213a4dcGilad Arnold    def __init__(self):
226fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        self._expected_events_chain = []
2270ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2280ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
229fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def add_event(self, expected_events, timeout, on_timeout=None):
230248108c1c925791d926105f42212b1033213a4dcGilad Arnold        """Adds an expected event to the chain.
231248108c1c925791d926105f42212b1033213a4dcGilad Arnold
232fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        @param expected_events: The ExpectedEvent, or a list thereof, to wait
233fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                                for. If a list is passed, it will wait for *any*
234fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                                of the provided events, but only one of those.
235248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param timeout: A timeout (in seconds) to wait for the event.
236248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param on_timeout: An error string to use if the event times out. If
237248108c1c925791d926105f42212b1033213a4dcGilad Arnold                           None, a generic message is used.
2380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
239fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        if isinstance(expected_events, ExpectedUpdateEvent):
24016e76893c34739649a751f42b3881b57620d665cGilad Arnold            expected_events = [expected_events]
241fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        self._expected_events_chain.append(
242fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                (expected_events, timeout, on_timeout))
2430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
245cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
246fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def _format_event_with_timeout(expected_events, timeout):
247cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """Returns a string representation of the event, with timeout."""
248248108c1c925791d926105f42212b1033213a4dcGilad Arnold        until = 'within %s seconds' % timeout if timeout else 'indefinitely'
249fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        return '%s, %s' % (' OR '.join(map(str, expected_events)), until)
2500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2510ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2520ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __str__(self):
2530ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        return ('[%s]' %
2540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                ', '.join(
255fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                    [self._format_event_with_timeout(expected_events, timeout)
256fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                     for expected_events, timeout, _
257fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                     in self._expected_events_chain]))
2580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def __repr__(self):
261fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        return str(self._expected_events_chain)
2620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2630ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def verify(self, get_next_event):
2650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verifies that an actual stream of events complies.
2660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: a function returning the next event
2680ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
269ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify an event.
2700ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2710ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
272fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        for expected_events, timeout, on_timeout in self._expected_events_chain:
2730338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Expecting %s',
274fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                         self._format_event_with_timeout(expected_events,
275248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                                         timeout))
276248108c1c925791d926105f42212b1033213a4dcGilad Arnold            err_msg = self._verify_event_with_timeout(
277fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                    expected_events, timeout, on_timeout, get_next_event)
278248108c1c925791d926105f42212b1033213a4dcGilad Arnold            if err_msg is not None:
279248108c1c925791d926105f42212b1033213a4dcGilad Arnold                logging.error('Failed expected event: %s', err_msg)
280248108c1c925791d926105f42212b1033213a4dcGilad Arnold                raise ExpectedUpdateEventChainFailed(err_msg)
2810ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2820ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
283cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold    @staticmethod
284fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def _verify_event_with_timeout(expected_events, timeout, on_timeout,
285248108c1c925791d926105f42212b1033213a4dcGilad Arnold                                   get_next_event):
2860ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Verify an expected event occurs within a given timeout.
2870ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
288fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        @param expected_events: the list of possible events expected next
289248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param timeout: specified in seconds
290248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @param on_timeout: A string to return if timeout occurs, or None.
2910ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param get_next_event: function returning the next event in a stream
2920ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
293248108c1c925791d926105f42212b1033213a4dcGilad Arnold        @return None if event complies, an error string otherwise.
2940ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
2950ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
2960ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        base_timestamp = curr_timestamp = time.time()
2970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        expired_timestamp = base_timestamp + timeout
2980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        while curr_timestamp <= expired_timestamp:
2990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            new_event = get_next_event()
3000ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            if new_event:
3010338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.info('Event received after %s seconds',
3020338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                             round(curr_timestamp - base_timestamp, 1))
30316e76893c34739649a751f42b3881b57620d665cGilad Arnold                results = [event.verify(new_event) for event in expected_events]
30416e76893c34739649a751f42b3881b57620d665cGilad Arnold                return None if None in results else ' AND '.join(results)
3050ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # No new events, sleep for one second only (so we don't miss
3070ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            # events at the end of the allotted timeout).
3080ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            time.sleep(1)
3090ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            curr_timestamp = time.time()
3100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3110338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.error('Timeout expired')
312248108c1c925791d926105f42212b1033213a4dcGilad Arnold        if on_timeout is None:
313248108c1c925791d926105f42212b1033213a4dcGilad Arnold            return ('Waiting for event %s timed out after %d seconds' %
314fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                    (' OR '.join(map(str, expected_events)), timeout))
315248108c1c925791d926105f42212b1033213a4dcGilad Arnold        return on_timeout
3160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3170ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3180ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass UpdateEventLogVerifier(object):
3190ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Verifies update event chains on a devserver update log."""
32003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    def __init__(self, event_log_url, url_request_timeout=None):
3210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log_url = event_log_url
32203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        self._url_request_timeout = url_request_timeout
3230ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._event_log = []
3240ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        self._num_consumed_events = 0
3250ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3260ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
327fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo    def verify_expected_events_chain(self, expected_event_chain):
328ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        """Verify a given event chain.
329ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa
330ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        @param expected_event_chain: instance of expected event chain.
331ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
332ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify the an
333ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                event.
334ae6acd9b3e1c6f56310cdb4e05737122b7b2818cChris Sosa        """
335ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        expected_event_chain.verify(self._get_next_log_event)
3360ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3370ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3380ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    def _get_next_log_event(self):
3390ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Returns the next event in an event log.
3400ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3410ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        Uses the URL handed to it during initialization to obtain the host log
3420ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        from a devserver. If new events are encountered, the first of them is
3430ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        consumed and returned.
3440ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3450ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @return The next new event in the host log, as reported by devserver;
34603901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                None if no such event was found or an error occurred.
3470ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3480ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
3490ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        # (Re)read event log from devserver, if necessary.
3500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) <= self._num_consumed_events:
35103901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            try:
35203901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                if self._url_request_timeout:
35303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url,
35403901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                                           timeout=self._url_request_timeout)
35503901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                else:
35603901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                    conn = urllib2.urlopen(self._event_log_url)
35703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            except urllib2.URLError, e:
3580338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold                logging.warning('Failed to read event log url: %s', e)
35903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                return None
360a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold            except socket.timeout, e:
361a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold                logging.warning('Timed out reading event log url: %s', e)
362a0261611f9be78ee9d456fd50bea81e9eda07c17Gilad Arnold                return None
36303901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold
3640ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            event_log_resp = conn.read()
3650ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            conn.close()
3660ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._event_log = json.loads(event_log_resp)
3670ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
36803901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        # Return next new event, if one is found.
3690ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        if len(self._event_log) > self._num_consumed_events:
3707572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold            new_event = {
3717572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold                    key: str(val) for key, val
3727572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold                    in self._event_log[self._num_consumed_events].iteritems()
3737572b05fd3356b2ddf6866958e80c7885e44647bGilad Arnold            }
3740ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            self._num_consumed_events += 1
3750338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            logging.info('Consumed new event: %s', new_event)
3760ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold            return new_event
3770ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3780ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
379793344b359304c31d78197788b55cfbbe2636025Chris Sosaclass OmahaDevserverFailedToStart(error.TestError):
380793344b359304c31d78197788b55cfbbe2636025Chris Sosa    """Raised when a omaha devserver fails to start."""
381793344b359304c31d78197788b55cfbbe2636025Chris Sosa
382793344b359304c31d78197788b55cfbbe2636025Chris Sosa
3830ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnoldclass OmahaDevserver(object):
3840ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold    """Spawns a test-private devserver instance."""
385793344b359304c31d78197788b55cfbbe2636025Chris Sosa    # How long to wait for a devserver to start.
38652c35724507ec105053d2af792fe161a627e05c1Alex Deymo    _WAIT_FOR_DEVSERVER_STARTED_SECONDS = 30
387793344b359304c31d78197788b55cfbbe2636025Chris Sosa
388260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    # How long to sleep (seconds) between checks to see if a devserver is up.
38903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold    _WAIT_SLEEP_INTERVAL = 1
3900ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3916f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    # Max devserver execution time (seconds); used with timeout(1) to ensure we
3926f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    # don't have defunct instances hogging the system.
3936f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold    _DEVSERVER_TIMELIMIT_SECONDS = 12 * 60 * 60
394793344b359304c31d78197788b55cfbbe2636025Chris Sosa
395793344b359304c31d78197788b55cfbbe2636025Chris Sosa
396260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def __init__(self, omaha_host, devserver_dir, update_payload_staged_url):
3970ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """Starts a private devserver instance, operating at Omaha capacity.
3980ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
3990ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        @param omaha_host: host address where the devserver is spawned.
40003901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold        @param devserver_dir: path to the devserver source directory
401ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param update_payload_staged_url: URL to provision for update requests.
4020ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4030ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
404ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        if not update_payload_staged_url:
4050338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold            raise error.TestError('Missing update payload url')
4060ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4076c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._omaha_host = omaha_host
408260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_pid = 0
409260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        self._devserver_port = 0  # Determined later from devserver portfile.
4106c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_dir = devserver_dir
411ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._update_payload_staged_url = update_payload_staged_url
4126c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
4136c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        self._devserver_ssh = hosts.SSHHost(self._omaha_host,
4146c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                                            user=os.environ['USER'])
415260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
4163563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        # Temporary files for various devserver outputs.
4173563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_logfile = None
418238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        self._devserver_stdoutfile = None
4193563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_portfile = None
4203563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_pidfile = None
4213563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_static_dir = None
4223563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4233563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4243563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa    def _cleanup_devserver_files(self):
4253563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Cleans up the temporary devserver files."""
426238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        for filename in (self._devserver_logfile, self._devserver_stdoutfile,
427238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                         self._devserver_portfile, self._devserver_pidfile):
428238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo            if filename:
429238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                self._devserver_ssh.run('rm -f %s' % filename,
430238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                                        ignore_status=True)
4313563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
4323563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        if self._devserver_static_dir:
4333563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa            self._devserver_ssh.run('rm -rf %s' % self._devserver_static_dir,
4343563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                                    ignore_status=True)
4353563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
436260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
4373563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa    def _create_tempfile_on_devserver(self, label, dir=False):
4383563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        """Creates a temporary file/dir on the devserver and returns its path.
439260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
440260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @param label: Identifier for the file context (string, no whitespaces).
4413563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        @param dir: If True, create a directory instead of a file.
442260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
443260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @raises test.TestError: If we failed to invoke mktemp on the server.
444260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        @raises OmahaDevserverFailedToStart: If tempfile creation failed.
445260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """
446260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        remote_cmd = 'mktemp --tmpdir devserver-%s.XXXXXX' % label
4473563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        if dir:
4483563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa            remote_cmd += ' --directory'
4493563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
450260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        try:
451260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            result = self._devserver_ssh.run(remote_cmd, ignore_status=True)
452260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        except error.AutoservRunError as e:
453260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            self._log_and_raise_remote_ssh_error(e)
454260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        if result.exit_status != 0:
455260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            raise OmahaDevserverFailedToStart(
456260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'Could not create a temporary %s file on the devserver, '
457a7412a90ffefac2b6314726b284af1b31d6bd797Alex Deymo                    'error output: "%s"' % (label, result.stderr))
458260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return result.stdout.strip()
459260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
460260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    @staticmethod
461260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _log_and_raise_remote_ssh_error(e):
462260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Logs failure to ssh remote, then raises a TestError."""
463260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.debug('Failed to ssh into the devserver: %s', e)
464260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.error('If you are running this locally it means you did not '
465260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                      'configure ssh correctly.')
466260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        raise error.TestError('Failed to ssh into the devserver: %s' % e)
467260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
468260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
469260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold    def _read_int_from_devserver_file(self, filename):
470260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        """Reads and returns an integer value from a file on the devserver."""
471260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        return int(self._get_devserver_file_content(filename).strip())
4720ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
4736c55bdb98e967675456a71a0971b81058536cac8Chris Sosa
474793344b359304c31d78197788b55cfbbe2636025Chris Sosa    def _wait_for_devserver_to_start(self):
475793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """Waits until the devserver starts within the time limit.
476793344b359304c31d78197788b55cfbbe2636025Chris Sosa
477260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        Infers and sets the devserver PID and serving port.
478260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
479793344b359304c31d78197788b55cfbbe2636025Chris Sosa        Raises:
480793344b359304c31d78197788b55cfbbe2636025Chris Sosa            OmahaDevserverFailedToStart: If the time limit is reached and we
481793344b359304c31d78197788b55cfbbe2636025Chris Sosa                                         cannot connect to the devserver.
482793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """
483260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # Compute the overall timeout.
484260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        deadline = time.time() + self._WAIT_FOR_DEVSERVER_STARTED_SECONDS
485260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
486260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # First, wait for port file to be filled and determine the server port.
48794b9ad4983cc735e162a3549c1628d08241b0d57Gilad Arnold        logging.warning('Waiting for devserver to start up.')
488260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        while time.time() < deadline:
489260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            try:
490260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                self._devserver_pid = self._read_int_from_devserver_file(
491260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        self._devserver_pidfile)
492260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                self._devserver_port = self._read_int_from_devserver_file(
493260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        self._devserver_portfile)
494260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                logging.info('Devserver pid is %d, serving on port %d',
495260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                             self._devserver_pid, self._devserver_port)
496260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
497260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            except Exception:  # Couldn't read file or corrupt content.
498260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                time.sleep(self._WAIT_SLEEP_INTERVAL)
499260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        else:
50052c35724507ec105053d2af792fe161a627e05c1Alex Deymo            try:
50152c35724507ec105053d2af792fe161a627e05c1Alex Deymo                self._devserver_ssh.run_output('uptime')
50252c35724507ec105053d2af792fe161a627e05c1Alex Deymo            except error.AutoservRunError as e:
50352c35724507ec105053d2af792fe161a627e05c1Alex Deymo                logging.debug('Failed to run uptime on the devserver: %s', e)
50494b9ad4983cc735e162a3549c1628d08241b0d57Gilad Arnold            raise OmahaDevserverFailedToStart(
505260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'The test failed to find the pid/port of the omaha '
50652c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    'devserver after %d seconds. Check the dumped devserver '
50752c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    'logs and devserver load for more information.' %
50852c35724507ec105053d2af792fe161a627e05c1Alex Deymo                    self._WAIT_FOR_DEVSERVER_STARTED_SECONDS)
509260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold
510260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        # Check that the server is reponsding to network requests.
511260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        logging.warning('Waiting for devserver to accept network requests.')
512260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        url = 'http://%s' % self.get_netloc()
513260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold        while time.time() < deadline:
514260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            if dev_server.DevServer.devserver_healthy(url, timeout_min=0.1):
515260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                break
516793344b359304c31d78197788b55cfbbe2636025Chris Sosa
517793344b359304c31d78197788b55cfbbe2636025Chris Sosa            # TODO(milleral): Refactor once crbug.com/221626 is resolved.
518793344b359304c31d78197788b55cfbbe2636025Chris Sosa            time.sleep(self._WAIT_SLEEP_INTERVAL)
519793344b359304c31d78197788b55cfbbe2636025Chris Sosa        else:
520793344b359304c31d78197788b55cfbbe2636025Chris Sosa            raise OmahaDevserverFailedToStart(
521793344b359304c31d78197788b55cfbbe2636025Chris Sosa                    'The test failed to establish a connection to the omaha '
522793344b359304c31d78197788b55cfbbe2636025Chris Sosa                    'devserver it set up on port %d. Check the dumped '
523260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    'devserver logs for more information.' %
524260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                    self._devserver_port)
525793344b359304c31d78197788b55cfbbe2636025Chris Sosa
526793344b359304c31d78197788b55cfbbe2636025Chris Sosa
5276c55bdb98e967675456a71a0971b81058536cac8Chris Sosa    def start_devserver(self):
528793344b359304c31d78197788b55cfbbe2636025Chris Sosa        """Starts the devserver and confirms it is up.
529793344b359304c31d78197788b55cfbbe2636025Chris Sosa
530793344b359304c31d78197788b55cfbbe2636025Chris Sosa        Raises:
531260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold            test.TestError: If we failed to spawn the remote devserver.
532793344b359304c31d78197788b55cfbbe2636025Chris Sosa            OmahaDevserverFailedToStart: If the time limit is reached and we
533793344b359304c31d78197788b55cfbbe2636025Chris Sosa                                         cannot connect to the devserver.
5346c55bdb98e967675456a71a0971b81058536cac8Chris Sosa        """
5352f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        update_payload_url_base, update_payload_path = self._split_url(
536ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                self._update_payload_staged_url)
5373563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
5383563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        # Allocate temporary files for various server outputs.
5393563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_logfile = self._create_tempfile_on_devserver('log')
540238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo        self._devserver_stdoutfile = self._create_tempfile_on_devserver(
541238cff65701d1ea7ea9458baa7148f5a76f9b183Alex Deymo                'stdout')
5423563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_portfile = self._create_tempfile_on_devserver('port')
5433563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_pidfile = self._create_tempfile_on_devserver('pid')
5443563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa        self._devserver_static_dir = self._create_tempfile_on_devserver(
5453563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                'static', dir=True)
5463563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa
5476f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # Invoke the Omaha/devserver on the remote server. Will attempt to kill
5486f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # it with a SIGTERM after a predetermined timeout has elapsed, followed
5496f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold        # by SIGKILL if not dead within 30 seconds from the former signal.
5500ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        cmdlist = [
5516f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold                'timeout', '-s', 'TERM', '-k', '30',
5526f36e954e1d73e961b8d47ebf4ec7d267c4307f4Gilad Arnold                str(self._DEVSERVER_TIMELIMIT_SECONDS),
5536c55bdb98e967675456a71a0971b81058536cac8Chris Sosa                '%s/devserver.py' % self._devserver_dir,
5540ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--payload=%s' % update_payload_path,
555260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--port=0',
556260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--pidfile=%s' % self._devserver_pidfile,
557260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--portfile=%s' % self._devserver_portfile,
558260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                '--logfile=%s' % self._devserver_logfile,
5590ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--remote_payload',
5600ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--urlbase=%s' % update_payload_url_base,
5610ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--max_updates=1',
5620ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold                '--host_log',
5633563042207623fbb9db02018156e8b5fa0e4c2c3Chris Sosa                '--static_dir=%s' % self._devserver_static_dir,
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()
711f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if os_type == 'cros':
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]
89515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        return self._get_stateful_uri(build_uri)
8962f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
8972f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
898f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _update_via_test_payloads(self, omaha_host, payload_url, stateful_url,
899f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                  clobber):
900cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """Given the following update and stateful urls, update the DUT.
901cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
902cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        Only updates the rootfs/stateful if the respective url is provided.
903cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
904cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param omaha_host: If updating rootfs, redirect updates through this
905cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            host. Should be None iff payload_url is None.
906cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param payload_url: If set, the specified url to find the update
907cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            payload.
908cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param stateful_url: If set, the specified url to find the stateful
909cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            payload.
910cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        @param clobber: If True, do a clean install of stateful.
911cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        """
912cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        def perform_update(url, is_stateful):
913cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """Perform a rootfs/stateful update using given URL.
914cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
915cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            @param url: URL to update from.
916cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            @param is_stateful: Whether this is a stateful or rootfs update.
917cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            """
918cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if url:
919cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                updater = autoupdater.ChromiumOSUpdater(url, host=self._host)
920cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                if is_stateful:
921cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                    updater.update_stateful(clobber=clobber)
922cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                else:
9239e30d201214e586d185b497dea9a13a7e8d0495eGilad Arnold                    updater.update_image()
924cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
925cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        # We create a OmahaDevserver to redirect blah.bin to update/. This
926cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        # allows us to use any payload filename to serve an update.
927cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        temp_devserver = None
928cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        try:
929cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if payload_url:
930cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                temp_devserver = OmahaDevserver(
931260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                        omaha_host, self._devserver_dir, payload_url)
932cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                temp_devserver.start_devserver()
933cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold                payload_url = temp_devserver.get_update_url()
934cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
935cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            stateful_url = self._payload_to_update_url(stateful_url)
936cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold
937cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            perform_update(payload_url, False)
938cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            perform_update(stateful_url, True)
939cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold        finally:
940cfa14a695654bc9cbfe9b9ce5868d669d0c57e0fGilad Arnold            if temp_devserver:
941260957d6079ee23ec6c9857a95fa183a78741b0bGilad Arnold                temp_devserver.stop_devserver()
9422f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9432f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
944f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _install_source_version(self, devserver_hostname, image_url,
945f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                stateful_url):
9462f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Prepare the specified host with the image given by the urls.
9472f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
948ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param devserver_hostname: If updating rootfs, redirect updates
949ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   through this host. Should be None iff
950ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                                   image_url is None.
9512f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param image_url: If set, the specified url to find the source image
9522f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                          or full payload for the source image.
9532f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param stateful_url: If set, the specified url to find the stateful
9542f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa                             payload.
9552f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """
9564cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        try:
9574cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # Reboot to get us into a clean state.
9584cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            self._host.reboot()
9594cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # Since we are installing the source image of the test, clobber
9604cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # stateful.
961f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._update_via_test_payloads(devserver_hostname, image_url,
96294694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                                           stateful_url, True)
9634cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            self._host.reboot()
9641b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold        except OmahaDevserverFailedToStart as e:
9651b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            logging.fatal('Failed to start private devserver for installing '
9661b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                          'the source image (%s) on the DUT', image_url)
9671b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            raise error.TestError(
9681b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                    'Failed to start private devserver for installing the '
9691b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                    'source image on the DUT: %s' % e)
9701b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold        except error.AutoservRunError as e:
9719b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.fatal('Error re-imaging or rebooting the DUT with the '
9729b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                          'source image from %s', image_url)
9731b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            raise error.TestError('Failed to install the source image or '
9741b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                                  'reboot the DUT: %s' % e)
975f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
976f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
977f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _stage_artifacts_onto_devserver(self, test_conf):
9782f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        """Stages artifacts that will be used by the test onto the devserver.
9792f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
9802f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        @param test_conf: a dictionary containing test configuration values
9812f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
982f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return a StagedURLs tuple containing the staged urls.
983f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        """
9840338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Staging images onto autotest devserver (%s)',
985f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     self._autotest_devserver.url())
986f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
98715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_source_url = None
98815384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        staged_source_stateful_url = None
9899cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold        try:
9909cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            source_payload_uri = test_conf['source_payload_uri']
9919cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold        except KeyError:
9929cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            # TODO(garnold) Remove legacy key support once control files on all
9939cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            # release branches have caught up.
9949cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold            source_payload_uri = test_conf['source_image_uri']
9954cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        if source_payload_uri:
99694694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold            staged_source_url = self._stage_payload_by_uri(source_payload_uri)
9974cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold
9984cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # In order to properly install the source image using a full
9994cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # payload we'll also need the stateful update that comes with it.
10004cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # In general, tests may have their source artifacts in a different
10014cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # location than their payloads. This is determined by whether or
10024cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # not the source_archive_uri attribute is set; if it isn't set,
10034cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # then we derive it from the dirname of the source payload.
10044cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            source_archive_uri = test_conf.get('source_archive_uri')
10054cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            if source_archive_uri:
10064cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                source_stateful_uri = self._get_stateful_uri(source_archive_uri)
10072f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa            else:
10084cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                source_stateful_uri = self._payload_to_stateful_uri(
10094cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                        source_payload_uri)
1010fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold
10114cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            staged_source_stateful_url = self._stage_payload_by_uri(
101294694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                    source_stateful_uri)
101315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
10144cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            # Log source image URLs.
10154cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            logging.info('Source full payload from %s staged at %s',
10164cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                         source_payload_uri, staged_source_url)
10174cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold            if staged_source_stateful_url:
10184cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                logging.info('Source stateful update from %s staged at %s',
10194cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold                             source_stateful_uri, staged_source_stateful_url)
102015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
102115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_payload_uri = test_conf['target_payload_uri']
102294694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold        staged_target_url = self._stage_payload_by_uri(target_payload_uri)
102315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_stateful_uri = None
10240e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold        staged_target_stateful_url = None
102515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        target_archive_uri = test_conf.get('target_archive_uri')
10260e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold        if target_archive_uri:
10270e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            target_stateful_uri = self._get_stateful_uri(target_archive_uri)
10282f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa        else:
10290e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            # Attempt to get the job_repo_url to find the stateful payload for
10300e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            # the target image.
10310e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            try:
10320e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                job_repo_url = self._host.lookup_job_repo_url()
10330e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            except KeyError:
10340e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                # If this failed, assume the stateful update is next to the
10350e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                # update payload.
103615384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                target_stateful_uri = self._payload_to_stateful_uri(
103715384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                    target_payload_uri)
10380e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold            else:
10390e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                _, devserver_label = tools.get_devserver_build_from_package_url(
10400e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                        job_repo_url)
10410e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold                staged_target_stateful_url = self._stage_payload(
104294694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                        devserver_label, self._STATEFUL_UPDATE_FILENAME)
1043f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
10440e18ad5f71d48432747733e10aad1e1cf5d93113Gilad Arnold        if not staged_target_stateful_url and target_stateful_uri:
104515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold            staged_target_stateful_url = self._stage_payload_by_uri(
104694694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                    target_stateful_uri)
10472f1ae9f1f2464ebbeb226a6669fa90ef211a179cChris Sosa
1048fea7e766e59f8356dad6f70e46c0ee25997863a5Gilad Arnold        # Log target payload URLs.
104915384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold        logging.info('%s test payload from %s staged at %s',
105015384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     test_conf['update_type'], target_payload_uri,
105115384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     staged_target_url)
10520338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Target stateful update from %s staged at %s',
105315384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     target_stateful_uri or 'standard location',
105415384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold                     staged_target_stateful_url)
105515384a4ded97f171906a72106791d19e96e68fa7Gilad Arnold
1056f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self.StagedURLs(staged_source_url, staged_source_stateful_url,
1057f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                               staged_target_url, staged_target_stateful_url)
1058f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1059f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1060c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen    def _run_login_test(self, release_string):
1061c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        """Runs login_LoginSuccess test if it is supported by the release."""
1062c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # Only do login tests with recent builds, since they depend on
1063c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # some binary compatibility with the build itself.
1064c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        # '5116.0.0' -> ('5116', '0', '0') -> 5116
10657ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        if not release_string:
10667ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold            logging.info('No release provided, skipping login test.')
10677ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        elif int(release_string.split('.')[0]) > self._LOGINABLE_MINIMUM_RELEASE:
1068c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            # Login, to prove we can before/after the update.
1069c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            logging.info('Attempting to login (release %s).', release_string)
1070c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1071c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            client_at = autotest.Autotest(self._host)
1072c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            client_at.run_test('login_LoginSuccess')
1073c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen        else:
1074c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen            logging.info('Not attempting login test because %s is older than '
1075c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen                         '%d.', release_string, self._LOGINABLE_MINIMUM_RELEASE)
1076c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1077c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1078f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _start_perf_mon(self, bindir):
107931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Starts monitoring performance and resource usage on a DUT.
108031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
108131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        Call _stop_perf_mon() with the returned PID to stop monitoring
108231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        and collect the results.
108331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1084f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param bindir: Directoy containing monitoring script.
1085f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1086f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return The PID of the newly created DUT monitoring process.
108731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
108831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # We can't assume much about the source image so we copy the
108931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # performance monitoring script to the DUT directly.
1090f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        path = os.path.join(bindir, 'update_engine_performance_monitor.py')
109131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        self._host.send_file(path, '/tmp')
109231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        cmd = 'python /tmp/update_engine_performance_monitor.py --start-bg'
109331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        return int(self._host.run(cmd).stdout)
109431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
109531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
109631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _stop_perf_mon(self, perf_mon_pid):
109731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Stops monitoring performance and resource usage on a DUT.
109831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
109931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        @param perf_mon_pid: the PID returned from _start_perf_mon().
110031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1101f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @return Dictionary containing performance attributes, or None if
1102f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                unavailable.
110331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
110431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # Gracefully handle problems with performance monitoring by
110531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # just returning None.
110631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        try:
110731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            cmd = ('python /tmp/update_engine_performance_monitor.py '
110831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                   '--stop-bg=%d') % perf_mon_pid
110931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            perf_json_txt = self._host.run(cmd).stdout
111031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            return json.loads(perf_json_txt)
111131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        except Exception as e:
111231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.warning('Failed to parse output from '
111331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                            'update_engine_performance_monitor.py: %s', e)
111431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        return None
111531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
111631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1117f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # Interface overrides.
1118f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    #
1119f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def initialize(self, autotest_devserver, devserver_dir):
1120f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._autotest_devserver = autotest_devserver
1121f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._devserver_dir = devserver_dir
1122f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1123f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1124f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def reboot_device(self):
1125f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._host.reboot()
1126f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1127f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1128f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def prep_artifacts(self, test_conf):
1129f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._staged_urls = self._stage_artifacts_onto_devserver(test_conf)
1130f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self._staged_urls
1131f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1132f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1133f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def prep_device_for_update(self, source_release):
1134f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Install the source version onto the DUT.
1135f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._staged_urls.source_url:
11369b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.info('Installing a source image on the DUT')
1137f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            devserver_hostname = urlparse.urlparse(
1138f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                    self._autotest_devserver.url()).hostname
1139f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._install_source_version(devserver_hostname,
1140f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                         self._staged_urls.source_url,
1141f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                                         self._staged_urls.source_stateful_url)
1142f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1143f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Make sure we can login before the update.
1144f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._run_login_test(source_release)
1145f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1146f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1147f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def get_active_slot(self):
1148f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self._host.run('rootdev -s').stdout.strip()
1149f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1150f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1151f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def start_update_perf(self, bindir):
1152f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._perf_mon_pid is None:
1153f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._perf_mon_pid = self._start_perf_mon(bindir)
1154f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1155f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1156f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def stop_update_perf(self):
1157f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        perf_data = None
1158f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._perf_mon_pid is not None:
1159f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            perf_data = self._stop_perf_mon(self._perf_mon_pid)
1160f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._perf_mon_pid = None
1161f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1162f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return perf_data
1163f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1164f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1165f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def trigger_update(self, omaha_devserver):
1166f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        updater = autoupdater.ChromiumOSUpdater(
1167f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                omaha_devserver.get_update_url(), host=self._host)
1168f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        updater.trigger_update()
1169f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1170f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1171f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def finalize_update(self):
1172f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._update_via_test_payloads(
117394694ec92c9e2c4d316aed34453ef75c21444c61Gilad Arnold                None, None, self._staged_urls.target_stateful_url, False)
1174f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1175f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1176f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def get_update_log(self, num_lines):
1177f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        return self._host.run_output(
1178fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                'tail -n %d /var/log/update_engine.log' % num_lines,
1179fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                stdout_tee=None)
1180f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1181f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1182f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def check_device_after_update(self, target_release):
1183f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Make sure we can login after update.
1184f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._run_login_test(target_release)
1185f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1186f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1187b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnoldclass BrilloTestPlatform(TestPlatform):
1188b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    """A TestPlatform implementation for Brillo."""
1189b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1190b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    _URL_DEFAULT_PORT = 80
1191b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    _DUT_LOCALHOST = '127.0.0.1'
1192b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1193b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def __init__(self, host):
1194b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        self._host = host
11959b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._autotest_devserver = None
11969b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._devserver_dir = None
11979b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._staged_urls = None
1198b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        self._forwarding_ports = set()
1199b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1200b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1201b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    @classmethod
1202b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _get_host_port(cls, url):
1203b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Returns the host and port values from a given URL.
1204b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1205b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param url: The URL from which the values are extracted.
1206b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1207b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @return A pair consisting of the host and port strings.
1208b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        """
1209b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        host, _, port = urlparse.urlsplit(url).netloc.partition(':')
1210b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        return host, port or str(cls._URL_DEFAULT_PORT)
1211b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1212b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1213b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _install_rev_forwarding(self, port=None):
1214b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Installs reverse forwarding rules via ADB.
12159b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
1216b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param port: The TCP port we want forwarded; if None, installs all
1217b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                     previously configured ports.
1218b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """
1219b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        ports = self._forwarding_ports if port is None else [port]
1220b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        for port in ports:
1221b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            port_spec = 'tcp:%s' % port
1222b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            self._host.add_forwarding(port_spec, port_spec, reverse=True)
1223b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1224b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1225b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _add_rev_forwarding(self, url):
1226b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Configures reverse port forwarding and adjusts the given URL.
1227b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1228b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        This extracts the port from the URL, adds it to the set of configured
1229b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        forwarding ports, installs it to the DUT, then returns the adjusted URL
1230b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        for use by the DUT.
1231b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1232b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param url: The URL for which we need to establish forwarding.
1233b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1234b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @return: The adjusted URL for use on the DUT.
12359b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        """
12369b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        if url:
1237b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            host, port = self._get_host_port(url)
1238b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            if port not in self._forwarding_ports:
1239b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._forwarding_ports.add(port)
1240b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._install_rev_forwarding(port=port)
1241b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            url = url.replace(host, self._DUT_LOCALHOST, 1)
1242b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        return url
12439b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
12449b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
1245b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold    def _remove_rev_forwarding(self, url=None):
1246b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """Removes a reverse port forwarding.
1247b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1248b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        @param url: The URL for which forwarding was established; if None,
1249b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                    removes all previously configured ports.
1250b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        """
1251b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        ports = set()
1252b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        if url is None:
1253b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            ports.update(self._forwarding_ports)
1254b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        else:
1255b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            _, port = self._get_host_port(url)
1256b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            if port in self._forwarding_ports:
1257b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                ports.add(port)
1258b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1259b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # TODO(garnold) Enable once ADB port removal is fixed (b/24771474):
1260b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # for port in ports:
1261b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        #     self._host.remove_forwarding(src='tcp:%s' % port, reverse=True)
1262b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold
1263b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        self._forwarding_ports.difference_update(ports)
1264b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1265b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
12669b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold    def _install_source_version(self, devserver_hostname, payload_url):
12679b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        """Installs a source version onto the test device.
12689b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
12699b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        @param devserver_hostname: Redirect updates through this host.
12709b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        @param payload_url: URL of staged payload for installing a source image.
12719b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        """
12729b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        try:
12739b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            # Start a private Omaha server and update the DUT.
12749b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            temp_devserver = None
1275b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold            url = None
12769b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            try:
12779b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                temp_devserver = OmahaDevserver(
12789b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                        devserver_hostname, self._devserver_dir, payload_url)
12799b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                temp_devserver.start_devserver()
1280b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                url = self._add_rev_forwarding(temp_devserver.get_update_url())
12819b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                updater = autoupdater.BrilloUpdater(url, host=self._host)
12829b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                updater.update_image()
12839b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            finally:
1284b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                if url:
1285b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                    self._remove_rev_forwarding(url)
12869b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                if temp_devserver:
12879b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    temp_devserver.stop_devserver()
12889b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
12899b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            # Reboot the DUT.
12909b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            self.reboot_device()
12919b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        except OmahaDevserverFailedToStart as e:
12929b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.fatal('Failed to start private devserver for installing '
12939b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                          'the source payload (%s) on the DUT', payload_url)
12949b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            raise error.TestError(
12959b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    'Failed to start private devserver for installing the '
12969b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    'source image on the DUT: %s' % e)
12979b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        except error.AutoservRunError as e:
12989b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.fatal('Error re-imaging or rebooting the DUT with the '
12999b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                          'source image from %s', payload_url)
13009b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            raise error.TestError('Failed to install the source image or '
13019b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                                  'reboot the DUT: %s' % e)
13029b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
13039b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold
1304b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    # Interface overrides.
1305b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    #
1306b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def initialize(self, autotest_devserver, devserver_dir):
13079b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._autotest_devserver = autotest_devserver
13089b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._devserver_dir = devserver_dir
1309b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1310b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1311b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def reboot_device(self):
1312b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        self._host.reboot()
1313b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        self._install_rev_forwarding()
1314b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1315b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1316b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def prep_artifacts(self, test_conf):
1317b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        # TODO(garnold) Currently we don't stage anything and assume that the
1318b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        # provided URLs are of pre-staged payloads. We should implement staging
1319b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        # support once a release scheme for Brillo is finalized.
13209b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        self._staged_urls = self.StagedURLs(
1321b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._add_rev_forwarding(test_conf['source_payload_uri']), None,
1322b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold                self._add_rev_forwarding(test_conf['target_payload_uri']), None)
13239b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        return self._staged_urls
1324b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1325b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1326b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def prep_device_for_update(self, source_release):
13279b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        # Install the source version onto the DUT.
13289b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold        if self._staged_urls.source_url:
13299b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            logging.info('Installing a source image on the DUT')
13309b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            devserver_hostname = urlparse.urlparse(
13319b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                    self._autotest_devserver.url()).hostname
13329b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold            self._install_source_version(devserver_hostname,
13339b976d77f77c94733ab711c15db4e08528926d46Gilad Arnold                                         self._staged_urls.source_url)
1334b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1335b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1336b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def get_active_slot(self):
1337b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        return self._host.run('rootdev -s /system').stdout.strip()
1338b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1339b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1340b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def start_update_perf(self, bindir):
1341b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        pass
1342b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1343b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1344b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def stop_update_perf(self):
1345b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        pass
1346b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1347b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1348b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def trigger_update(self, omaha_devserver):
1349b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        url = self._add_rev_forwarding(omaha_devserver.get_update_url())
1350b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        updater = autoupdater.BrilloUpdater(url, host=self._host)
1351b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        updater.trigger_update()
1352b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1353b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1354b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def finalize_update(self):
1355b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        pass
1356b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1357b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1358b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def get_update_log(self, num_lines):
1359b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold        return self._host.run_output(
1360fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                'logcat -d -s update_engine | tail -n %d' % num_lines,
1361fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                stdout_tee=None)
1362b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1363b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1364b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold    def check_device_after_update(self, target_release):
1365b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        self._remove_rev_forwarding()
1366b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # TODO(garnold) Port forwarding removal is broken in ADB (b/24771474).
1367b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # Instead we reboot the device, which has the side-effect of flushing
1368b90740522d88ed5770e512502aaa396710a54b1dGilad Arnold        # all forwarding rules. Once fixed, the following should be removed.
136903322d32f30c95159e0651709a87132fa8b12bb4Gilad Arnold        self.reboot_device()
1370b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1371b7ebf3322b6e63e0286ae27ed2c6d3b79655ff20Gilad Arnold
1372f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnoldclass autoupdate_EndToEndTest(test.test):
1373f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    """Complete update test between two Chrome OS releases.
1374f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1375f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    Performs an end-to-end test of updating a ChromeOS device from one version
1376f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    to another. The test performs the following steps:
1377f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1378f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      1. Stages the source (full) and target update payload on the central
1379f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold         devserver.
1380f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      2. Spawns a private Omaha-like devserver instance, configured to return
1381f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold         the target (update) payload URL in response for an update check.
1382f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      3. Reboots the DUT.
1383f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      4. Installs a source image on the DUT (if provided) and reboots to it.
1384f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      5. Triggers an update check at the DUT.
1385f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      6. Watches as the DUT obtains an update and applies it.
1386f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      7. Reboots and repeats steps 5-6, ensuring that the next update check
1387f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold         shows the new image version.
1388f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1389f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    Some notes on naming:
1390f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      devserver: Refers to a machine running the Chrome OS Update Devserver.
1391f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      autotest_devserver: An autotest wrapper to interact with a devserver.
1392f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          Can be used to stage artifacts to a devserver. While
1393f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          this can also be used to update a machine, we do not
1394f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          use it for that purpose in this test as we manage
1395f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          updates with out own devserver instances (see below).
1396f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      omaha_devserver: This test's wrapper of a devserver running for the
1397f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       purposes of emulating omaha. This test controls the
1398f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       lifetime of this devserver instance and is separate
1399f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       from the autotest lab's devserver's instances which are
1400f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       only used for staging and hosting artifacts (because they
1401f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       scale). These are run on the same machines as the actual
1402f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       autotest devservers which are used for staging but on
1403f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                       different ports.
1404f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      *staged_url's: In this case staged refers to the fact that these items
1405f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     are available to be downloaded statically from these urls
1406f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     e.g. 'localhost:8080/static/my_file.gz'. These are usually
1407f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     given after staging an artifact using a autotest_devserver
1408f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     though they can be re-created given enough assumptions.
1409f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      *update_url's: Urls refering to the update RPC on a given omaha devserver.
1410f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     Since we always use an instantiated omaha devserver to run
1411f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     updates, these will always reference an existing instance
1412f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     of an omaha devserver that we just created for the purposes
1413f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     of updating.
1414f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold      devserver_hostname: At the start of each test, we choose a devserver
1415f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          machine in the lab for the test. We use the devserver
1416f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          instance there (access by autotest_devserver) to stage
1417f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          artifacts. However, we also use the same host to start
1418f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          omaha devserver instances for updating machines with
1419f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          (that reference the staged paylaods on the autotest
1420f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          devserver instance). This hostname refers to that
1421f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          machine we are using (since it's always the same for
1422f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                          both staging/omaha'ing).
1423f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1424f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    """
1425f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    version = 1
1426f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1427f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # Timeout periods, given in seconds.
1428f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_AFTER_SHUTDOWN_SECONDS = 10
1429f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_AFTER_UPDATE_SECONDS = 20
1430f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_USB_INSTALL_SECONDS = 4 * 60
1431f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_MP_RECOVERY_SECONDS = 8 * 60
1432f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS = 12 * 60
1433f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # TODO(sosa): Investigate why this needs to be so long (this used to be
1434f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    # 120 and regressed).
1435f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_DOWNLOAD_STARTED_SECONDS = 4 * 60
1436f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS = 10 * 60
1437f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_UPDATE_COMPLETED_SECONDS = 4 * 60
1438f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS = 15 * 60
1439f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    _DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS = 30
1440f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1441a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    # Logs and their whereabouts.
1442a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _WHERE_UPDATE_LOG = ('update_engine log (in sysinfo or on the DUT, also '
1443a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                         'included in the test log)')
1444a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    _WHERE_OMAHA_LOG = 'Omaha-devserver log (included in the test log)'
1445a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1446f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1447f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def initialize(self):
1448f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Sets up variables that will be used by test."""
1449f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._host = None
1450f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._omaha_devserver = None
1451a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        self._source_image_installed = False
1452f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1453f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._devserver_dir = global_config.global_config.get_config_value(
1454f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                'CROS', 'devserver_dir', default=None)
1455f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._devserver_dir is None:
1456f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            raise error.TestError(
1457f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                    'Path to devserver source tree not provided; please define '
1458f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                    'devserver_dir under [CROS] in your shadow_config.ini')
1459f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1460f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1461f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def cleanup(self):
1462f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Kill the omaha devserver if it's still around."""
1463f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if self._omaha_devserver:
1464f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._omaha_devserver.stop_devserver()
1465f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1466f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        self._omaha_devserver = None
1467f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1468f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1469f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def _dump_update_engine_log(self, test_platform):
1470f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        """Dumps relevant AU error log."""
1471f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        try:
1472fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold            error_log = test_platform.get_update_log(80)
1473fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold            logging.error('Dumping snippet of update_engine log:\n%s',
1474fffb6d7274294d9a57523fb7535dd627d0378e5fGilad Arnold                          snippet(error_log))
1475f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        except Exception:
1476f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            # Mute any exceptions we get printing debug logs.
1477f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            pass
1478f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1479f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
148031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen    def _report_perf_data(self, perf_data):
148131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """Reports performance and resource data.
148231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1483f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        Currently, performance attributes are expected to include 'rss_peak'
1484f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        (peak memory usage in bytes).
1485f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1486f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param perf_data: A dictionary containing performance attributes.
148731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        """
148831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        rss_peak = perf_data.get('rss_peak')
148931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        if rss_peak:
149031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            rss_peak_kib = rss_peak / 1024
149131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.info('Peak memory (RSS) usage on DUT: %d KiB', rss_peak_kib)
149231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            self.output_perf_value(description='mem_usage_peak',
149331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   value=int(rss_peak_kib),
149431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   units='KiB',
149531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                                   higher_is_better=False)
149631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        else:
149731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.warning('No rss_peak key in JSON returned by '
149831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                            'update_engine_performance_monitor.py')
149931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
150031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1501a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_initial_check(self, expected, actual, mismatched_attrs):
1502a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'version' in mismatched_attrs:
1503a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            err_msg = ('Initial update check was received but the reported '
1504a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                       'version is different from what was expected.')
1505a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            if self._source_image_installed:
1506a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                err_msg += (' The source payload we installed was probably '
1507a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'incorrect or corrupt.')
1508a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            else:
1509a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                err_msg += (' The DUT is probably not running the correct '
1510a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'source image.')
1511a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return err_msg
1512a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1513a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return 'A test bug occurred; inspect the test log.'
1514a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1515a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1516a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_intermediate(self, expected, actual, mismatched_attrs, action,
1517a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            problem):
1518a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_result' in mismatched_attrs:
15190ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_result = actual.get('event_result')
15200ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = (('different than expected (%s)' %
15210ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                         EVENT_RESULT_DICT[event_result])
15220ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_result else 'missing')
15230ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            return ('The updater reported result code is %s. This could be an '
15240ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'updater bug or a connectivity problem; check the %s. For '
15250ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'a detailed log of update events, check the %s.' %
15260ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (reported, self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1527a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_type' in mismatched_attrs:
15280ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_type = actual.get('event_type')
15290ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = ('different (%s)' % EVENT_TYPE_DICT[event_type]
15300ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_type else 'missing')
15310ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            return ('Expected the updater to %s (%s) but received event type '
15320ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'is %s. This could be an updater %s; check the '
1533a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    '%s. For a detailed log of update events, check the %s.' %
15340ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (action, EVENT_TYPE_DICT[expected['event_type']], reported,
15350ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                     problem, self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1536a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'version' in mismatched_attrs:
1537a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The updater reported an unexpected version despite '
1538a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'previously reporting the correct one. This is most likely '
1539a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'a bug in update engine; check the %s.' %
1540a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WHERE_UPDATE_LOG)
1541a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1542a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return 'A test bug occurred; inspect the test log.'
1543a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1544a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1545a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_download_started(self, expected, actual, mismatched_attrs):
1546a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return self._error_intermediate(expected, actual, mismatched_attrs,
1547a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'begin downloading',
1548a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'bug, crash or provisioning error')
1549a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1550a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1551a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_download_finished(self, expected, actual, mismatched_attrs):
1552a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return self._error_intermediate(expected, actual, mismatched_attrs,
1553a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'finish downloading', 'bug or crash')
1554a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1555a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1556a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _error_update_complete(self, expected, actual, mismatched_attrs):
1557a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return self._error_intermediate(expected, actual, mismatched_attrs,
1558a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                                        'complete the update', 'bug or crash')
1559a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1560a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
156116e76893c34739649a751f42b3881b57620d665cGilad Arnold    def _error_reboot_after_update(self, expected, actual, mismatched_attrs):
1562a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_result' in mismatched_attrs:
15630ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_result = actual.get('event_result')
15640ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = ('different (%s)' % EVENT_RESULT_DICT[event_result]
15650ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_result else 'missing')
1566a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The updater was expected to reboot (%s) but reported '
15670ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'result code is %s. This could be a failure to reboot, an '
15680ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'updater bug or a connectivity problem; check the %s and '
15690ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'the system log. For a detailed log of update events, '
15700ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'check the %s.' %
15710ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (EVENT_RESULT_DICT[expected['event_result']], reported,
1572a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                     self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1573a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'event_type' in mismatched_attrs:
15740ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            event_type = actual.get('event_type')
15750ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold            reported = ('different (%s)' % EVENT_TYPE_DICT[event_type]
15760ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                        if event_type else 'missing')
1577a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('Expected to successfully reboot into the new image (%s) '
15780ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'but received event type is %s. This probably means that '
15790ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'the new image failed to verify after reboot, possibly '
15800ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'because the payload is corrupt. This might also be an '
15810ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'updater bug or crash; check the %s. For a detailed log of '
15820ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    'update events, check the %s.' %
15830ba4fcd882643427524c35522bf77959b57c6f48Gilad Arnold                    (EVENT_TYPE_DICT[expected['event_type']], reported,
1584a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                     self._WHERE_UPDATE_LOG, self._WHERE_OMAHA_LOG))
1585a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'version' in mismatched_attrs:
1586a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The DUT rebooted after the update but reports a different '
1587a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'image version than the one expected. This probably means '
1588a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'that the payload we applied was incorrect or corrupt.')
1589a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if 'previous_version' in mismatched_attrs:
1590a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            return ('The DUT rebooted after the update and reports the '
1591a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'expected version. However, it reports a previous version '
1592a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'that is different from the one previously reported. This '
1593a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    'is most likely a bug in update engine; check the %s.' %
1594a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WHERE_UPDATE_LOG)
1595a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1596a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return 'A test bug occurred; inspect the test log.'
1597a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1598a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1599a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold    def _timeout_err(self, desc, timeout, event_type=None):
1600a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        if event_type is not None:
1601a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold            desc += ' (%s)' % EVENT_TYPE_DICT[event_type]
1602a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        return ('Failed to receive %s within %d seconds. This could be a '
1603a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                'problem with the updater or a connectivity issue. For more '
1604a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                'details, check the %s.' %
1605a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                (desc, timeout, self._WHERE_UPDATE_LOG))
1606a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1607a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold
1608f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold    def run_update_test(self, test_platform, test_conf):
1609ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Runs the actual update test once preconditions are met.
16100ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1611f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        @param test_platform: TestPlatform implementation.
1612ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param test_conf: A dictionary containing test configuration values
16130ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1614ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raises ExpectedUpdateEventChainFailed if we failed to verify an update
1615ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                event.
16160ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold        """
1617c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1618f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Record the active root partition.
1619f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        source_active_slot = test_platform.get_active_slot()
1620f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        logging.info('Source active slot: %s', source_active_slot)
16210ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
16227ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        source_release = test_conf['source_release']
16237ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold        target_release = test_conf['target_release']
16247ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold
162531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        # Start the performance monitoring process on the DUT.
1626f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.start_update_perf(self.bindir)
162731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        try:
162831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Trigger an update.
1629f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            test_platform.trigger_update(self._omaha_devserver)
16300ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
163131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Track update progress.
163231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            omaha_netloc = self._omaha_devserver.get_netloc()
163331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            omaha_hostlog_url = urlparse.urlunsplit(
163431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    ['http', omaha_netloc, '/api/hostlog',
163531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                     'ip=' + self._host.ip, ''])
163631887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            logging.info('Polling update progress from omaha/devserver: %s',
163731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                         omaha_hostlog_url)
163831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            log_verifier = UpdateEventLogVerifier(
163931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    omaha_hostlog_url,
164031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                    self._DEVSERVER_HOSTLOG_REQUEST_TIMEOUT_SECONDS)
164131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
164231887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Verify chain of events in a successful update process.
1643248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain = ExpectedUpdateEventChain()
1644248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1645248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
16467ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1647a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_initial_check),
1648a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS,
1649a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1650a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'an initial update check',
1651a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_INITIAL_UPDATE_CHECK_SECONDS))
1652248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1653248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
1654248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_type=EVENT_TYPE_DOWNLOAD_STARTED,
1655248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_result=EVENT_RESULT_SUCCESS,
16567ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1657a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_download_started),
1658a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_DOWNLOAD_STARTED_SECONDS,
1659a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1660a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'a download started notification',
1661a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_DOWNLOAD_STARTED_SECONDS,
1662a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            event_type=EVENT_TYPE_DOWNLOAD_STARTED))
1663248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1664248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
1665248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_type=EVENT_TYPE_DOWNLOAD_FINISHED,
1666248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_result=EVENT_RESULT_SUCCESS,
16677ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1668a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_download_finished),
1669a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS,
1670a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1671a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'a download finished notification',
1672a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_DOWNLOAD_COMPLETED_SECONDS,
1673a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            event_type=EVENT_TYPE_DOWNLOAD_FINISHED))
1674248108c1c925791d926105f42212b1033213a4dcGilad Arnold            chain.add_event(
1675248108c1c925791d926105f42212b1033213a4dcGilad Arnold                    ExpectedUpdateEvent(
1676248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_type=EVENT_TYPE_UPDATE_COMPLETE,
1677248108c1c925791d926105f42212b1033213a4dcGilad Arnold                        event_result=EVENT_RESULT_SUCCESS,
16787ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                        version=source_release,
1679a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        on_error=self._error_update_complete),
1680a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    self._WAIT_FOR_UPDATE_COMPLETED_SECONDS,
1681a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                    on_timeout=self._timeout_err(
1682a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            'an update complete notification',
1683a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            self._WAIT_FOR_UPDATE_COMPLETED_SECONDS,
1684a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                            event_type=EVENT_TYPE_UPDATE_COMPLETE))
168531887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
1686fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            log_verifier.verify_expected_events_chain(chain)
168731887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen
168831887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Wait after an update completion (safety margin).
168931887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            _wait(self._WAIT_AFTER_UPDATE_SECONDS, 'after update completion')
169031887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen        finally:
169131887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            # Terminate perf monitoring process and collect its output.
1692f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            perf_data = test_platform.stop_update_perf()
169331887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen            if perf_data:
169431887b7a4ea7e1517d468c2ccf94df14ba6d0315David Zeuthen                self._report_perf_data(perf_data)
1695f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
16964cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        # Only update the stateful partition (the test updated the rootfs).
1697f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.finalize_update()
16984cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold
1699f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Reboot the DUT after the update.
1700bacc35bdb2fb44b0a760c05d364854cd92fb3f98Gilad Arnold        test_platform.reboot_device()
17010ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1702f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Trigger a second update check (again, test vs MP).
1703f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.trigger_update(self._omaha_devserver)
17040ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1705f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # Observe post-reboot update check, which should indicate that the
1706f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa        # image version has been updated.
1707248108c1c925791d926105f42212b1033213a4dcGilad Arnold        chain = ExpectedUpdateEventChain()
1708fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        expected_events = [
1709fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            ExpectedUpdateEvent(
1710fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_type=EVENT_TYPE_UPDATE_COMPLETE,
1711fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_result=EVENT_RESULT_SUCCESS_REBOOT,
1712fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                version=target_release,
1713fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                previous_version=source_release,
1714fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                on_error=self._error_reboot_after_update),
1715fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            # Newer versions send a "rebooted_after_update" message after reboot
1716fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            # with the previous version instead of another "update_complete".
1717fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo            ExpectedUpdateEvent(
1718fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_type=EVENT_TYPE_REBOOTED_AFTER_UPDATE,
1719fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                event_result=EVENT_RESULT_SUCCESS,
1720fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                version=target_release,
1721fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                previous_version=source_release,
1722fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                on_error=self._error_reboot_after_update),
1723fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        ]
1724248108c1c925791d926105f42212b1033213a4dcGilad Arnold        chain.add_event(
1725fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo                expected_events,
1726a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                self._WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS,
1727a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                on_timeout=self._timeout_err(
1728a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        'a successful reboot notification',
1729a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        self._WAIT_FOR_UPDATE_CHECK_AFTER_REBOOT_SECONDS,
1730a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold                        event_type=EVENT_TYPE_UPDATE_COMPLETE))
1731ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1732fac9a5d238337da91939328386c2fa4fdfb6d957Alex Deymo        log_verifier.verify_expected_events_chain(chain)
1733f4fa49b09cfa64e039355f78df2c34a9acca0c0cChris Sosa
1734f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Make sure we're using a different slot after the update.
1735f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        target_active_slot = test_platform.get_active_slot()
1736f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        if target_active_slot == source_active_slot:
17371b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            err_msg = 'The active image slot did not change after the update.'
17387ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold            if None in (source_release, target_release):
17397ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                err_msg += (' The DUT likely rebooted into the old image, which '
17407ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                            'probably means that the payload we applied was '
17417ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                            'corrupt. But since we did not check the source '
17427ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold                            'and/or target version we cannot say for sure.')
17437ac0a621527b1ecb4b82fea910c730682f9c1f76Gilad Arnold            elif source_release == target_release:
17441b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                err_msg += (' Given that the source and target versions are '
17451b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'identical, the DUT likely rebooted into the old '
17461b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'image. This probably means that the payload we '
17471b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'applied was corrupt.')
17481b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            else:
17491b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                err_msg += (' This is strange since the DUT reported the '
17501b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'correct target version. This is probably a system '
17511b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold                            'bug; check the DUT system log.')
17521b4da911c86c446bef8d0d5cb63dcf605ce6d289Gilad Arnold            raise error.TestFail(err_msg)
17530338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold
1754f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        logging.info('Target active slot changed as expected: %s',
1755f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                     target_active_slot)
1756eb300ac8af429e51f751b95ce375636fbb649e37Gilad Arnold
17570338ff3b754d52b41ca2d0432164a757daa1112dGilad Arnold        logging.info('Update successful, test completed')
17580ed760cbee4ce839988585003d445465bb0b95b9Gilad Arnold
1759ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
17609cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold    # TODO(garnold) Remove the use_servo argument once control files on all
17619cd891a18dcdeb1dd5f544322dd64f8f1845c6a6Gilad Arnold    # release branches have caught up.
17624cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold    def run_once(self, host, test_conf, use_servo=False):
1763ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """Performs a complete auto update test.
1764ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1765ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param host: a host object representing the DUT
1766ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @param test_conf: a dictionary containing test configuration values
17674cec366fff66e4603d9851c76680a6bb3f10fbe6Gilad Arnold        @param use_servo: DEPRECATED
1768ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1769ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        @raise error.TestError if anything went wrong with setting up the test;
1770ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa               error.TestFail if any part of the test has failed.
1771ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1772ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        """
1773f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1774ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._host = host
1775ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1776009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # Find a devserver to use. We first try to pick a devserver with the
1777009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # least load. In case all devservers' load are higher than threshold,
1778009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # fall back to the old behavior by picking a devserver based on the
1779009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # payload URI, with which ImageServer.resolve will return a random
1780009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi        # devserver based on the hash of the URI.
17814e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        least_loaded_devserver = dev_server.get_least_loaded_devserver()
17824e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        if least_loaded_devserver:
17834e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi            logging.debug('Choose the least loaded devserver: %s',
17844e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi                          least_loaded_devserver)
17854e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi            autotest_devserver = dev_server.ImageServer(least_loaded_devserver)
17864e2153dd994a088d92e2a4c1cbee9043e08f9fe7Dan Shi        else:
1787009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi            logging.warning('No devserver meets the maximum load requirement. '
1788009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi                            'Pick a random devserver to use.')
1789009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi            autotest_devserver = dev_server.ImageServer.resolve(
1790009ab000fbeeec0e0be9a2d6e744107a2b82306bDan Shi                    test_conf['target_payload_uri'])
1791ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        devserver_hostname = urlparse.urlparse(
1792ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa                autotest_devserver.url()).hostname
1793d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        counter_key = dev_server.ImageServer.create_stats_str(
1794d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi                'paygen', devserver_hostname, artifacts=None)
1795d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        metadata = {'devserver': devserver_hostname,
1796d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi                    '_type': 'devserver_paygen'}
1797d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        metadata.update(test_conf)
1798d874a4d63dacfb6e8a74466eab5160e940aaa690Dan Shi        autotest_stats.Counter(counter_key, metadata=metadata).increment()
1799ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1800f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Obtain a test platform implementation.
1801f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform = TestPlatform.create(host)
1802f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.initialize(autotest_devserver, self._devserver_dir)
1803f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold
1804ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        # Stage source images and update payloads onto a devserver.
1805f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        staged_urls = test_platform.prep_artifacts(test_conf)
1806a0ca5707ed10a6575ed290f341294331455f7769Gilad Arnold        self._source_image_installed = bool(staged_urls.source_url)
1807ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1808f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        # Prepare the DUT (install source version etc).
1809f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.prep_device_for_update(test_conf['source_release'])
1810ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa
1811ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._omaha_devserver = OmahaDevserver(
1812f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                devserver_hostname, self._devserver_dir,
1813f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold                staged_urls.target_url)
1814ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        self._omaha_devserver.start_devserver()
1815c6ed2f633a1c4609a4b212da66b3e506008ee3a7Nam T. Nguyen
1816ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        try:
1817f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self.run_update_test(test_platform, test_conf)
1818ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa        except ExpectedUpdateEventChainFailed:
1819f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold            self._dump_update_engine_log(test_platform)
1820ed8c1858bc32b066e4fbe4100a9ad49bdf1adc61Chris Sosa            raise
1821f789cf3a52c720344062f0a6c782bb758f08b189Don Garrett
1822f1d1104c736f79145209c76a147c1334f8dbb6a9Gilad Arnold        test_platform.check_device_after_update(test_conf['target_release'])
1823