blob: fbd20b4e12b720bafd34af75054bc6cf4fab3189 [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 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
Amin Hassaniba847c82019-12-06 16:30:56 -0800102 self.devserver = None
Chris Sosa7cd23202013-10-15 17:22:57 -0700103
104 def tearDown(self):
Gilad Arnold08516112014-02-14 13:14:03 -0800105 """Kill the server, remove the test directory and temporary files."""
Amin Hassaniba847c82019-12-06 16:30:56 -0800106
107 self._StopServer()
Chris Sosa7cd23202013-10-15 17:22:57 -0700108
Gilad Arnold08516112014-02-14 13:14:03 -0800109 self._RemoveFile(self.pidfile)
110 self._RemoveFile(self.portfile)
Amin Hassaniba847c82019-12-06 16:30:56 -0800111 # If the unittest did not succeed, print out the devserver log.
112 if sys.exc_info() != (None, None, None):
113 with open(self.logfile, 'r') as f:
114 logging.info('--- BEGINNING OF DEVSERVER LOG ---')
115 logging.info(f.read())
116 logging.info('--- ENDING OF DEVSERVER LOG ---')
Gilad Arnold08516112014-02-14 13:14:03 -0800117 self._RemoveFile(self.logfile)
118 shutil.rmtree(self.test_data_path)
Chris Sosa7cd23202013-10-15 17:22:57 -0700119
120 # Helper methods begin here.
121
Amin Hassani0b4843b2019-09-25 16:38:56 -0700122 def _CreateLabelAndCopyUpdatePayloadFiles(self, label):
Gilad Arnold08516112014-02-14 13:14:03 -0800123 """Creates a label location and copies an image to it."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700124 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
Gilad Arnold08516112014-02-14 13:14:03 -0800125 label_dir = os.path.join(self.test_data_path, label)
126 os.makedirs(label_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700127 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
128 shutil.copy(os.path.join(update_dir, name), label_dir)
Gilad Arnold08516112014-02-14 13:14:03 -0800129
130 def _MakeTempFile(self, suffix):
131 """Return path of a newly created temporary file."""
132 with tempfile.NamedTemporaryFile(suffix='-devserver-%s' % suffix) as f:
133 name = f.name
134 f.close()
135
136 return name
137
138 def _RemoveFile(self, filename):
139 """Removes a file if it is present."""
140 if os.path.isfile(filename):
141 os.remove(filename)
142
143 def _ReadIntValueFromFile(self, path, desc):
144 """Reads a string from file and returns its conversion into an integer."""
145 if not os.path.isfile(path):
146 raise DevserverFailedToStart('Devserver did not drop %s (%r).' %
147 (desc, path))
148
149 with open(path) as f:
150 value_str = f.read()
151
152 try:
153 return int(value_str)
154 except ValueError:
155 raise DevserverFailedToStart('Devserver did not drop a valid value '
156 'in %s (%r).' % (desc, value_str))
157
158 def _StartServer(self, port=0):
Chris Sosa7cd23202013-10-15 17:22:57 -0700159 """Attempts to start devserver on |port|.
160
Gilad Arnold08516112014-02-14 13:14:03 -0800161 In the default case where port == 0, the server will bind to an arbitrary
Dan Shi2f136862016-02-11 15:38:38 -0800162 available port. If successful, this method will set the devserver's pid
Gilad Arnold08516112014-02-14 13:14:03 -0800163 (self.pid), actual listening port (self.port) and URL (self.devserver_url).
Chris Sosa7cd23202013-10-15 17:22:57 -0700164
165 Raises:
166 DevserverFailedToStart: If the devserver could not be started.
167 """
168 cmd = [
Chris Sosa7cd23202013-10-15 17:22:57 -0700169 os.path.join(self.src_dir, 'devserver.py'),
Chris Sosa7cd23202013-10-15 17:22:57 -0700170 '--static_dir', self.test_data_path,
171 '--pidfile', self.pidfile,
Gilad Arnold08516112014-02-14 13:14:03 -0800172 '--portfile', self.portfile,
Chris Sosa7cd23202013-10-15 17:22:57 -0700173 '--port', str(port),
Amin Hassaniba847c82019-12-06 16:30:56 -0800174 '--logfile', self.logfile,
175 ]
Chris Sosa7cd23202013-10-15 17:22:57 -0700176
177 # Pipe all output. Use logfile to get devserver log.
Amin Hassaniba847c82019-12-06 16:30:56 -0800178 self.devserver = subprocess.Popen(cmd, stderr=subprocess.PIPE,
179 stdout=subprocess.PIPE)
Chris Sosa7cd23202013-10-15 17:22:57 -0700180
Gilad Arnold08516112014-02-14 13:14:03 -0800181 # Wait for devserver to start, determining its actual serving port and URL.
Chris Sosa7cd23202013-10-15 17:22:57 -0700182 current_time = time.time()
183 deadline = current_time + DEVSERVER_START_TIMEOUT
Amin Hassaniba847c82019-12-06 16:30:56 -0800184 error = None
Chris Sosa7cd23202013-10-15 17:22:57 -0700185 while current_time < deadline:
Chris Sosa7cd23202013-10-15 17:22:57 -0700186 try:
Gilad Arnold08516112014-02-14 13:14:03 -0800187 self.port = self._ReadIntValueFromFile(self.portfile, 'portfile')
188 self.devserver_url = 'http://127.0.0.1:%d' % self.port
Gabe Black3b567202015-09-23 14:07:59 -0700189 self._MakeRPC(CHECK_HEALTH, timeout=1)
Chris Sosa7cd23202013-10-15 17:22:57 -0700190 break
Amin Hassaniba847c82019-12-06 16:30:56 -0800191 except Exception as e:
192 error = e
Gilad Arnold08516112014-02-14 13:14:03 -0800193 time.sleep(DEVSERVER_START_SLEEP)
194 current_time = time.time()
Chris Sosa7cd23202013-10-15 17:22:57 -0700195 else:
Amin Hassaniba847c82019-12-06 16:30:56 -0800196 raise DevserverFailedToStart(
197 'Devserver failed to start within timeout with error: %s' % error)
Chris Sosa7cd23202013-10-15 17:22:57 -0700198
Gilad Arnold08516112014-02-14 13:14:03 -0800199 # Retrieve PID.
200 self.pid = self._ReadIntValueFromFile(self.pidfile, 'pidfile')
Chris Sosa7cd23202013-10-15 17:22:57 -0700201
Amin Hassaniba847c82019-12-06 16:30:56 -0800202 def _StopServer(self):
203 """Stops the current running devserver."""
204 if not self.pid:
205 return
206
207 self.devserver.terminate()
208
209 # Just to flush the stdout/stderr so python3 doesn't complain about the
210 # unclosed file.
211 self.devserver.communicate()
212
213 self.devserver.wait()
214
215 self.pid = None
216 self.devserver = None
217
218
Amin Hassani0b4843b2019-09-25 16:38:56 -0700219 def VerifyHandleUpdate(self, label, use_test_payload=True,
220 appid='{DEV-BUILD}'):
Chris Sosa7cd23202013-10-15 17:22:57 -0700221 """Verifies that we can send an update request to the devserver.
222
223 This method verifies (using a fake update_request blob) that the devserver
224 can interpret the payload and give us back the right payload.
225
226 Args:
227 label: Label that update is served from e.g. <board>-release/<version>
228 use_test_payload: If set to true, expects to serve payload under
229 testdata/ and does extra checks i.e. compares hash and content of
230 payload.
Amin Hassaniba847c82019-12-06 16:30:56 -0800231 appid: The APP ID of the board.
Gilad Arnold08516112014-02-14 13:14:03 -0800232
Chris Sosa7cd23202013-10-15 17:22:57 -0700233 Returns:
234 url of the update payload if we verified the update.
235 """
236 update_label = '/'.join([UPDATE, label])
Amin Hassani0b4843b2019-09-25 16:38:56 -0700237 response = self._MakeRPC(
238 update_label, data=UPDATE_REQUEST.substitute({'appid': appid}))
Chris Sosa7cd23202013-10-15 17:22:57 -0700239 self.assertNotEqual('', response)
240
241 # Parse the response and check if it contains the right result.
242 dom = minidom.parseString(response)
243 update = dom.getElementsByTagName('updatecheck')[0]
244 expected_static_url = '/'.join([self.devserver_url, STATIC, label])
Amin Hassani0b4843b2019-09-25 16:38:56 -0700245 url = self.VerifyV3Response(update, expected_static_url)
Chris Sosa7cd23202013-10-15 17:22:57 -0700246
247 # Verify the image we download is correct since we already know what it is.
248 if use_test_payload:
Amin Hassani469f5702019-10-21 15:35:06 -0700249 connection = urllib.request.urlopen(url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800250 contents = connection.read().decode('utf-8')
Chris Sosa7cd23202013-10-15 17:22:57 -0700251 connection.close()
252 self.assertEqual('Developers, developers, developers!\n', contents)
253
254 return url
255
Amin Hassani0b4843b2019-09-25 16:38:56 -0700256 def VerifyV3Response(self, update, expected_static_url):
Chris Sosa7cd23202013-10-15 17:22:57 -0700257 """Verifies the update DOM from a v3 response and returns the url."""
258 # Parse the response and check if it contains the right result.
259 urls = update.getElementsByTagName('urls')[0]
260 url = urls.getElementsByTagName('url')[0]
261
262 static_url = url.getAttribute('codebase')
263 # Static url's end in /.
264 self.assertEqual(expected_static_url + '/', static_url)
265
266 manifest = update.getElementsByTagName('manifest')[0]
267 packages = manifest.getElementsByTagName('packages')[0]
268 package = packages.getElementsByTagName('package')[0]
269 filename = package.getAttribute('name')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700270 self.assertEqual(TEST_UPDATE_PAYLOAD_NAME, filename)
Chris Sosa7cd23202013-10-15 17:22:57 -0700271
Amin Hassaniba847c82019-12-06 16:30:56 -0800272 return os.path.join(static_url, filename)
Chris Sosa7cd23202013-10-15 17:22:57 -0700273
274 def _MakeRPC(self, rpc, data=None, timeout=None, **kwargs):
Gilad Arnold08516112014-02-14 13:14:03 -0800275 """Makes an RPC call to the devserver.
Chris Sosa7cd23202013-10-15 17:22:57 -0700276
277 Args:
Gilad Arnold08516112014-02-14 13:14:03 -0800278 rpc: The function to run on the devserver, e.g. 'stage'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700279 data: Optional post data to send.
280 timeout: Optional timeout to pass to urlopen.
Gilad Arnold08516112014-02-14 13:14:03 -0800281 kwargs: Optional arguments to the function, e.g. artifact_url='foo/bar'.
Chris Sosa7cd23202013-10-15 17:22:57 -0700282
Gilad Arnold08516112014-02-14 13:14:03 -0800283 Returns:
284 The function output.
Chris Sosa7cd23202013-10-15 17:22:57 -0700285 """
286 request = '/'.join([self.devserver_url, rpc])
287 if kwargs:
288 # Join the kwargs to the URL.
Amin Hassani469f5702019-10-21 15:35:06 -0700289 request += '?' + '&'.join('%s=%s' % item for item in kwargs.items())
Chris Sosa7cd23202013-10-15 17:22:57 -0700290
Amin Hassaniba847c82019-12-06 16:30:56 -0800291 # Let's log output for all rpc's without timeouts because we only
292 # use timeouts to check to see if something is up and these checks tend
293 # to be small and so logging it will be extremely repetitive.
294 if not timeout:
295 logging.info('Making request using %s', request)
Chris Sosa7cd23202013-10-15 17:22:57 -0700296
Amin Hassaniba847c82019-12-06 16:30:56 -0800297 connection = urllib.request.urlopen(
298 request, data=data.encode('utf-8') if data else None, timeout=timeout)
299 output = connection.read().decode('utf-8')
300 connection.close()
Chris Sosa7cd23202013-10-15 17:22:57 -0700301 return output
302
303
Gilad Arnold08516112014-02-14 13:14:03 -0800304class AutoStartDevserverTestBase(DevserverTestBase):
305 """Test base class that automatically starts the devserver."""
306
307 def setUp(self):
308 """Initialize everything, then start the server."""
309 super(AutoStartDevserverTestBase, self).setUp()
310 self._StartServer()
311
312
Gilad Arnold7de05f72014-02-14 13:14:20 -0800313class DevserverStartTests(DevserverTestBase):
314 """Test that devserver starts up correctly."""
315
316 def testStartAnyPort(self):
317 """Starts the devserver, have it bind to an arbitrary available port."""
318 self._StartServer()
319
320 def testStartSpecificPort(self):
321 """Starts the devserver with a specific port."""
322 for _ in range(MAX_START_ATTEMPTS):
323 # This is a cheap hack to find an arbitrary unused port: we open a socket
324 # and bind it to port zero, then pull out the actual port number and
325 # close the socket. In all likelihood, this will leave us with an
326 # available port number that we can use for starting the devserver.
327 # However, this heuristic is susceptible to race conditions, hence the
328 # retry loop.
329 s = socket.socket()
330 s.bind(('', 0))
Gilad Arnold7de05f72014-02-14 13:14:20 -0800331 _, port = s.getsockname()
332 s.close()
333
334 self._StartServer(port=port)
Amin Hassaniba847c82019-12-06 16:30:56 -0800335 self._StopServer()
Gilad Arnold7de05f72014-02-14 13:14:20 -0800336
337
Gilad Arnold08516112014-02-14 13:14:03 -0800338class DevserverBasicTests(AutoStartDevserverTestBase):
339 """Short running tests for the devserver (no remote deps).
Chris Sosa7cd23202013-10-15 17:22:57 -0700340
341 These are technically not unittests because they depend on being able to
342 start a devserver locally which technically requires external resources so
343 they are lumped with the remote tests here.
344 """
345
346 def testHandleUpdateV3(self):
347 self.VerifyHandleUpdate(label=LABEL)
348
Chris Sosa7cd23202013-10-15 17:22:57 -0700349 def testXBuddyLocalAlias(self):
350 """Extensive local image xbuddy unittest.
351
352 This test verifies all the local xbuddy logic by creating a new local folder
353 with the necessary update items and verifies we can use all of them.
354 """
Chris Sosa7cd23202013-10-15 17:22:57 -0700355 build_id = 'x86-generic/R32-9999.0.0-a1'
356 xbuddy_path = 'x86-generic/R32-9999.0.0-a1/test'
357 build_dir = os.path.join(self.test_data_path, build_id)
358 os.makedirs(build_dir)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700359
360 # Writing dummy files.
361 image_data = 'TEST IMAGE'
Chris Sosa7cd23202013-10-15 17:22:57 -0700362 test_image_file = os.path.join(build_dir,
363 devserver_constants.TEST_IMAGE_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700364 with open(test_image_file, 'w') as f:
365 f.write(image_data)
366
367 stateful_data = 'STATEFUL STUFFS'
Chris Sosa7cd23202013-10-15 17:22:57 -0700368 stateful_file = os.path.join(build_dir, devserver_constants.STATEFUL_FILE)
Amin Hassani0b4843b2019-09-25 16:38:56 -0700369 with open(stateful_file, 'w') as f:
370 f.write(stateful_data)
Chris Sosa7cd23202013-10-15 17:22:57 -0700371
Amin Hassani0b4843b2019-09-25 16:38:56 -0700372 update_dir = os.path.join(self.src_dir, TEST_IMAGE_PATH)
373 for name in (TEST_UPDATE_PAYLOAD_NAME, TEST_UPDATE_PAYLOAD_METADATA_NAME):
374 shutil.copy(os.path.join(update_dir, name), build_dir)
375 with open(os.path.join(build_dir, TEST_UPDATE_PAYLOAD_NAME), 'r') as f:
376 update_data = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700377
Amin Hassani0b4843b2019-09-25 16:38:56 -0700378 for item, data in zip(['full_payload', 'test', 'stateful'],
379 [update_data, image_data, stateful_data]):
Chris Sosa7cd23202013-10-15 17:22:57 -0700380
381 xbuddy_path = '/'.join([build_id, item])
382 logging.info('Testing xbuddy path %s', xbuddy_path)
383 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]))
384 self.assertEqual(response, data)
385
386 expected_dir = '/'.join([self.devserver_url, STATIC, build_id])
387 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
388 self.assertEqual(response, expected_dir)
389
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800390 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]),
391 relative_path=True)
392 self.assertEqual(response, build_id)
393
Chris Sosa7cd23202013-10-15 17:22:57 -0700394 xbuddy_path = '/'.join([build_id, 'test'])
395 logging.info('Testing for_update for %s', xbuddy_path)
396 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
397 expected_path = '/'.join([self.devserver_url, UPDATE, build_id])
398 self.assertTrue(response, expected_path)
399
400 logging.info('Verifying the actual payload data')
401 url = self.VerifyHandleUpdate(build_id, use_test_payload=False)
402 logging.info('Verify the actual content of the update payload')
Amin Hassani469f5702019-10-21 15:35:06 -0700403 connection = urllib.request.urlopen(url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800404 contents = connection.read().decode('utf-8')
Chris Sosa7cd23202013-10-15 17:22:57 -0700405 connection.close()
406 self.assertEqual(update_data, contents)
407
408 def testPidFile(self):
409 """Test that using a pidfile works correctly."""
410 with open(self.pidfile, 'r') as f:
411 pid = f.read()
Chris Sosa7cd23202013-10-15 17:22:57 -0700412 # Let's assert some process information about the devserver.
Dan Shi2f136862016-02-11 15:38:38 -0800413 self.assertTrue(pid.strip().isdigit())
Chris Sosa7cd23202013-10-15 17:22:57 -0700414 process = psutil.Process(int(pid))
415 self.assertTrue(process.is_running())
Amin Hassaniba847c82019-12-06 16:30:56 -0800416 self.assertIn('./devserver.py', process.cmdline())
Chris Sosa7cd23202013-10-15 17:22:57 -0700417
Amin Hassani28df4212019-10-28 10:16:50 -0700418 def testFileInfo(self):
419 """Verifies the fileinfo API."""
420 response = self._MakeRPC('/'.join(['api', 'fileinfo', LABEL,
421 TEST_UPDATE_PAYLOAD_NAME]))
422 self.assertEqual(
423 response,
424 '{"sha256": "hgfZ19hcsA5OGUN3f4SDtoO0kzT24o+JsGgjNVCVEy0=", '
425 '"size": 36}')
426
Chris Sosa7cd23202013-10-15 17:22:57 -0700427
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
Amin Hassaniba847c82019-12-06 16:30:56 -0800475 p.terminate()
476 p.wait()
xixuan52c2fba2016-05-20 17:02:48 -0700477
Chris Sosa7cd23202013-10-15 17:22:57 -0700478 def testStageAndUpdate(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700479 """Tests core stage/update autotest workflow where with a test payload."""
Amin Hassani0b4843b2019-09-25 16:38:56 -0700480 build_id = 'eve-release/R78-12499.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700481 archive_url = 'gs://chromeos-image-archive/%s' % build_id
482
483 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
484 artifacts='full_payload,stateful')
485 self.assertEqual(response, 'False')
486
487 logging.info('Staging update artifacts')
488 self._MakeRPC(STAGE, archive_url=archive_url,
489 artifacts='full_payload,stateful')
490 logging.info('Staging complete. '
491 'Verifying files exist and are staged in the staging '
492 'directory.')
493 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
494 artifacts='full_payload,stateful')
495 self.assertEqual(response, 'True')
496 staged_dir = os.path.join(self.test_data_path, build_id)
497 self.assertTrue(os.path.isdir(staged_dir))
498 self.assertTrue(os.path.exists(
499 os.path.join(staged_dir, devserver_constants.UPDATE_FILE)))
500 self.assertTrue(os.path.exists(
Amin Hassani0b4843b2019-09-25 16:38:56 -0700501 os.path.join(staged_dir, devserver_constants.UPDATE_METADATA_FILE)))
502 self.assertTrue(os.path.exists(
Chris Sosa7cd23202013-10-15 17:22:57 -0700503 os.path.join(staged_dir, devserver_constants.STATEFUL_FILE)))
504
505 logging.info('Verifying we can update using the stage update artifacts.')
Amin Hassani0b4843b2019-09-25 16:38:56 -0700506 self.VerifyHandleUpdate(build_id, use_test_payload=False,
507 appid='{01906EA2-3EB2-41F1-8F62-F0B7120EFD2E}')
Chris Sosa7cd23202013-10-15 17:22:57 -0700508
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700509 @unittest.skip('crbug.com/640063 Broken test.')
Chris Sosa7cd23202013-10-15 17:22:57 -0700510 def testStageAutotestAndGetPackages(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700511 """Another stage/update autotest workflow test with a test payload."""
512 build_id = 'eve-release/R69-10782.0.0'
Chris Sosa7cd23202013-10-15 17:22:57 -0700513 archive_url = 'gs://chromeos-image-archive/%s' % build_id
514 autotest_artifacts = 'autotest,test_suites,au_suite'
515 logging.info('Staging autotest artifacts (may take a while).')
516 self._MakeRPC(STAGE, archive_url=archive_url, artifacts=autotest_artifacts)
517
518 response = self._MakeRPC(IS_STAGED, archive_url=archive_url,
519 artifacts=autotest_artifacts)
520 self.assertEqual(response, 'True')
521
522 # Verify the files exist and are staged in the staging directory.
523 logging.info('Checking directories exist after we staged the files.')
524 staged_dir = os.path.join(self.test_data_path, build_id)
525 autotest_dir = os.path.join(staged_dir, 'autotest')
526 package_dir = os.path.join(autotest_dir, 'packages')
527 self.assertTrue(os.path.isdir(staged_dir))
528 self.assertTrue(os.path.isdir(autotest_dir))
529 self.assertTrue(os.path.isdir(package_dir))
530
531 control_files = self._MakeRPC(CONTROL_FILES, build=build_id,
532 suite_name='bvt')
533 logging.info('Checking for known control file in bvt suite.')
Amin Hassaniba847c82019-12-06 16:30:56 -0800534 self.assertIn('client/site_tests/platform_FilePerms/control', control_files)
Chris Sosa7cd23202013-10-15 17:22:57 -0700535
536 def testRemoteXBuddyAlias(self):
Luis Hector Chaveza1518052018-06-14 08:19:34 -0700537 """Another stage/update autotest workflow test with a test payload."""
538 build_id = 'eve-release/R69-10782.0.0'
539 xbuddy_path = 'remote/eve/R69-10782.0.0/full_payload'
540 xbuddy_bad_path = 'remote/eve/R32-9999.9999.9999'
Chris Sosa7cd23202013-10-15 17:22:57 -0700541 logging.info('Staging artifacts using xbuddy.')
542 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), return_dir=True)
543
544 logging.info('Verifying static url returned is valid.')
545 expected_static_url = '/'.join([self.devserver_url, STATIC, build_id])
546 self.assertEqual(response, expected_static_url)
547
548 logging.info('Checking for_update returns an update_url for what we just '
549 'staged.')
550 expected_update_url = '/'.join([self.devserver_url, UPDATE, build_id])
551 response = self._MakeRPC('/'.join([XBUDDY, xbuddy_path]), for_update=True)
552 self.assertEqual(response, expected_update_url)
553
554 logging.info('Now give xbuddy a bad path.')
Amin Hassani469f5702019-10-21 15:35:06 -0700555 self.assertRaises(urllib.error.HTTPError,
Chris Sosa7cd23202013-10-15 17:22:57 -0700556 self._MakeRPC,
557 '/'.join([XBUDDY, xbuddy_bad_path]))
558
Prashanth Ba06d2d22014-03-07 15:35:19 -0800559 def testListImageDir(self):
560 """Verifies that we can list the contents of the image directory."""
561 build_id = 'x86-mario-release/R32-4810.0.0'
562 archive_url = 'gs://chromeos-image-archive/%s' % build_id
563 build_dir = os.path.join(self.test_data_path, build_id)
564 shutil.rmtree(build_dir, ignore_errors=True)
565
566 logging.info('checking for %s on an unstaged build.', LIST_IMAGE_DIR)
567 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800568 self.assertIn(archive_url, response)
569 self.assertIn('not been staged', response)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800570
571 logging.info('Checking for %s on a staged build.', LIST_IMAGE_DIR)
572 fake_file_name = 'fake_file'
573 try:
574 os.makedirs(build_dir)
575 open(os.path.join(build_dir, fake_file_name), 'w').close()
576 except OSError:
577 logging.error('Could not create files to imitate staged content. '
578 'Build dir %s, file %s', build_dir, fake_file_name)
579 raise
580 response = self._MakeRPC(LIST_IMAGE_DIR, archive_url=archive_url)
Amin Hassaniba847c82019-12-06 16:30:56 -0800581 self.assertIn(fake_file_name, response)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800582 shutil.rmtree(build_dir, ignore_errors=True)
Chris Sosa7cd23202013-10-15 17:22:57 -0700583
Amin Hassani28df4212019-10-28 10:16:50 -0700584
Chris Sosa7cd23202013-10-15 17:22:57 -0700585if __name__ == '__main__':
586 logging_format = '%(levelname)-8s: %(message)s'
587 logging.basicConfig(level=logging.DEBUG, format=logging_format)
588 unittest.main()