blob: c1cca8c46f5c1581550323593dbe60c4e6d9a321 [file] [log] [blame]
Luis Hector Chaveza1518052018-06-14 08:19:34 -07001#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
Chris Sosa7cd23202013-10-15 17:22:57 -07003# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Integration tests for the devserver.
8
9This module is responsible for testing the actual devserver APIs and should be
10run whenever changes are made to the devserver.
11
xixuan52c2fba2016-05-20 17:02:48 -070012To run the integration test for devserver:
13 python ./devserver_integration_test.py
Chris Sosa7cd23202013-10-15 17:22:57 -070014"""
15
Gabe Black3b567202015-09-23 14:07:59 -070016from __future__ import print_function
17
Chris Sosa7cd23202013-10-15 17:22:57 -070018import json
Chris Sosa7cd23202013-10-15 17:22:57 -070019import os
Chris Sosa7cd23202013-10-15 17:22:57 -070020import shutil
21import signal
Gilad Arnold7de05f72014-02-14 13:14:20 -080022import socket
Chris Sosa7cd23202013-10-15 17:22:57 -070023import subprocess
24import tempfile
25import time
26import unittest
Chris Sosa7cd23202013-10-15 17:22:57 -070027
Amin Hassani0b4843b2019-09-25 16:38:56 -070028from string import Template
29
30from xml.dom import minidom
31
Amin Hassani469f5702019-10-21 15:35:06 -070032from six.moves import urllib
33
34import psutil # pylint: disable=import-error
35
Amin Hassani469f5702019-10-21 15:35:06 -070036import setup_chromite # pylint: disable=unused-import
Luis Hector Chaveza1518052018-06-14 08:19:34 -070037from chromite.lib import cros_logging as logging
Amin Hassanie427e212019-10-28 11:04:27 -070038from chromite.lib import cros_update_progress
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070039from chromite.lib.xbuddy import devserver_constants
Luis Hector Chaveza1518052018-06-14 08:19:34 -070040
Chris Sosa7cd23202013-10-15 17:22:57 -070041
42# Paths are relative to this script's base directory.
43LABEL = 'devserver'
44TEST_IMAGE_PATH = 'testdata/devserver'
Amin Hassani0b4843b2019-09-25 16:38:56 -070045TEST_UPDATE_PAYLOAD_NAME = 'update.gz'
46TEST_UPDATE_PAYLOAD_METADATA_NAME = 'update.gz.json'
Chris Sosa7cd23202013-10-15 17:22:57 -070047
48# Update request based on Omaha v3 protocol format.
Amin Hassani0b4843b2019-09-25 16:38:56 -070049UPDATE_REQUEST = Template("""<?xml version="1.0" encoding="UTF-8"?>
Amin Hassani495f1de2019-02-26 11:13:39 -080050<request protocol="3.0" updater="ChromeOSUpdateEngine" updaterversion="0.1.0.0" ismachine="1">
Chris Sosa7cd23202013-10-15 17:22:57 -070051 <os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></os>
Amin Hassani0b4843b2019-09-25 16:38:56 -070052 <app appid="$appid" version="11.254.2011_03_09_1814" lang="en-US" track="developer-build" board="x86-generic" hardware_class="BETA DVT" delta_okay="true">
Chris Sosa7cd23202013-10-15 17:22:57 -070053 <updatecheck></updatecheck>
54 </app>
55</request>
Amin Hassani0b4843b2019-09-25 16:38:56 -070056""")
Chris Sosa7cd23202013-10-15 17:22:57 -070057
58# RPC constants.
59STAGE = 'stage'
60IS_STAGED = 'is_staged'
61STATIC = 'static'
62UPDATE = 'update'
63CHECK_HEALTH = 'check_health'
64CONTROL_FILES = 'controlfiles'
65XBUDDY = 'xbuddy'
Prashanth Ba06d2d22014-03-07 15:35:19 -080066LIST_IMAGE_DIR = 'list_image_dir'
Chris Sosa7cd23202013-10-15 17:22:57 -070067
68# API rpcs and constants.
Chris Sosa7cd23202013-10-15 17:22:57 -070069API_SET_UPDATE_REQUEST = 'new_update-test/the-new-update'
70API_TEST_IP_ADDR = '127.0.0.1'
71
72DEVSERVER_START_TIMEOUT = 15
Gilad Arnold08516112014-02-14 13:14:03 -080073DEVSERVER_START_SLEEP = 1
Gilad Arnold7de05f72014-02-14 13:14:20 -080074MAX_START_ATTEMPTS = 5
Chris Sosa7cd23202013-10-15 17:22:57 -070075
76
77class DevserverFailedToStart(Exception):
78 """Raised if we could not start the devserver."""
79
80
Gilad Arnold08516112014-02-14 13:14:03 -080081class DevserverTestBase(unittest.TestCase):
Chris Sosa7cd23202013-10-15 17:22:57 -070082 """Class containing common logic between devserver test classes."""
83
84 def setUp(self):
Gilad Arnold08516112014-02-14 13:14:03 -080085 """Creates and populates a test directory, temporary files."""
Chris Sosa7cd23202013-10-15 17:22:57 -070086 self.test_data_path = tempfile.mkdtemp()
87 self.src_dir = os.path.dirname(__file__)
88
Gilad Arnold08516112014-02-14 13:14:03 -080089 # Copy the payload to the location of the update label.
Amin Hassani0b4843b2019-09-25 16:38:56 -070090 self._CreateLabelAndCopyUpdatePayloadFiles(LABEL)
Chris Sosa7cd23202013-10-15 17:22:57 -070091
92 # Copy the payload to the location of forced label.
Amin Hassani0b4843b2019-09-25 16:38:56 -070093 self._CreateLabelAndCopyUpdatePayloadFiles(API_SET_UPDATE_REQUEST)
Chris Sosa7cd23202013-10-15 17:22:57 -070094
Gilad Arnold08516112014-02-14 13:14:03 -080095 # Allocate temporary files for various devserver outputs.
96 self.pidfile = self._MakeTempFile('pid')
97 self.portfile = self._MakeTempFile('port')
98 self.logfile = self._MakeTempFile('log')
Chris Sosa7cd23202013-10-15 17:22:57 -070099
Gilad Arnold08516112014-02-14 13:14:03 -0800100 # Initialize various runtime values.
101 self.devserver_url = self.port = self.pid = None
Chris Sosa7cd23202013-10-15 17:22:57 -0700102
103 def tearDown(self):
Gilad Arnold08516112014-02-14 13:14:03 -0800104 """Kill the server, remove the test directory and temporary files."""
Chris Sosa7cd23202013-10-15 17:22:57 -0700105 if self.pid:
106 os.kill(self.pid, signal.SIGKILL)
107
Gilad Arnold08516112014-02-14 13:14:03 -0800108 self._RemoveFile(self.pidfile)
109 self._RemoveFile(self.portfile)
110 self._RemoveFile(self.logfile)
111 shutil.rmtree(self.test_data_path)
Chris Sosa7cd23202013-10-15 17:22:57 -0700112
113 # Helper methods begin here.
114
Amin Hassani0b4843b2019-09-25 16:38:56 -0700115 def _CreateLabelAndCopyUpdatePayloadFiles(self, label):
Gilad Arnold08516112014-02-14 13:14:03 -0800116 """Creates a label location and copies an image to it."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700117 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
Gilad Arnold08516112014-02-14 13:14:03 -0800118 label_dir = os.path.join(self.test_data_path, label)
119 os.makedirs(label_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700120 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
121 shutil.copy(os.path.join(update_dir, name), label_dir)
Gilad Arnold08516112014-02-14 13:14:03 -0800122
123 def _MakeTempFile(self, suffix):
124 """Return path of a newly created temporary file."""
125 with tempfile.NamedTemporaryFile(suffix='-devserver-%s' % suffix) as f:
126 name = f.name
127 f.close()
128
129 return name
130
131 def _RemoveFile(self, filename):
132 """Removes a file if it is present."""
133 if os.path.isfile(filename):
134 os.remove(filename)
135
136 def _ReadIntValueFromFile(self, path, desc):
137 """Reads a string from file and returns its conversion into an integer."""
138 if not os.path.isfile(path):
139 raise DevserverFailedToStart('Devserver did not drop %s (%r).' %
140 (desc, path))
141
142 with open(path) as f:
143 value_str = f.read()
144
145 try:
146 return int(value_str)
147 except ValueError:
148 raise DevserverFailedToStart('Devserver did not drop a valid value '
149 'in %s (%r).' % (desc, value_str))
150
151 def _StartServer(self, port=0):
Chris Sosa7cd23202013-10-15 17:22:57 -0700152 """Attempts to start devserver on |port|.
153
Gilad Arnold08516112014-02-14 13:14:03 -0800154 In the default case where port == 0, the server will bind to an arbitrary
Dan Shi2f136862016-02-11 15:38:38 -0800155 available port. If successful, this method will set the devserver's pid
Gilad Arnold08516112014-02-14 13:14:03 -0800156 (self.pid), actual listening port (self.port) and URL (self.devserver_url).
Chris Sosa7cd23202013-10-15 17:22:57 -0700157
158 Raises:
159 DevserverFailedToStart: If the devserver could not be started.
160 """
161 cmd = [
162 'python',
163 os.path.join(self.src_dir, 'devserver.py'),
164 'devserver.py',
165 '--static_dir', self.test_data_path,
166 '--pidfile', self.pidfile,
Gilad Arnold08516112014-02-14 13:14:03 -0800167 '--portfile', self.portfile,
Chris Sosa7cd23202013-10-15 17:22:57 -0700168 '--port', str(port),
169 '--logfile', self.logfile]
170
171 # Pipe all output. Use logfile to get devserver log.
172 subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
173
Gilad Arnold08516112014-02-14 13:14:03 -0800174 # Wait for devserver to start, determining its actual serving port and URL.
Chris Sosa7cd23202013-10-15 17:22:57 -0700175 current_time = time.time()
176 deadline = current_time + DEVSERVER_START_TIMEOUT
177 while current_time < deadline:
Chris Sosa7cd23202013-10-15 17:22:57 -0700178 try:
Gilad Arnold08516112014-02-14 13:14:03 -0800179 self.port = self._ReadIntValueFromFile(self.portfile, 'portfile')
180 self.devserver_url = 'http://127.0.0.1:%d' % self.port
Gabe Black3b567202015-09-23 14:07:59 -0700181 self._MakeRPC(CHECK_HEALTH, timeout=1)
Chris Sosa7cd23202013-10-15 17:22:57 -0700182 break
183 except Exception:
Gilad Arnold08516112014-02-14 13:14:03 -0800184 time.sleep(DEVSERVER_START_SLEEP)
185 current_time = time.time()
Chris Sosa7cd23202013-10-15 17:22:57 -0700186 else:
187 raise DevserverFailedToStart('Devserver failed to start within timeout.')
188
Gilad Arnold08516112014-02-14 13:14:03 -0800189 # Retrieve PID.
190 self.pid = self._ReadIntValueFromFile(self.pidfile, 'pidfile')
Chris Sosa7cd23202013-10-15 17:22:57 -0700191
Amin Hassani0b4843b2019-09-25 16:38:56 -0700192 def VerifyHandleUpdate(self, label, use_test_payload=True,
193 appid='{DEV-BUILD}'):
Chris Sosa7cd23202013-10-15 17:22:57 -0700194 """Verifies that we can send an update request to the devserver.
195
196 This method verifies (using a fake update_request blob) that the devserver
197 can interpret the payload and give us back the right payload.
198
199 Args:
200 label: Label that update is served from e.g. <board>-release/<version>
201 use_test_payload: If set to true, expects to serve payload under
202 testdata/ and does extra checks i.e. compares hash and content of
203 payload.
Gilad Arnold08516112014-02-14 13:14:03 -0800204
Chris Sosa7cd23202013-10-15 17:22:57 -0700205 Returns:
206 url of the update payload if we verified the update.
207 """
208 update_label = '/'.join([UPDATE, label])
Amin Hassani0b4843b2019-09-25 16:38:56 -0700209 response = self._MakeRPC(
210 update_label, data=UPDATE_REQUEST.substitute({'appid': appid}))
Chris Sosa7cd23202013-10-15 17:22:57 -0700211 self.assertNotEqual('', response)
212
213 # Parse the response and check if it contains the right result.
214 dom = minidom.parseString(response)
215 update = dom.getElementsByTagName('updatecheck')[0]
216 expected_static_url = '/'.join([self.devserver_url, STATIC, label])
Amin Hassani0b4843b2019-09-25 16:38:56 -0700217 url = self.VerifyV3Response(update, expected_static_url)
Chris Sosa7cd23202013-10-15 17:22:57 -0700218
219 # Verify the image we download is correct since we already know what it is.
220 if use_test_payload:
Amin Hassani469f5702019-10-21 15:35:06 -0700221 connection = urllib.request.urlopen(url)
Chris Sosa7cd23202013-10-15 17:22:57 -0700222 contents = connection.read()
223 connection.close()
224 self.assertEqual('Developers, developers, developers!\n', contents)
225
226 return url
227
Amin Hassani0b4843b2019-09-25 16:38:56 -0700228 def VerifyV3Response(self, update, expected_static_url):
Chris Sosa7cd23202013-10-15 17:22:57 -0700229 """Verifies the update DOM from a v3 response and returns the url."""
230 # Parse the response and check if it contains the right result.
231 urls = update.getElementsByTagName('urls')[0]
232 url = urls.getElementsByTagName('url')[0]
233
234 static_url = url.getAttribute('codebase')
235 # Static url's end in /.
236 self.assertEqual(expected_static_url + '/', static_url)
237
238 manifest = update.getElementsByTagName('manifest')[0]
239 packages = manifest.getElementsByTagName('packages')[0]
240 package = packages.getElementsByTagName('package')[0]
241 filename = package.getAttribute('name')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700242 self.assertEqual(TEST_UPDATE_PAYLOAD_NAME, filename)
Chris Sosa7cd23202013-10-15 17:22:57 -0700243
244 url = os.path.join(static_url, filename)
245 return url
246
247 def _MakeRPC(self, rpc, data=None, timeout=None, **kwargs):
Gilad Arnold08516112014-02-14 13:14:03 -0800248 """Makes an RPC call to the devserver.
Chris Sosa7cd23202013-10-15 17:22:57 -0700249
250 Args:
Gilad Arnold08516112014-02-14 13:14:03 -0800251 rpc: The function to run on the devserver, e.g. 'stage'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700252 data: Optional post data to send.
253 timeout: Optional timeout to pass to urlopen.
Gilad Arnold08516112014-02-14 13:14:03 -0800254 kwargs: Optional arguments to the function, e.g. artifact_url='foo/bar'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700255
Gilad Arnold08516112014-02-14 13:14:03 -0800256 Returns:
257 The function output.
Chris Sosa7cd23202013-10-15 17:22:57 -0700258 """
259 request = '/'.join([self.devserver_url, rpc])
260 if kwargs:
261 # Join the kwargs to the URL.
Amin Hassani469f5702019-10-21 15:35:06 -0700262 request += '?' + '&'.join('%s=%s' % item for item in kwargs.items())
Chris Sosa7cd23202013-10-15 17:22:57 -0700263
264 output = None
265 try:
266 # Let's log output for all rpc's without timeouts because we only
267 # use timeouts to check to see if something is up and these checks tend
268 # to be small and so logging it will be extremely repetitive.
269 if not timeout:
270 logging.info('Making request using %s', request)
271
Amin Hassani469f5702019-10-21 15:35:06 -0700272 # pylint: disable=redundant-keyword-arg
273 connection = urllib.request.urlopen(request, data=data, timeout=timeout)
Chris Sosa7cd23202013-10-15 17:22:57 -0700274 output = connection.read()
275 connection.close()
Amin Hassani469f5702019-10-21 15:35:06 -0700276 except urllib.error.HTTPError:
Chris Sosa7cd23202013-10-15 17:22:57 -0700277 raise
278
279 return output
280
281
Gilad Arnold08516112014-02-14 13:14:03 -0800282class AutoStartDevserverTestBase(DevserverTestBase):
283 """Test base class that automatically starts the devserver."""
284
285 def setUp(self):
286 """Initialize everything, then start the server."""
287 super(AutoStartDevserverTestBase, self).setUp()
288 self._StartServer()
289
290
Gilad Arnold7de05f72014-02-14 13:14:20 -0800291class DevserverStartTests(DevserverTestBase):
292 """Test that devserver starts up correctly."""
293
294 def testStartAnyPort(self):
295 """Starts the devserver, have it bind to an arbitrary available port."""
296 self._StartServer()
297
298 def testStartSpecificPort(self):
299 """Starts the devserver with a specific port."""
300 for _ in range(MAX_START_ATTEMPTS):
301 # This is a cheap hack to find an arbitrary unused port: we open a socket
302 # and bind it to port zero, then pull out the actual port number and
303 # close the socket. In all likelihood, this will leave us with an
304 # available port number that we can use for starting the devserver.
305 # However, this heuristic is susceptible to race conditions, hence the
306 # retry loop.
307 s = socket.socket()
308 s.bind(('', 0))
309 # s.getsockname() is definitely callable.
310 # pylint: disable=E1102
311 _, port = s.getsockname()
312 s.close()
313
314 self._StartServer(port=port)
315
316
Gilad Arnold08516112014-02-14 13:14:03 -0800317class DevserverBasicTests(AutoStartDevserverTestBase):
318 """Short running tests for the devserver (no remote deps).
Chris Sosa7cd23202013-10-15 17:22:57 -0700319
320 These are technically not unittests because they depend on being able to
321 start a devserver locally which technically requires external resources so
322 they are lumped with the remote tests here.
323 """
324
325 def testHandleUpdateV3(self):
326 self.VerifyHandleUpdate(label=LABEL)
327
Chris Sosa7cd23202013-10-15 17:22:57 -0700328 def testXBuddyLocalAlias(self):
329 """Extensive local image xbuddy unittest.
330
331 This test verifies all the local xbuddy logic by creating a new local folder
332 with the necessary update items and verifies we can use all of them.
333 """
Chris Sosa7cd23202013-10-15 17:22:57 -0700334 build_id = 'x86-generic/R32-9999.0.0-a1'
335 xbuddy_path = 'x86-generic/R32-9999.0.0-a1/test'
336 build_dir = os.path.join(self.test_data_path, build_id)
337 os.makedirs(build_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700338
339 # Writing dummy files.
340 image_data = 'TEST IMAGE'
Chris Sosa7cd23202013-10-15 17:22:57 -0700341 test_image_file = os.path.join(build_dir,
342 devserver_constants.TEST_IMAGE_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700343 with open(test_image_file, 'w') as f:
344 f.write(image_data)
345
346 stateful_data = 'STATEFUL STUFFS'
Chris Sosa7cd23202013-10-15 17:22:57 -0700347 stateful_file = os.path.join(build_dir, devserver_constants.STATEFUL_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700348 with open(stateful_file, 'w') as f:
349 f.write(stateful_data)
Chris Sosa7cd23202013-10-15 17:22:57 -0700350
Amin Hassani0b4843b2019-09-25 16:38:56 -0700351 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
352 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
353 shutil.copy(os.path.join(update_dir, name), build_dir)
354 with open(os.path.join(build_dir, TEST_UPDATE_PAYLOAD_NAME), 'r') as f:
355 update_data = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700356
Amin Hassani0b4843b2019-09-25 16:38:56 -0700357 for item, data in zip(['full_payload', 'test', 'stateful'],
358 [update_data, image_data, stateful_data]):
Chris Sosa7cd23202013-10-15 17:22:57 -0700359
360 xbuddy_path = '/'.join([build_id, item])
361 logging.info('Testing xbuddy path %s', xbuddy_path)
362 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]))
363 self.assertEqual(response, data)
364
365 expected_dir = '/'.join([self.devserver_url, STATIC, build_id])
366 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
367 self.assertEqual(response, expected_dir)
368
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800369 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]),
370 relative_path=True)
371 self.assertEqual(response, build_id)
372
Chris Sosa7cd23202013-10-15 17:22:57 -0700373 xbuddy_path = '/'.join([build_id, 'test'])
374 logging.info('Testing for_update for %s', xbuddy_path)
375 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
376 expected_path = '/'.join([self.devserver_url, UPDATE, build_id])
377 self.assertTrue(response, expected_path)
378
379 logging.info('Verifying the actual payload data')
380 url = self.VerifyHandleUpdate(build_id, use_test_payload=False)
381 logging.info('Verify the actual content of the update payload')
Amin Hassani469f5702019-10-21 15:35:06 -0700382 connection = urllib.request.urlopen(url)
Chris Sosa7cd23202013-10-15 17:22:57 -0700383 contents = connection.read()
384 connection.close()
385 self.assertEqual(update_data, contents)
386
387 def testPidFile(self):
388 """Test that using a pidfile works correctly."""
389 with open(self.pidfile, 'r') as f:
390 pid = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700391 # Let's assert some process information about the devserver.
Dan Shi2f136862016-02-11 15:38:38 -0800392 self.assertTrue(pid.strip().isdigit())
Chris Sosa7cd23202013-10-15 17:22:57 -0700393 process = psutil.Process(int(pid))
394 self.assertTrue(process.is_running())
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700395 self.assertTrue('devserver.py' in process.cmdline())
Chris Sosa7cd23202013-10-15 17:22:57 -0700396
Amin Hassani28df4212019-10-28 10:16:50 -0700397 def testFileInfo(self):
398 """Verifies the fileinfo API."""
399 response = self._MakeRPC('/'.join(['api', 'fileinfo', LABEL,
400 TEST_UPDATE_PAYLOAD_NAME]))
401 self.assertEqual(
402 response,
403 '{"sha256": "hgfZ19hcsA5OGUN3f4SDtoO0kzT24o+JsGgjNVCVEy0=", '
404 '"size": 36}')
405
Chris Sosa7cd23202013-10-15 17:22:57 -0700406
Gilad Arnold08516112014-02-14 13:14:03 -0800407class DevserverExtendedTests(AutoStartDevserverTestBase):
Chris Sosa7cd23202013-10-15 17:22:57 -0700408 """Longer running integration tests that test interaction with Google Storage.
409
410 Note: due to the interaction with Google Storage, these tests both require
411 1) runner has access to the Google Storage bucket where builders store builds.
412 2) time. These tests actually download the artifacts needed.
413 """
414
xixuan52c2fba2016-05-20 17:02:48 -0700415 def testCrosAU(self):
416 """Tests core autotest workflow where we trigger CrOS auto-update.
417
418 It mainly tests the following API:
419 a. 'get_au_status'
420 b. 'handler_cleanup'
421 c. 'kill_au_proc'
422 """
423 host_name = '100.0.0.0'
xixuan2a0970a2016-08-10 12:12:44 -0700424 p = subprocess.Popen(['sleep 100'], shell=True, preexec_fn=os.setsid)
425 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700426 status = 'updating'
427 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
428 progress_tracker.WriteStatus(status)
429
430 logging.info('Retrieving auto-update status for process %d', pid)
431 response = self._MakeRPC('get_au_status', host_name=host_name, pid=pid)
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700432 self.assertFalse(json.loads(response)['finished'])
433 self.assertEqual(json.loads(response)['status'], status)
xixuan52c2fba2016-05-20 17:02:48 -0700434
435 progress_tracker.WriteStatus(cros_update_progress.FINISHED)
436 logging.info('Mock auto-update process is finished')
437 response = self._MakeRPC('get_au_status', host_name=host_name, pid=pid)
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700438 self.assertTrue(json.loads(response)['finished'])
439 self.assertEqual(json.loads(response)['status'],
440 cros_update_progress.FINISHED)
xixuan52c2fba2016-05-20 17:02:48 -0700441
442 logging.info('Delete auto-update track status file')
443 self.assertTrue(os.path.exists(progress_tracker.track_status_file))
444 self._MakeRPC('handler_cleanup', host_name=host_name, pid=pid)
445 self.assertFalse(os.path.exists(progress_tracker.track_status_file))
446
447 logging.info('Kill the left auto-update processes for host %s', host_name)
448 progress_tracker.WriteStatus(cros_update_progress.FINISHED)
449 response = self._MakeRPC('kill_au_proc', host_name=host_name)
450 self.assertEqual(response, 'True')
451 self.assertFalse(os.path.exists(progress_tracker.track_status_file))
452 self.assertFalse(cros_update_progress.IsProcessAlive(pid))
453
454
Chris Sosa7cd23202013-10-15 17:22:57 -0700455 def testStageAndUpdate(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700456 """Tests core stage/update autotest workflow where with a test payload."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700457 build_id = 'eve-release/R78-12499.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700458 archive_url = 'gs://chromeos-image-archive/%s' % build_id
459
460 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
461 artifacts='full_payload,stateful')
462 self.assertEqual(response, 'False')
463
464 logging.info('Staging update artifacts')
465 self._MakeRPC(STAGE, archive_url=archive_url,
466 artifacts='full_payload,stateful')
467 logging.info('Staging complete. '
468 'Verifying files exist and are staged in the staging '
469 'directory.')
470 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
471 artifacts='full_payload,stateful')
472 self.assertEqual(response, 'True')
473 staged_dir = os.path.join(self.test_data_path, build_id)
474 self.assertTrue(os.path.isdir(staged_dir))
475 self.assertTrue(os.path.exists(
476 os.path.join(staged_dir, devserver_constants.UPDATE_FILE)))
477 self.assertTrue(os.path.exists(
Amin Hassani0b4843b2019-09-25 16:38:56 -0700478 os.path.join(staged_dir, devserver_constants.UPDATE_METADATA_FILE)))
479 self.assertTrue(os.path.exists(
Chris Sosa7cd23202013-10-15 17:22:57 -0700480 os.path.join(staged_dir, devserver_constants.STATEFUL_FILE)))
481
482 logging.info('Verifying we can update using the stage update artifacts.')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700483 self.VerifyHandleUpdate(build_id, use_test_payload=False,
484 appid='{01906EA2-3EB2-41F1-8F62-F0B7120EFD2E}')
Chris Sosa7cd23202013-10-15 17:22:57 -0700485
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700486 @unittest.skip('crbug.com/640063 Broken test.')
Chris Sosa7cd23202013-10-15 17:22:57 -0700487 def testStageAutotestAndGetPackages(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700488 """Another stage/update autotest workflow test with a test payload."""
489 build_id = 'eve-release/R69-10782.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700490 archive_url = 'gs://chromeos-image-archive/%s' % build_id
491 autotest_artifacts = 'autotest,test_suites,au_suite'
492 logging.info('Staging autotest artifacts (may take a while).')
493 self._MakeRPC(STAGE, archive_url=archive_url, artifacts=autotest_artifacts)
494
495 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
496 artifacts=autotest_artifacts)
497 self.assertEqual(response, 'True')
498
499 # Verify the files exist and are staged in the staging directory.
500 logging.info('Checking directories exist after we staged the files.')
501 staged_dir = os.path.join(self.test_data_path, build_id)
502 autotest_dir = os.path.join(staged_dir, 'autotest')
503 package_dir = os.path.join(autotest_dir, 'packages')
504 self.assertTrue(os.path.isdir(staged_dir))
505 self.assertTrue(os.path.isdir(autotest_dir))
506 self.assertTrue(os.path.isdir(package_dir))
507
508 control_files = self._MakeRPC(CONTROL_FILES, build=build_id,
509 suite_name='bvt')
510 logging.info('Checking for known control file in bvt suite.')
511 self.assertTrue('client/site_tests/platform_FilePerms/'
512 'control' in control_files)
513
514 def testRemoteXBuddyAlias(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700515 """Another stage/update autotest workflow test with a test payload."""
516 build_id = 'eve-release/R69-10782.0.0'
517 xbuddy_path = 'remote/eve/R69-10782.0.0/full_payload'
518 xbuddy_bad_path = 'remote/eve/R32-9999.9999.9999'
Chris Sosa7cd23202013-10-15 17:22:57 -0700519 logging.info('Staging artifacts using xbuddy.')
520 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
521
522 logging.info('Verifying static url returned is valid.')
523 expected_static_url = '/'.join([self.devserver_url, STATIC, build_id])
524 self.assertEqual(response, expected_static_url)
525
526 logging.info('Checking for_update returns an update_url for what we just '
527 'staged.')
528 expected_update_url = '/'.join([self.devserver_url, UPDATE, build_id])
529 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
530 self.assertEqual(response, expected_update_url)
531
532 logging.info('Now give xbuddy a bad path.')
Amin Hassani469f5702019-10-21 15:35:06 -0700533 self.assertRaises(urllib.error.HTTPError,
Chris Sosa7cd23202013-10-15 17:22:57 -0700534 self._MakeRPC,
535 '/'.join([XBUDDY, xbuddy_bad_path]))
536
Prashanth Ba06d2d22014-03-07 15:35:19 -0800537 def testListImageDir(self):
538 """Verifies that we can list the contents of the image directory."""
539 build_id = 'x86-mario-release/R32-4810.0.0'
540 archive_url = 'gs://chromeos-image-archive/%s' % build_id
541 build_dir = os.path.join(self.test_data_path, build_id)
542 shutil.rmtree(build_dir, ignore_errors=True)
543
544 logging.info('checking for %s on an unstaged build.', LIST_IMAGE_DIR)
545 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
546 self.assertTrue(archive_url in response and 'not been staged' in response)
547
548 logging.info('Checking for %s on a staged build.', LIST_IMAGE_DIR)
549 fake_file_name = 'fake_file'
550 try:
551 os.makedirs(build_dir)
552 open(os.path.join(build_dir, fake_file_name), 'w').close()
553 except OSError:
554 logging.error('Could not create files to imitate staged content. '
555 'Build dir %s, file %s', build_dir, fake_file_name)
556 raise
557 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
558 self.assertTrue(fake_file_name in response)
559 shutil.rmtree(build_dir, ignore_errors=True)
Chris Sosa7cd23202013-10-15 17:22:57 -0700560
Amin Hassani28df4212019-10-28 10:16:50 -0700561
Chris Sosa7cd23202013-10-15 17:22:57 -0700562if __name__ == '__main__':
563 logging_format = '%(levelname)-8s: %(message)s'
564 logging.basicConfig(level=logging.DEBUG, format=logging_format)
565 unittest.main()