blob: 6209ab27a6375866de36fa11533120208c62a043 [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
20import psutil
21import shutil
22import signal
Gilad Arnold7de05f72014-02-14 13:14:20 -080023import socket
Chris Sosa7cd23202013-10-15 17:22:57 -070024import subprocess
25import tempfile
26import time
27import unittest
28import urllib2
29
Amin Hassani0b4843b2019-09-25 16:38:56 -070030from string import Template
31
32from xml.dom import minidom
33
34import cros_update_progress
35import devserver_constants
36
Luis Hector Chaveza1518052018-06-14 08:19:34 -070037from chromite.lib import cros_logging as logging
38
Chris Sosa7cd23202013-10-15 17:22:57 -070039
40# Paths are relative to this script's base directory.
41LABEL = 'devserver'
42TEST_IMAGE_PATH = 'testdata/devserver'
Amin Hassani0b4843b2019-09-25 16:38:56 -070043TEST_UPDATE_PAYLOAD_NAME = 'update.gz'
44TEST_UPDATE_PAYLOAD_METADATA_NAME = 'update.gz.json'
Chris Sosa7cd23202013-10-15 17:22:57 -070045
46# Update request based on Omaha v3 protocol format.
Amin Hassani0b4843b2019-09-25 16:38:56 -070047UPDATE_REQUEST = Template("""<?xml version="1.0" encoding="UTF-8"?>
Amin Hassani495f1de2019-02-26 11:13:39 -080048<request protocol="3.0" updater="ChromeOSUpdateEngine" updaterversion="0.1.0.0" ismachine="1">
Chris Sosa7cd23202013-10-15 17:22:57 -070049 <os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></os>
Amin Hassani0b4843b2019-09-25 16:38:56 -070050 <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 -070051 <updatecheck></updatecheck>
52 </app>
53</request>
Amin Hassani0b4843b2019-09-25 16:38:56 -070054""")
Chris Sosa7cd23202013-10-15 17:22:57 -070055
56# RPC constants.
57STAGE = 'stage'
58IS_STAGED = 'is_staged'
59STATIC = 'static'
60UPDATE = 'update'
61CHECK_HEALTH = 'check_health'
62CONTROL_FILES = 'controlfiles'
63XBUDDY = 'xbuddy'
Prashanth Ba06d2d22014-03-07 15:35:19 -080064LIST_IMAGE_DIR = 'list_image_dir'
Chris Sosa7cd23202013-10-15 17:22:57 -070065
66# API rpcs and constants.
67API_HOST_INFO = 'api/hostinfo'
68API_SET_NEXT_UPDATE = 'api/setnextupdate'
69API_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:
221 connection = urllib2.urlopen(url)
222 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.
262 request += '?' + '&'.join('%s=%s' % item for item in kwargs.iteritems())
263
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
272 connection = urllib2.urlopen(request, data=data, timeout=timeout)
273 output = connection.read()
274 connection.close()
275 except urllib2.HTTPError:
276 raise
277
278 return output
279
280
Gilad Arnold08516112014-02-14 13:14:03 -0800281class AutoStartDevserverTestBase(DevserverTestBase):
282 """Test base class that automatically starts the devserver."""
283
284 def setUp(self):
285 """Initialize everything, then start the server."""
286 super(AutoStartDevserverTestBase, self).setUp()
287 self._StartServer()
288
289
Gilad Arnold7de05f72014-02-14 13:14:20 -0800290class DevserverStartTests(DevserverTestBase):
291 """Test that devserver starts up correctly."""
292
293 def testStartAnyPort(self):
294 """Starts the devserver, have it bind to an arbitrary available port."""
295 self._StartServer()
296
297 def testStartSpecificPort(self):
298 """Starts the devserver with a specific port."""
299 for _ in range(MAX_START_ATTEMPTS):
300 # This is a cheap hack to find an arbitrary unused port: we open a socket
301 # and bind it to port zero, then pull out the actual port number and
302 # close the socket. In all likelihood, this will leave us with an
303 # available port number that we can use for starting the devserver.
304 # However, this heuristic is susceptible to race conditions, hence the
305 # retry loop.
306 s = socket.socket()
307 s.bind(('', 0))
308 # s.getsockname() is definitely callable.
309 # pylint: disable=E1102
310 _, port = s.getsockname()
311 s.close()
312
313 self._StartServer(port=port)
314
315
Gilad Arnold08516112014-02-14 13:14:03 -0800316class DevserverBasicTests(AutoStartDevserverTestBase):
317 """Short running tests for the devserver (no remote deps).
Chris Sosa7cd23202013-10-15 17:22:57 -0700318
319 These are technically not unittests because they depend on being able to
320 start a devserver locally which technically requires external resources so
321 they are lumped with the remote tests here.
322 """
323
324 def testHandleUpdateV3(self):
325 self.VerifyHandleUpdate(label=LABEL)
326
327 def testApiBadSetNextUpdateRequest(self):
328 """Tests sending a bad setnextupdate request."""
329 # Send bad request and ensure it fails...
330 self.assertRaises(urllib2.URLError,
331 self._MakeRPC,
332 '/'.join([API_SET_NEXT_UPDATE, API_TEST_IP_ADDR]))
333
334 def testApiBadSetNextUpdateURL(self):
335 """Tests contacting a bad setnextupdate url."""
336 # Send bad request and ensure it fails...
337 self.assertRaises(urllib2.URLError,
338 self._MakeRPC, API_SET_NEXT_UPDATE)
339
340 def testApiBadHostInfoURL(self):
341 """Tests contacting a bad hostinfo url."""
342 # Host info should be invalid without a specified address.
343 self.assertRaises(urllib2.URLError,
344 self._MakeRPC, API_HOST_INFO)
345
346 def testApiHostInfoAndSetNextUpdate(self):
347 """Tests using the setnextupdate and hostinfo api commands."""
348 # Send setnextupdate command.
349 self._MakeRPC('/'.join([API_SET_NEXT_UPDATE, API_TEST_IP_ADDR]),
350 data=API_SET_UPDATE_REQUEST)
351
352 # Send hostinfo command and verify the setnextupdate worked.
353 response = self._MakeRPC('/'.join([API_HOST_INFO, API_TEST_IP_ADDR]))
354
355 self.assertEqual(
356 json.loads(response)['forced_update_label'], API_SET_UPDATE_REQUEST)
357
358 def testXBuddyLocalAlias(self):
359 """Extensive local image xbuddy unittest.
360
361 This test verifies all the local xbuddy logic by creating a new local folder
362 with the necessary update items and verifies we can use all of them.
363 """
Chris Sosa7cd23202013-10-15 17:22:57 -0700364 build_id = 'x86-generic/R32-9999.0.0-a1'
365 xbuddy_path = 'x86-generic/R32-9999.0.0-a1/test'
366 build_dir = os.path.join(self.test_data_path, build_id)
367 os.makedirs(build_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700368
369 # Writing dummy files.
370 image_data = 'TEST IMAGE'
Chris Sosa7cd23202013-10-15 17:22:57 -0700371 test_image_file = os.path.join(build_dir,
372 devserver_constants.TEST_IMAGE_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700373 with open(test_image_file, 'w') as f:
374 f.write(image_data)
375
376 stateful_data = 'STATEFUL STUFFS'
Chris Sosa7cd23202013-10-15 17:22:57 -0700377 stateful_file = os.path.join(build_dir, devserver_constants.STATEFUL_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700378 with open(stateful_file, 'w') as f:
379 f.write(stateful_data)
Chris Sosa7cd23202013-10-15 17:22:57 -0700380
Amin Hassani0b4843b2019-09-25 16:38:56 -0700381 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
382 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
383 shutil.copy(os.path.join(update_dir, name), build_dir)
384 with open(os.path.join(build_dir, TEST_UPDATE_PAYLOAD_NAME), 'r') as f:
385 update_data = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700386
Amin Hassani0b4843b2019-09-25 16:38:56 -0700387 for item, data in zip(['full_payload', 'test', 'stateful'],
388 [update_data, image_data, stateful_data]):
Chris Sosa7cd23202013-10-15 17:22:57 -0700389
390 xbuddy_path = '/'.join([build_id, item])
391 logging.info('Testing xbuddy path %s', xbuddy_path)
392 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]))
393 self.assertEqual(response, data)
394
395 expected_dir = '/'.join([self.devserver_url, STATIC, build_id])
396 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
397 self.assertEqual(response, expected_dir)
398
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800399 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]),
400 relative_path=True)
401 self.assertEqual(response, build_id)
402
Chris Sosa7cd23202013-10-15 17:22:57 -0700403 xbuddy_path = '/'.join([build_id, 'test'])
404 logging.info('Testing for_update for %s', xbuddy_path)
405 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
406 expected_path = '/'.join([self.devserver_url, UPDATE, build_id])
407 self.assertTrue(response, expected_path)
408
409 logging.info('Verifying the actual payload data')
410 url = self.VerifyHandleUpdate(build_id, use_test_payload=False)
411 logging.info('Verify the actual content of the update payload')
412 connection = urllib2.urlopen(url)
413 contents = connection.read()
414 connection.close()
415 self.assertEqual(update_data, contents)
416
417 def testPidFile(self):
418 """Test that using a pidfile works correctly."""
419 with open(self.pidfile, 'r') as f:
420 pid = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700421 # Let's assert some process information about the devserver.
Dan Shi2f136862016-02-11 15:38:38 -0800422 self.assertTrue(pid.strip().isdigit())
Chris Sosa7cd23202013-10-15 17:22:57 -0700423 process = psutil.Process(int(pid))
424 self.assertTrue(process.is_running())
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700425 self.assertTrue('devserver.py' in process.cmdline())
Chris Sosa7cd23202013-10-15 17:22:57 -0700426
427
Gilad Arnold08516112014-02-14 13:14:03 -0800428class DevserverExtendedTests(AutoStartDevserverTestBase):
Chris Sosa7cd23202013-10-15 17:22:57 -0700429 """Longer running integration tests that test interaction with Google Storage.
430
431 Note: due to the interaction with Google Storage, these tests both require
432 1) runner has access to the Google Storage bucket where builders store builds.
433 2) time. These tests actually download the artifacts needed.
434 """
435
xixuan52c2fba2016-05-20 17:02:48 -0700436 def testCrosAU(self):
437 """Tests core autotest workflow where we trigger CrOS auto-update.
438
439 It mainly tests the following API:
440 a. 'get_au_status'
441 b. 'handler_cleanup'
442 c. 'kill_au_proc'
443 """
444 host_name = '100.0.0.0'
xixuan2a0970a2016-08-10 12:12:44 -0700445 p = subprocess.Popen(['sleep 100'], shell=True, preexec_fn=os.setsid)
446 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700447 status = 'updating'
448 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
449 progress_tracker.WriteStatus(status)
450
451 logging.info('Retrieving auto-update status for process %d', pid)
452 response = self._MakeRPC('get_au_status', host_name=host_name, pid=pid)
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700453 self.assertFalse(json.loads(response)['finished'])
454 self.assertEqual(json.loads(response)['status'], status)
xixuan52c2fba2016-05-20 17:02:48 -0700455
456 progress_tracker.WriteStatus(cros_update_progress.FINISHED)
457 logging.info('Mock auto-update process is finished')
458 response = self._MakeRPC('get_au_status', host_name=host_name, pid=pid)
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700459 self.assertTrue(json.loads(response)['finished'])
460 self.assertEqual(json.loads(response)['status'],
461 cros_update_progress.FINISHED)
xixuan52c2fba2016-05-20 17:02:48 -0700462
463 logging.info('Delete auto-update track status file')
464 self.assertTrue(os.path.exists(progress_tracker.track_status_file))
465 self._MakeRPC('handler_cleanup', host_name=host_name, pid=pid)
466 self.assertFalse(os.path.exists(progress_tracker.track_status_file))
467
468 logging.info('Kill the left auto-update processes for host %s', host_name)
469 progress_tracker.WriteStatus(cros_update_progress.FINISHED)
470 response = self._MakeRPC('kill_au_proc', host_name=host_name)
471 self.assertEqual(response, 'True')
472 self.assertFalse(os.path.exists(progress_tracker.track_status_file))
473 self.assertFalse(cros_update_progress.IsProcessAlive(pid))
474
475
Chris Sosa7cd23202013-10-15 17:22:57 -0700476 def testStageAndUpdate(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700477 """Tests core stage/update autotest workflow where with a test payload."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700478 build_id = 'eve-release/R78-12499.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700479 archive_url = 'gs://chromeos-image-archive/%s' % build_id
480
481 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
482 artifacts='full_payload,stateful')
483 self.assertEqual(response, 'False')
484
485 logging.info('Staging update artifacts')
486 self._MakeRPC(STAGE, archive_url=archive_url,
487 artifacts='full_payload,stateful')
488 logging.info('Staging complete. '
489 'Verifying files exist and are staged in the staging '
490 'directory.')
491 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
492 artifacts='full_payload,stateful')
493 self.assertEqual(response, 'True')
494 staged_dir = os.path.join(self.test_data_path, build_id)
495 self.assertTrue(os.path.isdir(staged_dir))
496 self.assertTrue(os.path.exists(
497 os.path.join(staged_dir, devserver_constants.UPDATE_FILE)))
498 self.assertTrue(os.path.exists(
Amin Hassani0b4843b2019-09-25 16:38:56 -0700499 os.path.join(staged_dir, devserver_constants.UPDATE_METADATA_FILE)))
500 self.assertTrue(os.path.exists(
Chris Sosa7cd23202013-10-15 17:22:57 -0700501 os.path.join(staged_dir, devserver_constants.STATEFUL_FILE)))
502
503 logging.info('Verifying we can update using the stage update artifacts.')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700504 self.VerifyHandleUpdate(build_id, use_test_payload=False,
505 appid='{01906EA2-3EB2-41F1-8F62-F0B7120EFD2E}')
Chris Sosa7cd23202013-10-15 17:22:57 -0700506
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700507 @unittest.skip('crbug.com/640063 Broken test.')
Chris Sosa7cd23202013-10-15 17:22:57 -0700508 def testStageAutotestAndGetPackages(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700509 """Another stage/update autotest workflow test with a test payload."""
510 build_id = 'eve-release/R69-10782.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700511 archive_url = 'gs://chromeos-image-archive/%s' % build_id
512 autotest_artifacts = 'autotest,test_suites,au_suite'
513 logging.info('Staging autotest artifacts (may take a while).')
514 self._MakeRPC(STAGE, archive_url=archive_url, artifacts=autotest_artifacts)
515
516 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
517 artifacts=autotest_artifacts)
518 self.assertEqual(response, 'True')
519
520 # Verify the files exist and are staged in the staging directory.
521 logging.info('Checking directories exist after we staged the files.')
522 staged_dir = os.path.join(self.test_data_path, build_id)
523 autotest_dir = os.path.join(staged_dir, 'autotest')
524 package_dir = os.path.join(autotest_dir, 'packages')
525 self.assertTrue(os.path.isdir(staged_dir))
526 self.assertTrue(os.path.isdir(autotest_dir))
527 self.assertTrue(os.path.isdir(package_dir))
528
529 control_files = self._MakeRPC(CONTROL_FILES, build=build_id,
530 suite_name='bvt')
531 logging.info('Checking for known control file in bvt suite.')
532 self.assertTrue('client/site_tests/platform_FilePerms/'
533 'control' in control_files)
534
535 def testRemoteXBuddyAlias(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700536 """Another stage/update autotest workflow test with a test payload."""
537 build_id = 'eve-release/R69-10782.0.0'
538 xbuddy_path = 'remote/eve/R69-10782.0.0/full_payload'
539 xbuddy_bad_path = 'remote/eve/R32-9999.9999.9999'
Chris Sosa7cd23202013-10-15 17:22:57 -0700540 logging.info('Staging artifacts using xbuddy.')
541 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
542
543 logging.info('Verifying static url returned is valid.')
544 expected_static_url = '/'.join([self.devserver_url, STATIC, build_id])
545 self.assertEqual(response, expected_static_url)
546
547 logging.info('Checking for_update returns an update_url for what we just '
548 'staged.')
549 expected_update_url = '/'.join([self.devserver_url, UPDATE, build_id])
550 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
551 self.assertEqual(response, expected_update_url)
552
553 logging.info('Now give xbuddy a bad path.')
554 self.assertRaises(urllib2.HTTPError,
555 self._MakeRPC,
556 '/'.join([XBUDDY, xbuddy_bad_path]))
557
Prashanth Ba06d2d22014-03-07 15:35:19 -0800558 def testListImageDir(self):
559 """Verifies that we can list the contents of the image directory."""
560 build_id = 'x86-mario-release/R32-4810.0.0'
561 archive_url = 'gs://chromeos-image-archive/%s' % build_id
562 build_dir = os.path.join(self.test_data_path, build_id)
563 shutil.rmtree(build_dir, ignore_errors=True)
564
565 logging.info('checking for %s on an unstaged build.', LIST_IMAGE_DIR)
566 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
567 self.assertTrue(archive_url in response and 'not been staged' in response)
568
569 logging.info('Checking for %s on a staged build.', LIST_IMAGE_DIR)
570 fake_file_name = 'fake_file'
571 try:
572 os.makedirs(build_dir)
573 open(os.path.join(build_dir, fake_file_name), 'w').close()
574 except OSError:
575 logging.error('Could not create files to imitate staged content. '
576 'Build dir %s, file %s', build_dir, fake_file_name)
577 raise
578 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
579 self.assertTrue(fake_file_name in response)
580 shutil.rmtree(build_dir, ignore_errors=True)
Chris Sosa7cd23202013-10-15 17:22:57 -0700581
582if __name__ == '__main__':
583 logging_format = '%(levelname)-8s: %(message)s'
584 logging.basicConfig(level=logging.DEBUG, format=logging_format)
585 unittest.main()