blob: fbde817b06f197478f1a8f6ebb8dfc45839ab2ef [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
Gilad Arnold7de05f72014-02-14 13:14:20 -080021import socket
Chris Sosa7cd23202013-10-15 17:22:57 -070022import subprocess
Amin Hassaniba847c82019-12-06 16:30:56 -080023import sys
Chris Sosa7cd23202013-10-15 17:22:57 -070024import 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 Hassani6eec8792020-01-09 14:06:48 -080032import requests
33
Amin Hassani469f5702019-10-21 15:35:06 -070034from six.moves import urllib
35
36import psutil # pylint: disable=import-error
37
Amin Hassani469f5702019-10-21 15:35:06 -070038import setup_chromite # pylint: disable=unused-import
Luis Hector Chaveza1518052018-06-14 08:19:34 -070039from chromite.lib import cros_logging as logging
Amin Hassanie427e212019-10-28 11:04:27 -070040from chromite.lib import cros_update_progress
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070041from chromite.lib.xbuddy import devserver_constants
Luis Hector Chaveza1518052018-06-14 08:19:34 -070042
Chris Sosa7cd23202013-10-15 17:22:57 -070043
44# Paths are relative to this script's base directory.
45LABEL = 'devserver'
46TEST_IMAGE_PATH = 'testdata/devserver'
Amin Hassani0b4843b2019-09-25 16:38:56 -070047TEST_UPDATE_PAYLOAD_NAME = 'update.gz'
48TEST_UPDATE_PAYLOAD_METADATA_NAME = 'update.gz.json'
Chris Sosa7cd23202013-10-15 17:22:57 -070049
50# Update request based on Omaha v3 protocol format.
Amin Hassani0b4843b2019-09-25 16:38:56 -070051UPDATE_REQUEST = Template("""<?xml version="1.0" encoding="UTF-8"?>
Amin Hassani495f1de2019-02-26 11:13:39 -080052<request protocol="3.0" updater="ChromeOSUpdateEngine" updaterversion="0.1.0.0" ismachine="1">
Chris Sosa7cd23202013-10-15 17:22:57 -070053 <os version="Indy" platform="Chrome OS" sp="0.11.254.2011_03_09_1814_i686"></os>
Amin Hassani0b4843b2019-09-25 16:38:56 -070054 <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 -070055 <updatecheck></updatecheck>
56 </app>
57</request>
Amin Hassani0b4843b2019-09-25 16:38:56 -070058""")
Chris Sosa7cd23202013-10-15 17:22:57 -070059
60# RPC constants.
61STAGE = 'stage'
62IS_STAGED = 'is_staged'
63STATIC = 'static'
64UPDATE = 'update'
65CHECK_HEALTH = 'check_health'
66CONTROL_FILES = 'controlfiles'
67XBUDDY = 'xbuddy'
Prashanth Ba06d2d22014-03-07 15:35:19 -080068LIST_IMAGE_DIR = 'list_image_dir'
Chris Sosa7cd23202013-10-15 17:22:57 -070069
70# API rpcs and constants.
Chris Sosa7cd23202013-10-15 17:22:57 -070071API_SET_UPDATE_REQUEST = 'new_update-test/the-new-update'
72API_TEST_IP_ADDR = '127.0.0.1'
73
74DEVSERVER_START_TIMEOUT = 15
Gilad Arnold08516112014-02-14 13:14:03 -080075DEVSERVER_START_SLEEP = 1
Gilad Arnold7de05f72014-02-14 13:14:20 -080076MAX_START_ATTEMPTS = 5
Chris Sosa7cd23202013-10-15 17:22:57 -070077
78
79class DevserverFailedToStart(Exception):
80 """Raised if we could not start the devserver."""
81
82
Gilad Arnold08516112014-02-14 13:14:03 -080083class DevserverTestBase(unittest.TestCase):
Chris Sosa7cd23202013-10-15 17:22:57 -070084 """Class containing common logic between devserver test classes."""
85
86 def setUp(self):
Gilad Arnold08516112014-02-14 13:14:03 -080087 """Creates and populates a test directory, temporary files."""
Chris Sosa7cd23202013-10-15 17:22:57 -070088 self.test_data_path = tempfile.mkdtemp()
89 self.src_dir = os.path.dirname(__file__)
90
Gilad Arnold08516112014-02-14 13:14:03 -080091 # Copy the payload to the location of the update label.
Amin Hassani0b4843b2019-09-25 16:38:56 -070092 self._CreateLabelAndCopyUpdatePayloadFiles(LABEL)
Chris Sosa7cd23202013-10-15 17:22:57 -070093
94 # Copy the payload to the location of forced label.
Amin Hassani0b4843b2019-09-25 16:38:56 -070095 self._CreateLabelAndCopyUpdatePayloadFiles(API_SET_UPDATE_REQUEST)
Chris Sosa7cd23202013-10-15 17:22:57 -070096
Gilad Arnold08516112014-02-14 13:14:03 -080097 # Allocate temporary files for various devserver outputs.
98 self.pidfile = self._MakeTempFile('pid')
99 self.portfile = self._MakeTempFile('port')
100 self.logfile = self._MakeTempFile('log')
Chris Sosa7cd23202013-10-15 17:22:57 -0700101
Gilad Arnold08516112014-02-14 13:14:03 -0800102 # Initialize various runtime values.
103 self.devserver_url = self.port = self.pid = None
Amin Hassaniba847c82019-12-06 16:30:56 -0800104 self.devserver = None
Chris Sosa7cd23202013-10-15 17:22:57 -0700105
106 def tearDown(self):
Gilad Arnold08516112014-02-14 13:14:03 -0800107 """Kill the server, remove the test directory and temporary files."""
Amin Hassaniba847c82019-12-06 16:30:56 -0800108
109 self._StopServer()
Chris Sosa7cd23202013-10-15 17:22:57 -0700110
Gilad Arnold08516112014-02-14 13:14:03 -0800111 self._RemoveFile(self.pidfile)
112 self._RemoveFile(self.portfile)
Amin Hassaniba847c82019-12-06 16:30:56 -0800113 # If the unittest did not succeed, print out the devserver log.
114 if sys.exc_info() != (None, None, None):
115 with open(self.logfile, 'r') as f:
116 logging.info('--- BEGINNING OF DEVSERVER LOG ---')
117 logging.info(f.read())
118 logging.info('--- ENDING OF DEVSERVER LOG ---')
Gilad Arnold08516112014-02-14 13:14:03 -0800119 self._RemoveFile(self.logfile)
120 shutil.rmtree(self.test_data_path)
Chris Sosa7cd23202013-10-15 17:22:57 -0700121
122 # Helper methods begin here.
123
Amin Hassani0b4843b2019-09-25 16:38:56 -0700124 def _CreateLabelAndCopyUpdatePayloadFiles(self, label):
Gilad Arnold08516112014-02-14 13:14:03 -0800125 """Creates a label location and copies an image to it."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700126 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
Gilad Arnold08516112014-02-14 13:14:03 -0800127 label_dir = os.path.join(self.test_data_path, label)
128 os.makedirs(label_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700129 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
130 shutil.copy(os.path.join(update_dir, name), label_dir)
Gilad Arnold08516112014-02-14 13:14:03 -0800131
132 def _MakeTempFile(self, suffix):
133 """Return path of a newly created temporary file."""
134 with tempfile.NamedTemporaryFile(suffix='-devserver-%s' % suffix) as f:
135 name = f.name
136 f.close()
137
138 return name
139
140 def _RemoveFile(self, filename):
141 """Removes a file if it is present."""
142 if os.path.isfile(filename):
143 os.remove(filename)
144
145 def _ReadIntValueFromFile(self, path, desc):
146 """Reads a string from file and returns its conversion into an integer."""
147 if not os.path.isfile(path):
148 raise DevserverFailedToStart('Devserver did not drop %s (%r).' %
149 (desc, path))
150
151 with open(path) as f:
152 value_str = f.read()
153
154 try:
155 return int(value_str)
156 except ValueError:
157 raise DevserverFailedToStart('Devserver did not drop a valid value '
158 'in %s (%r).' % (desc, value_str))
159
160 def _StartServer(self, port=0):
Chris Sosa7cd23202013-10-15 17:22:57 -0700161 """Attempts to start devserver on |port|.
162
Gilad Arnold08516112014-02-14 13:14:03 -0800163 In the default case where port == 0, the server will bind to an arbitrary
Dan Shi2f136862016-02-11 15:38:38 -0800164 available port. If successful, this method will set the devserver's pid
Gilad Arnold08516112014-02-14 13:14:03 -0800165 (self.pid), actual listening port (self.port) and URL (self.devserver_url).
Chris Sosa7cd23202013-10-15 17:22:57 -0700166
167 Raises:
168 DevserverFailedToStart: If the devserver could not be started.
169 """
170 cmd = [
Chris Sosa7cd23202013-10-15 17:22:57 -0700171 os.path.join(self.src_dir, 'devserver.py'),
Chris Sosa7cd23202013-10-15 17:22:57 -0700172 '--static_dir', self.test_data_path,
173 '--pidfile', self.pidfile,
Gilad Arnold08516112014-02-14 13:14:03 -0800174 '--portfile', self.portfile,
Chris Sosa7cd23202013-10-15 17:22:57 -0700175 '--port', str(port),
Amin Hassaniba847c82019-12-06 16:30:56 -0800176 '--logfile', self.logfile,
177 ]
Chris Sosa7cd23202013-10-15 17:22:57 -0700178
179 # Pipe all output. Use logfile to get devserver log.
Amin Hassaniba847c82019-12-06 16:30:56 -0800180 self.devserver = subprocess.Popen(cmd, stderr=subprocess.PIPE,
181 stdout=subprocess.PIPE)
Chris Sosa7cd23202013-10-15 17:22:57 -0700182
Gilad Arnold08516112014-02-14 13:14:03 -0800183 # Wait for devserver to start, determining its actual serving port and URL.
Chris Sosa7cd23202013-10-15 17:22:57 -0700184 current_time = time.time()
185 deadline = current_time + DEVSERVER_START_TIMEOUT
Amin Hassaniba847c82019-12-06 16:30:56 -0800186 error = None
Chris Sosa7cd23202013-10-15 17:22:57 -0700187 while current_time < deadline:
Chris Sosa7cd23202013-10-15 17:22:57 -0700188 try:
Gilad Arnold08516112014-02-14 13:14:03 -0800189 self.port = self._ReadIntValueFromFile(self.portfile, 'portfile')
190 self.devserver_url = 'http://127.0.0.1:%d' % self.port
Gabe Black3b567202015-09-23 14:07:59 -0700191 self._MakeRPC(CHECK_HEALTH, timeout=1)
Chris Sosa7cd23202013-10-15 17:22:57 -0700192 break
Amin Hassaniba847c82019-12-06 16:30:56 -0800193 except Exception as e:
194 error = e
Gilad Arnold08516112014-02-14 13:14:03 -0800195 time.sleep(DEVSERVER_START_SLEEP)
196 current_time = time.time()
Chris Sosa7cd23202013-10-15 17:22:57 -0700197 else:
Amin Hassaniba847c82019-12-06 16:30:56 -0800198 raise DevserverFailedToStart(
199 'Devserver failed to start within timeout with error: %s' % error)
Chris Sosa7cd23202013-10-15 17:22:57 -0700200
Gilad Arnold08516112014-02-14 13:14:03 -0800201 # Retrieve PID.
202 self.pid = self._ReadIntValueFromFile(self.pidfile, 'pidfile')
Chris Sosa7cd23202013-10-15 17:22:57 -0700203
Amin Hassaniba847c82019-12-06 16:30:56 -0800204 def _StopServer(self):
205 """Stops the current running devserver."""
206 if not self.pid:
207 return
208
209 self.devserver.terminate()
210
211 # Just to flush the stdout/stderr so python3 doesn't complain about the
212 # unclosed file.
213 self.devserver.communicate()
214
215 self.devserver.wait()
216
217 self.pid = None
218 self.devserver = None
219
220
Amin Hassani0b4843b2019-09-25 16:38:56 -0700221 def VerifyHandleUpdate(self, label, use_test_payload=True,
222 appid='{DEV-BUILD}'):
Chris Sosa7cd23202013-10-15 17:22:57 -0700223 """Verifies that we can send an update request to the devserver.
224
225 This method verifies (using a fake update_request blob) that the devserver
226 can interpret the payload and give us back the right payload.
227
228 Args:
229 label: Label that update is served from e.g. <board>-release/<version>
230 use_test_payload: If set to true, expects to serve payload under
231 testdata/ and does extra checks i.e. compares hash and content of
232 payload.
Amin Hassaniba847c82019-12-06 16:30:56 -0800233 appid: The APP ID of the board.
Gilad Arnold08516112014-02-14 13:14:03 -0800234
Chris Sosa7cd23202013-10-15 17:22:57 -0700235 Returns:
236 url of the update payload if we verified the update.
237 """
238 update_label = '/'.join([UPDATE, label])
Amin Hassani0b4843b2019-09-25 16:38:56 -0700239 response = self._MakeRPC(
Amin Hassani6eec8792020-01-09 14:06:48 -0800240 update_label, data=UPDATE_REQUEST.substitute({'appid': appid}),
241 critical_update=True)
Chris Sosa7cd23202013-10-15 17:22:57 -0700242 self.assertNotEqual('', response)
Amin Hassani6eec8792020-01-09 14:06:48 -0800243 self.assertIn('deadline="now"', response)
Chris Sosa7cd23202013-10-15 17:22:57 -0700244
245 # Parse the response and check if it contains the right result.
246 dom = minidom.parseString(response)
247 update = dom.getElementsByTagName('updatecheck')[0]
248 expected_static_url = '/'.join([self.devserver_url, STATIC, label])
Amin Hassani0b4843b2019-09-25 16:38:56 -0700249 url = self.VerifyV3Response(update, expected_static_url)
Chris Sosa7cd23202013-10-15 17:22:57 -0700250
251 # Verify the image we download is correct since we already know what it is.
252 if use_test_payload:
Amin Hassani469f5702019-10-21 15:35:06 -0700253 connection = urllib.request.urlopen(url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800254 contents = connection.read().decode('utf-8')
Chris Sosa7cd23202013-10-15 17:22:57 -0700255 connection.close()
256 self.assertEqual('Developers, developers, developers!\n', contents)
257
258 return url
259
Amin Hassani0b4843b2019-09-25 16:38:56 -0700260 def VerifyV3Response(self, update, expected_static_url):
Chris Sosa7cd23202013-10-15 17:22:57 -0700261 """Verifies the update DOM from a v3 response and returns the url."""
262 # Parse the response and check if it contains the right result.
263 urls = update.getElementsByTagName('urls')[0]
264 url = urls.getElementsByTagName('url')[0]
265
266 static_url = url.getAttribute('codebase')
267 # Static url's end in /.
268 self.assertEqual(expected_static_url + '/', static_url)
269
270 manifest = update.getElementsByTagName('manifest')[0]
271 packages = manifest.getElementsByTagName('packages')[0]
272 package = packages.getElementsByTagName('package')[0]
273 filename = package.getAttribute('name')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700274 self.assertEqual(TEST_UPDATE_PAYLOAD_NAME, filename)
Chris Sosa7cd23202013-10-15 17:22:57 -0700275
Amin Hassaniba847c82019-12-06 16:30:56 -0800276 return os.path.join(static_url, filename)
Chris Sosa7cd23202013-10-15 17:22:57 -0700277
278 def _MakeRPC(self, rpc, data=None, timeout=None, **kwargs):
Gilad Arnold08516112014-02-14 13:14:03 -0800279 """Makes an RPC call to the devserver.
Chris Sosa7cd23202013-10-15 17:22:57 -0700280
281 Args:
Gilad Arnold08516112014-02-14 13:14:03 -0800282 rpc: The function to run on the devserver, e.g. 'stage'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700283 data: Optional post data to send.
284 timeout: Optional timeout to pass to urlopen.
Gilad Arnold08516112014-02-14 13:14:03 -0800285 kwargs: Optional arguments to the function, e.g. artifact_url='foo/bar'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700286
Gilad Arnold08516112014-02-14 13:14:03 -0800287 Returns:
288 The function output.
Chris Sosa7cd23202013-10-15 17:22:57 -0700289 """
290 request = '/'.join([self.devserver_url, rpc])
291 if kwargs:
292 # Join the kwargs to the URL.
Amin Hassani6eec8792020-01-09 14:06:48 -0800293 request += '?' + '&'.join('%s=%s' % (k, v) for k, v in kwargs.items())
Chris Sosa7cd23202013-10-15 17:22:57 -0700294
Amin Hassani6eec8792020-01-09 14:06:48 -0800295 response = (requests.post(request, data=data, timeout=timeout) if data
296 else requests.get(request, timeout=timeout))
297 response.raise_for_status()
298 return response.text
Chris Sosa7cd23202013-10-15 17:22:57 -0700299
300
Gilad Arnold08516112014-02-14 13:14:03 -0800301class AutoStartDevserverTestBase(DevserverTestBase):
302 """Test base class that automatically starts the devserver."""
303
304 def setUp(self):
305 """Initialize everything, then start the server."""
306 super(AutoStartDevserverTestBase, self).setUp()
307 self._StartServer()
308
309
Gilad Arnold7de05f72014-02-14 13:14:20 -0800310class DevserverStartTests(DevserverTestBase):
311 """Test that devserver starts up correctly."""
312
313 def testStartAnyPort(self):
314 """Starts the devserver, have it bind to an arbitrary available port."""
315 self._StartServer()
316
317 def testStartSpecificPort(self):
318 """Starts the devserver with a specific port."""
319 for _ in range(MAX_START_ATTEMPTS):
320 # This is a cheap hack to find an arbitrary unused port: we open a socket
321 # and bind it to port zero, then pull out the actual port number and
322 # close the socket. In all likelihood, this will leave us with an
323 # available port number that we can use for starting the devserver.
324 # However, this heuristic is susceptible to race conditions, hence the
325 # retry loop.
326 s = socket.socket()
327 s.bind(('', 0))
Gilad Arnold7de05f72014-02-14 13:14:20 -0800328 _, port = s.getsockname()
329 s.close()
330
331 self._StartServer(port=port)
Amin Hassaniba847c82019-12-06 16:30:56 -0800332 self._StopServer()
Gilad Arnold7de05f72014-02-14 13:14:20 -0800333
334
Gilad Arnold08516112014-02-14 13:14:03 -0800335class DevserverBasicTests(AutoStartDevserverTestBase):
336 """Short running tests for the devserver (no remote deps).
Chris Sosa7cd23202013-10-15 17:22:57 -0700337
338 These are technically not unittests because they depend on being able to
339 start a devserver locally which technically requires external resources so
340 they are lumped with the remote tests here.
341 """
342
343 def testHandleUpdateV3(self):
344 self.VerifyHandleUpdate(label=LABEL)
345
Chris Sosa7cd23202013-10-15 17:22:57 -0700346 def testXBuddyLocalAlias(self):
347 """Extensive local image xbuddy unittest.
348
349 This test verifies all the local xbuddy logic by creating a new local folder
350 with the necessary update items and verifies we can use all of them.
351 """
Chris Sosa7cd23202013-10-15 17:22:57 -0700352 build_id = 'x86-generic/R32-9999.0.0-a1'
353 xbuddy_path = 'x86-generic/R32-9999.0.0-a1/test'
354 build_dir = os.path.join(self.test_data_path, build_id)
355 os.makedirs(build_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700356
357 # Writing dummy files.
358 image_data = 'TEST IMAGE'
Chris Sosa7cd23202013-10-15 17:22:57 -0700359 test_image_file = os.path.join(build_dir,
360 devserver_constants.TEST_IMAGE_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700361 with open(test_image_file, 'w') as f:
362 f.write(image_data)
363
364 stateful_data = 'STATEFUL STUFFS'
Chris Sosa7cd23202013-10-15 17:22:57 -0700365 stateful_file = os.path.join(build_dir, devserver_constants.STATEFUL_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700366 with open(stateful_file, 'w') as f:
367 f.write(stateful_data)
Chris Sosa7cd23202013-10-15 17:22:57 -0700368
Amin Hassani0b4843b2019-09-25 16:38:56 -0700369 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
370 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
371 shutil.copy(os.path.join(update_dir, name), build_dir)
372 with open(os.path.join(build_dir, TEST_UPDATE_PAYLOAD_NAME), 'r') as f:
373 update_data = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700374
Amin Hassani0b4843b2019-09-25 16:38:56 -0700375 for item, data in zip(['full_payload', 'test', 'stateful'],
376 [update_data, image_data, stateful_data]):
Chris Sosa7cd23202013-10-15 17:22:57 -0700377
378 xbuddy_path = '/'.join([build_id, item])
379 logging.info('Testing xbuddy path %s', xbuddy_path)
380 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]))
381 self.assertEqual(response, data)
382
383 expected_dir = '/'.join([self.devserver_url, STATIC, build_id])
384 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
385 self.assertEqual(response, expected_dir)
386
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800387 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]),
388 relative_path=True)
389 self.assertEqual(response, build_id)
390
Chris Sosa7cd23202013-10-15 17:22:57 -0700391 xbuddy_path = '/'.join([build_id, 'test'])
392 logging.info('Testing for_update for %s', xbuddy_path)
393 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
394 expected_path = '/'.join([self.devserver_url, UPDATE, build_id])
395 self.assertTrue(response, expected_path)
396
397 logging.info('Verifying the actual payload data')
398 url = self.VerifyHandleUpdate(build_id, use_test_payload=False)
399 logging.info('Verify the actual content of the update payload')
Amin Hassani469f5702019-10-21 15:35:06 -0700400 connection = urllib.request.urlopen(url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800401 contents = connection.read().decode('utf-8')
Chris Sosa7cd23202013-10-15 17:22:57 -0700402 connection.close()
403 self.assertEqual(update_data, contents)
404
405 def testPidFile(self):
406 """Test that using a pidfile works correctly."""
407 with open(self.pidfile, 'r') as f:
408 pid = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700409 # Let's assert some process information about the devserver.
Dan Shi2f136862016-02-11 15:38:38 -0800410 self.assertTrue(pid.strip().isdigit())
Chris Sosa7cd23202013-10-15 17:22:57 -0700411 process = psutil.Process(int(pid))
412 self.assertTrue(process.is_running())
Amin Hassaniba847c82019-12-06 16:30:56 -0800413 self.assertIn('./devserver.py', process.cmdline())
Chris Sosa7cd23202013-10-15 17:22:57 -0700414
Amin Hassani28df4212019-10-28 10:16:50 -0700415 def testFileInfo(self):
416 """Verifies the fileinfo API."""
417 response = self._MakeRPC('/'.join(['api', 'fileinfo', LABEL,
418 TEST_UPDATE_PAYLOAD_NAME]))
419 self.assertEqual(
420 response,
421 '{"sha256": "hgfZ19hcsA5OGUN3f4SDtoO0kzT24o+JsGgjNVCVEy0=", '
422 '"size": 36}')
423
Chris Sosa7cd23202013-10-15 17:22:57 -0700424
Gilad Arnold08516112014-02-14 13:14:03 -0800425class DevserverExtendedTests(AutoStartDevserverTestBase):
Chris Sosa7cd23202013-10-15 17:22:57 -0700426 """Longer running integration tests that test interaction with Google Storage.
427
428 Note: due to the interaction with Google Storage, these tests both require
429 1) runner has access to the Google Storage bucket where builders store builds.
430 2) time. These tests actually download the artifacts needed.
431 """
432
xixuan52c2fba2016-05-20 17:02:48 -0700433 def testCrosAU(self):
434 """Tests core autotest workflow where we trigger CrOS auto-update.
435
436 It mainly tests the following API:
437 a. 'get_au_status'
438 b. 'handler_cleanup'
439 c. 'kill_au_proc'
440 """
441 host_name = '100.0.0.0'
xixuan2a0970a2016-08-10 12:12:44 -0700442 p = subprocess.Popen(['sleep 100'], shell=True, preexec_fn=os.setsid)
443 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700444 status = 'updating'
445 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
446 progress_tracker.WriteStatus(status)
447
448 logging.info('Retrieving auto-update status for process %d', pid)
449 response = self._MakeRPC('get_au_status', host_name=host_name, pid=pid)
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700450 self.assertFalse(json.loads(response)['finished'])
451 self.assertEqual(json.loads(response)['status'], status)
xixuan52c2fba2016-05-20 17:02:48 -0700452
453 progress_tracker.WriteStatus(cros_update_progress.FINISHED)
454 logging.info('Mock auto-update process is finished')
455 response = self._MakeRPC('get_au_status', host_name=host_name, pid=pid)
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700456 self.assertTrue(json.loads(response)['finished'])
457 self.assertEqual(json.loads(response)['status'],
458 cros_update_progress.FINISHED)
xixuan52c2fba2016-05-20 17:02:48 -0700459
460 logging.info('Delete auto-update track status file')
461 self.assertTrue(os.path.exists(progress_tracker.track_status_file))
462 self._MakeRPC('handler_cleanup', host_name=host_name, pid=pid)
463 self.assertFalse(os.path.exists(progress_tracker.track_status_file))
464
465 logging.info('Kill the left auto-update processes for host %s', host_name)
466 progress_tracker.WriteStatus(cros_update_progress.FINISHED)
467 response = self._MakeRPC('kill_au_proc', host_name=host_name)
468 self.assertEqual(response, 'True')
469 self.assertFalse(os.path.exists(progress_tracker.track_status_file))
470 self.assertFalse(cros_update_progress.IsProcessAlive(pid))
471
Amin Hassaniba847c82019-12-06 16:30:56 -0800472 p.terminate()
473 p.wait()
xixuan52c2fba2016-05-20 17:02:48 -0700474
Chris Sosa7cd23202013-10-15 17:22:57 -0700475 def testStageAndUpdate(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700476 """Tests core stage/update autotest workflow where with a test payload."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700477 build_id = 'eve-release/R78-12499.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700478 archive_url = 'gs://chromeos-image-archive/%s' % build_id
479
480 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
481 artifacts='full_payload,stateful')
482 self.assertEqual(response, 'False')
483
484 logging.info('Staging update artifacts')
485 self._MakeRPC(STAGE, archive_url=archive_url,
486 artifacts='full_payload,stateful')
487 logging.info('Staging complete. '
488 'Verifying files exist and are staged in the staging '
489 'directory.')
490 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
491 artifacts='full_payload,stateful')
492 self.assertEqual(response, 'True')
493 staged_dir = os.path.join(self.test_data_path, build_id)
494 self.assertTrue(os.path.isdir(staged_dir))
495 self.assertTrue(os.path.exists(
496 os.path.join(staged_dir, devserver_constants.UPDATE_FILE)))
497 self.assertTrue(os.path.exists(
Amin Hassani0b4843b2019-09-25 16:38:56 -0700498 os.path.join(staged_dir, devserver_constants.UPDATE_METADATA_FILE)))
499 self.assertTrue(os.path.exists(
Chris Sosa7cd23202013-10-15 17:22:57 -0700500 os.path.join(staged_dir, devserver_constants.STATEFUL_FILE)))
501
502 logging.info('Verifying we can update using the stage update artifacts.')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700503 self.VerifyHandleUpdate(build_id, use_test_payload=False,
504 appid='{01906EA2-3EB2-41F1-8F62-F0B7120EFD2E}')
Chris Sosa7cd23202013-10-15 17:22:57 -0700505
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700506 @unittest.skip('crbug.com/640063 Broken test.')
Chris Sosa7cd23202013-10-15 17:22:57 -0700507 def testStageAutotestAndGetPackages(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700508 """Another stage/update autotest workflow test with a test payload."""
509 build_id = 'eve-release/R69-10782.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700510 archive_url = 'gs://chromeos-image-archive/%s' % build_id
511 autotest_artifacts = 'autotest,test_suites,au_suite'
512 logging.info('Staging autotest artifacts (may take a while).')
513 self._MakeRPC(STAGE, archive_url=archive_url, artifacts=autotest_artifacts)
514
515 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
516 artifacts=autotest_artifacts)
517 self.assertEqual(response, 'True')
518
519 # Verify the files exist and are staged in the staging directory.
520 logging.info('Checking directories exist after we staged the files.')
521 staged_dir = os.path.join(self.test_data_path, build_id)
522 autotest_dir = os.path.join(staged_dir, 'autotest')
523 package_dir = os.path.join(autotest_dir, 'packages')
524 self.assertTrue(os.path.isdir(staged_dir))
525 self.assertTrue(os.path.isdir(autotest_dir))
526 self.assertTrue(os.path.isdir(package_dir))
527
528 control_files = self._MakeRPC(CONTROL_FILES, build=build_id,
529 suite_name='bvt')
530 logging.info('Checking for known control file in bvt suite.')
Amin Hassaniba847c82019-12-06 16:30:56 -0800531 self.assertIn('client/site_tests/platform_FilePerms/control', control_files)
Chris Sosa7cd23202013-10-15 17:22:57 -0700532
533 def testRemoteXBuddyAlias(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700534 """Another stage/update autotest workflow test with a test payload."""
535 build_id = 'eve-release/R69-10782.0.0'
536 xbuddy_path = 'remote/eve/R69-10782.0.0/full_payload'
537 xbuddy_bad_path = 'remote/eve/R32-9999.9999.9999'
Chris Sosa7cd23202013-10-15 17:22:57 -0700538 logging.info('Staging artifacts using xbuddy.')
539 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
540
541 logging.info('Verifying static url returned is valid.')
542 expected_static_url = '/'.join([self.devserver_url, STATIC, build_id])
543 self.assertEqual(response, expected_static_url)
544
545 logging.info('Checking for_update returns an update_url for what we just '
546 'staged.')
547 expected_update_url = '/'.join([self.devserver_url, UPDATE, build_id])
548 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
549 self.assertEqual(response, expected_update_url)
550
551 logging.info('Now give xbuddy a bad path.')
Amin Hassani6eec8792020-01-09 14:06:48 -0800552 self.assertRaises(requests.exceptions.RequestException,
Chris Sosa7cd23202013-10-15 17:22:57 -0700553 self._MakeRPC,
554 '/'.join([XBUDDY, xbuddy_bad_path]))
555
Prashanth Ba06d2d22014-03-07 15:35:19 -0800556 def testListImageDir(self):
557 """Verifies that we can list the contents of the image directory."""
558 build_id = 'x86-mario-release/R32-4810.0.0'
559 archive_url = 'gs://chromeos-image-archive/%s' % build_id
560 build_dir = os.path.join(self.test_data_path, build_id)
561 shutil.rmtree(build_dir, ignore_errors=True)
562
563 logging.info('checking for %s on an unstaged build.', LIST_IMAGE_DIR)
564 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800565 self.assertIn(archive_url, response)
566 self.assertIn('not been staged', response)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800567
568 logging.info('Checking for %s on a staged build.', LIST_IMAGE_DIR)
569 fake_file_name = 'fake_file'
570 try:
571 os.makedirs(build_dir)
572 open(os.path.join(build_dir, fake_file_name), 'w').close()
573 except OSError:
574 logging.error('Could not create files to imitate staged content. '
575 'Build dir %s, file %s', build_dir, fake_file_name)
576 raise
577 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800578 self.assertIn(fake_file_name, response)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800579 shutil.rmtree(build_dir, ignore_errors=True)
Chris Sosa7cd23202013-10-15 17:22:57 -0700580
Amin Hassani28df4212019-10-28 10:16:50 -0700581
Chris Sosa7cd23202013-10-15 17:22:57 -0700582if __name__ == '__main__':
Chris Sosa7cd23202013-10-15 17:22:57 -0700583 unittest.main()