dev_server_unittest.py revision 580717f35931982b0a98fef941ecf445a8092348
1#!/usr/bin/python 2# 3# Copyright (c) 2012 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"""Unit tests for client/common_lib/cros/dev_server.py.""" 8 9import httplib 10import mox 11import StringIO 12import time 13import unittest 14import urllib2 15 16from autotest_lib.client.common_lib import error 17from autotest_lib.client.common_lib import global_config 18from autotest_lib.client.common_lib.cros import dev_server 19from autotest_lib.client.common_lib.cros import retry 20 21def retry_mock(ExceptionToCheck, timeout_min): 22 """A mock retry decorator to use in place of the actual one for testing. 23 24 @param ExceptionToCheck: the exception to check. 25 @param timeout_mins: Amount of time in mins to wait before timing out. 26 27 """ 28 def inner_retry(func): 29 """The actual decorator. 30 31 @param func: Function to be called in decorator. 32 33 """ 34 return func 35 36 return inner_retry 37 38 39class DevServerTest(mox.MoxTestBase): 40 """Unit tests for dev_server.DevServer. 41 42 @var _HOST: fake dev server host address. 43 """ 44 45 _HOST = 'http://nothing' 46 _CRASH_HOST = 'http://nothing-crashed' 47 _CONFIG = global_config.global_config 48 49 50 def setUp(self): 51 super(DevServerTest, self).setUp() 52 self.crash_server = dev_server.CrashServer(DevServerTest._CRASH_HOST) 53 self.dev_server = dev_server.ImageServer(DevServerTest._HOST) 54 self.mox.StubOutWithMock(urllib2, 'urlopen') 55 56 57 def testSimpleResolve(self): 58 """One devserver, verify we resolve to it.""" 59 self.mox.StubOutWithMock(dev_server, '_get_dev_server_list') 60 self.mox.StubOutWithMock(dev_server.DevServer, 'devserver_healthy') 61 dev_server._get_dev_server_list().AndReturn([DevServerTest._HOST]) 62 dev_server.DevServer.devserver_healthy(DevServerTest._HOST).AndReturn( 63 True) 64 self.mox.ReplayAll() 65 devserver = dev_server.ImageServer.resolve('my_build') 66 self.assertEquals(devserver.url(), DevServerTest._HOST) 67 68 69 def testResolveWithFailure(self): 70 """Ensure we rehash on a failed ping on a bad_host.""" 71 self.mox.StubOutWithMock(dev_server, '_get_dev_server_list') 72 bad_host, good_host = 'http://bad_host:99', 'http://good_host:8080' 73 dev_server._get_dev_server_list().AndReturn([bad_host, good_host]) 74 75 # Mock out bad ping failure to bad_host by raising devserver exception. 76 urllib2.urlopen(mox.StrContains(bad_host), data=None).AndRaise( 77 dev_server.DevServerException()) 78 # Good host is good. 79 to_return = StringIO.StringIO('{"free_disk": 1024}') 80 urllib2.urlopen(mox.StrContains(good_host), data=None).AndReturn(to_return) 81 82 self.mox.ReplayAll() 83 host = dev_server.ImageServer.resolve(0) # Using 0 as it'll hash to 0. 84 self.assertEquals(host.url(), good_host) 85 self.mox.VerifyAll() 86 87 88 def testResolveWithFailureURLError(self): 89 """Ensure we rehash on a failed ping on a bad_host after urlerror.""" 90 # Retry mock just return the original method. 91 retry.retry = retry_mock 92 self.mox.StubOutWithMock(dev_server, '_get_dev_server_list') 93 bad_host, good_host = 'http://bad_host:99', 'http://good_host:8080' 94 dev_server._get_dev_server_list().AndReturn([bad_host, good_host]) 95 96 # Mock out bad ping failure to bad_host by raising devserver exception. 97 urllib2.urlopen(mox.StrContains(bad_host), 98 data=None).MultipleTimes().AndRaise( 99 urllib2.URLError('urlopen connection timeout')) 100 101 # Good host is good. 102 to_return = StringIO.StringIO('{"free_disk": 1024}') 103 urllib2.urlopen(mox.StrContains(good_host), 104 data=None).AndReturn(to_return) 105 106 self.mox.ReplayAll() 107 host = dev_server.ImageServer.resolve(0) # Using 0 as it'll hash to 0. 108 self.assertEquals(host.url(), good_host) 109 self.mox.VerifyAll() 110 111 112 def testResolveWithManyDevservers(self): 113 """Should be able to return different urls with multiple devservers.""" 114 self.mox.StubOutWithMock(dev_server.ImageServer, 'servers') 115 self.mox.StubOutWithMock(dev_server.DevServer, 'devserver_healthy') 116 117 host0_expected = 'http://host0:8080' 118 host1_expected = 'http://host1:8082' 119 120 dev_server.ImageServer.servers().MultipleTimes().AndReturn( 121 [host0_expected, host1_expected]) 122 dev_server.DevServer.devserver_healthy(host0_expected).AndReturn(True) 123 dev_server.DevServer.devserver_healthy(host1_expected).AndReturn(True) 124 125 self.mox.ReplayAll() 126 host0 = dev_server.ImageServer.resolve(0) 127 host1 = dev_server.ImageServer.resolve(1) 128 self.mox.VerifyAll() 129 130 self.assertEqual(host0.url(), host0_expected) 131 self.assertEqual(host1.url(), host1_expected) 132 133 134 def _returnHttpServerError(self): 135 e500 = urllib2.HTTPError(url='', 136 code=httplib.INTERNAL_SERVER_ERROR, 137 msg='', 138 hdrs=None, 139 fp=StringIO.StringIO('Expected.')) 140 urllib2.urlopen(mox.IgnoreArg()).AndRaise(e500) 141 142 143 def _returnHttpForbidden(self): 144 e403 = urllib2.HTTPError(url='', 145 code=httplib.FORBIDDEN, 146 msg='', 147 hdrs=None, 148 fp=StringIO.StringIO('Expected.')) 149 urllib2.urlopen(mox.IgnoreArg()).AndRaise(e403) 150 151 152 def testSuccessfulTriggerDownloadSync(self): 153 """Call the dev server's download method with synchronous=True.""" 154 name = 'fake/image' 155 self.mox.StubOutWithMock(dev_server.ImageServer, 'finish_download') 156 to_return = StringIO.StringIO('Success') 157 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 158 mox.StrContains(name), 159 mox.StrContains('stage?'))).AndReturn(to_return) 160 to_return = StringIO.StringIO('True') 161 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 162 mox.StrContains(name), 163 mox.StrContains('is_staged'))).AndReturn( 164 to_return) 165 self.dev_server.finish_download(name) 166 167 # Synchronous case requires a call to finish download. 168 self.mox.ReplayAll() 169 self.dev_server.trigger_download(name, synchronous=True) 170 self.mox.VerifyAll() 171 172 173 def testSuccessfulTriggerDownloadASync(self): 174 """Call the dev server's download method with synchronous=False.""" 175 name = 'fake/image' 176 to_return = StringIO.StringIO('Success') 177 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 178 mox.StrContains(name), 179 mox.StrContains('stage?'))).AndReturn(to_return) 180 to_return = StringIO.StringIO('True') 181 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 182 mox.StrContains(name), 183 mox.StrContains('is_staged'))).AndReturn( 184 to_return) 185 186 self.mox.ReplayAll() 187 self.dev_server.trigger_download(name, synchronous=False) 188 self.mox.VerifyAll() 189 190 191 def testURLErrorRetryTriggerDownload(self): 192 """Should retry on URLError, but pass through real exception.""" 193 self.mox.StubOutWithMock(time, 'sleep') 194 195 refused = urllib2.URLError('[Errno 111] Connection refused') 196 urllib2.urlopen(mox.IgnoreArg()).AndRaise(refused) 197 time.sleep(mox.IgnoreArg()) 198 self._returnHttpForbidden() 199 self.mox.ReplayAll() 200 self.assertRaises(dev_server.DevServerException, 201 self.dev_server.trigger_download, 202 '') 203 204 205 def testErrorTriggerDownload(self): 206 """Should call the dev server's download method, fail gracefully.""" 207 self._returnHttpServerError() 208 self.mox.ReplayAll() 209 self.assertRaises(dev_server.DevServerException, 210 self.dev_server.trigger_download, 211 '') 212 213 214 def testForbiddenTriggerDownload(self): 215 """Should call the dev server's download method, get exception.""" 216 self._returnHttpForbidden() 217 self.mox.ReplayAll() 218 self.assertRaises(dev_server.DevServerException, 219 self.dev_server.trigger_download, 220 '') 221 222 223 def testSuccessfulFinishDownload(self): 224 """Should successfully call the dev server's finish download method.""" 225 name = 'fake/image' 226 to_return = StringIO.StringIO('Success') 227 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 228 mox.StrContains(name), 229 mox.StrContains('stage?'))).AndReturn(to_return) 230 to_return = StringIO.StringIO('True') 231 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 232 mox.StrContains(name), 233 mox.StrContains('is_staged'))).AndReturn( 234 to_return) 235 236 # Synchronous case requires a call to finish download. 237 self.mox.ReplayAll() 238 self.dev_server.finish_download(name) # Raises on failure. 239 self.mox.VerifyAll() 240 241 242 def testErrorFinishDownload(self): 243 """Should call the dev server's finish download method, fail gracefully. 244 """ 245 self._returnHttpServerError() 246 self.mox.ReplayAll() 247 self.assertRaises(dev_server.DevServerException, 248 self.dev_server.finish_download, 249 '') 250 251 252 def testListControlFiles(self): 253 """Should successfully list control files from the dev server.""" 254 name = 'fake/build' 255 control_files = ['file/one', 'file/two'] 256 to_return = StringIO.StringIO('\n'.join(control_files)) 257 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 258 mox.StrContains(name))).AndReturn(to_return) 259 self.mox.ReplayAll() 260 paths = self.dev_server.list_control_files(name) 261 self.assertEquals(len(paths), 2) 262 for f in control_files: 263 self.assertTrue(f in paths) 264 265 266 def testFailedListControlFiles(self): 267 """Should call the dev server's list-files method, get exception.""" 268 self._returnHttpServerError() 269 self.mox.ReplayAll() 270 self.assertRaises(dev_server.DevServerException, 271 self.dev_server.list_control_files, 272 '') 273 274 275 def testExplodingListControlFiles(self): 276 """Should call the dev server's list-files method, get exception.""" 277 self._returnHttpForbidden() 278 self.mox.ReplayAll() 279 self.assertRaises(dev_server.DevServerException, 280 self.dev_server.list_control_files, 281 '') 282 283 284 def testGetControlFile(self): 285 """Should successfully get a control file from the dev server.""" 286 name = 'fake/build' 287 file = 'file/one' 288 contents = 'Multi-line\nControl File Contents\n' 289 to_return = StringIO.StringIO(contents) 290 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 291 mox.StrContains(name), 292 mox.StrContains(file))).AndReturn(to_return) 293 self.mox.ReplayAll() 294 self.assertEquals(self.dev_server.get_control_file(name, file), 295 contents) 296 297 298 def testErrorGetControlFile(self): 299 """Should try to get the contents of a control file, get exception.""" 300 self._returnHttpServerError() 301 self.mox.ReplayAll() 302 self.assertRaises(dev_server.DevServerException, 303 self.dev_server.get_control_file, 304 '', '') 305 306 307 def testForbiddenGetControlFile(self): 308 """Should try to get the contents of a control file, get exception.""" 309 self._returnHttpForbidden() 310 self.mox.ReplayAll() 311 self.assertRaises(dev_server.DevServerException, 312 self.dev_server.get_control_file, 313 '', '') 314 315 316 def testGetLatestBuild(self): 317 """Should successfully return a build for a given target.""" 318 self.mox.StubOutWithMock(dev_server.ImageServer, 'servers') 319 self.mox.StubOutWithMock(dev_server.DevServer, 'devserver_healthy') 320 321 dev_server.ImageServer.servers().AndReturn([self._HOST]) 322 dev_server.DevServer.devserver_healthy(self._HOST).AndReturn(True) 323 324 target = 'x86-generic-release' 325 build_string = 'R18-1586.0.0-a1-b1514' 326 to_return = StringIO.StringIO(build_string) 327 urllib2.urlopen(mox.And(mox.StrContains(self._HOST), 328 mox.StrContains(target))).AndReturn(to_return) 329 self.mox.ReplayAll() 330 build = dev_server.ImageServer.get_latest_build(target) 331 self.assertEquals(build_string, build) 332 333 334 def testGetLatestBuildWithManyDevservers(self): 335 """Should successfully return newest build with multiple devservers.""" 336 self.mox.StubOutWithMock(dev_server.ImageServer, 'servers') 337 self.mox.StubOutWithMock(dev_server.DevServer, 'devserver_healthy') 338 339 host0_expected = 'http://host0:8080' 340 host1_expected = 'http://host1:8082' 341 342 dev_server.ImageServer.servers().MultipleTimes().AndReturn( 343 [host0_expected, host1_expected]) 344 345 dev_server.DevServer.devserver_healthy(host0_expected).AndReturn(True) 346 dev_server.DevServer.devserver_healthy(host1_expected).AndReturn(True) 347 348 target = 'x86-generic-release' 349 build_string1 = 'R9-1586.0.0-a1-b1514' 350 build_string2 = 'R19-1586.0.0-a1-b3514' 351 to_return1 = StringIO.StringIO(build_string1) 352 to_return2 = StringIO.StringIO(build_string2) 353 urllib2.urlopen(mox.And(mox.StrContains(host0_expected), 354 mox.StrContains(target))).AndReturn(to_return1) 355 urllib2.urlopen(mox.And(mox.StrContains(host1_expected), 356 mox.StrContains(target))).AndReturn(to_return2) 357 358 self.mox.ReplayAll() 359 build = dev_server.ImageServer.get_latest_build(target) 360 self.assertEquals(build_string2, build) 361 362 363 def testCrashesAreSetToTheCrashServer(self): 364 """Should send symbolicate dump rpc calls to crash_server.""" 365 hv = 'iliketacos' 366 self.mox.ReplayAll() 367 call = self.crash_server.build_call('symbolicate_dump') 368 self.assertTrue(call.startswith(self._CRASH_HOST)) 369 370 371 def _stageTestHelper(self, artifacts=[], files=[], archive_url=None): 372 """Helper to test combos of files/artifacts/urls with stage call.""" 373 expected_archive_url = archive_url 374 if not archive_url: 375 expected_archive_url = 'gs://my_default_url' 376 self.mox.StubOutWithMock(dev_server, '_get_image_storage_server') 377 dev_server._get_image_storage_server().AndReturn( 378 'gs://my_default_url') 379 name = 'fake/image' 380 else: 381 # This is embedded in the archive_url. Not needed. 382 name = '' 383 384 to_return = StringIO.StringIO('Success') 385 urllib2.urlopen(mox.And(mox.StrContains(expected_archive_url), 386 mox.StrContains(name), 387 mox.StrContains('artifacts=%s' % 388 ','.join(artifacts)), 389 mox.StrContains('files=%s' % ','.join(files)), 390 mox.StrContains('stage?'))).AndReturn(to_return) 391 to_return = StringIO.StringIO('True') 392 urllib2.urlopen(mox.And(mox.StrContains(expected_archive_url), 393 mox.StrContains(name), 394 mox.StrContains('artifacts=%s' % 395 ','.join(artifacts)), 396 mox.StrContains('files=%s' % ','.join(files)), 397 mox.StrContains('is_staged'))).AndReturn( 398 to_return) 399 400 self.mox.ReplayAll() 401 self.dev_server.stage_artifacts(name, artifacts, files, archive_url) 402 self.mox.VerifyAll() 403 404 405 def testStageArtifactsBasic(self): 406 """Basic functionality to stage artifacts (similar to trigger_download). 407 """ 408 self._stageTestHelper(artifacts=['full_payload', 'stateful']) 409 410 411 def testStageArtifactsBasicWithFiles(self): 412 """Basic functionality to stage artifacts (similar to trigger_download). 413 """ 414 self._stageTestHelper(artifacts=['full_payload', 'stateful'], 415 files=['taco_bell.coupon']) 416 417 418 def testStageArtifactsOnlyFiles(self): 419 """Test staging of only file artifacts.""" 420 self._stageTestHelper(files=['tasty_taco_bell.coupon']) 421 422 423 def testStageWithArchiveURL(self): 424 """Basic functionality to stage artifacts (similar to trigger_download). 425 """ 426 self._stageTestHelper(files=['tasty_taco_bell.coupon'], 427 archive_url='gs://tacos_galore/my/dir') 428 429 430 def testStagedFileUrl(self): 431 """Sanity tests that the staged file url looks right.""" 432 devserver_label = 'x86-mario-release/R30-1234.0.0' 433 url = self.dev_server.get_staged_file_url('stateful.tgz', 434 devserver_label) 435 expected_url = '/'.join([self._HOST, 'static', devserver_label, 436 'stateful.tgz']) 437 self.assertEquals(url, expected_url) 438 439 devserver_label = 'something_crazy/that/you_MIGHT/hate' 440 url = self.dev_server.get_staged_file_url('chromiumos_image.bin', 441 devserver_label) 442 expected_url = '/'.join([self._HOST, 'static', devserver_label, 443 'chromiumos_image.bin']) 444 self.assertEquals(url, expected_url) 445 446 447 def _StageTimeoutHelper(self): 448 """Helper class for testing staging timeout.""" 449 self.mox.StubOutWithMock(dev_server.ImageServer, 'call_and_wait') 450 dev_server.ImageServer.call_and_wait( 451 call_name='stage', 452 artifacts=mox.IgnoreArg(), 453 files=mox.IgnoreArg(), 454 archive_url=mox.IgnoreArg(), 455 error_message=mox.IgnoreArg()).AndRaise(error.TimeoutException) 456 457 458 def test_StageArtifactsTimeout(self): 459 """Test DevServerException is raised when stage_artifacts timed out.""" 460 self._StageTimeoutHelper() 461 self.mox.ReplayAll() 462 self.assertRaises(dev_server.DevServerException, 463 self.dev_server.stage_artifacts, 464 image='fake/image', artifacts=['full_payload']) 465 self.mox.VerifyAll() 466 467 468 def test_TriggerDownloadTimeout(self): 469 """Test DevServerException is raised when trigger_download timed out.""" 470 self._StageTimeoutHelper() 471 self.mox.ReplayAll() 472 self.assertRaises(dev_server.DevServerException, 473 self.dev_server.trigger_download, 474 image='fake/image') 475 self.mox.VerifyAll() 476 477 478 def test_FinishDownloadTimeout(self): 479 """Test DevServerException is raised when finish_download timed out.""" 480 self._StageTimeoutHelper() 481 self.mox.ReplayAll() 482 self.assertRaises(dev_server.DevServerException, 483 self.dev_server.finish_download, 484 image='fake/image') 485 self.mox.VerifyAll() 486 487 488 def test_compare_load(self): 489 """Test load comparison logic. 490 """ 491 load_high_cpu = {'devserver': 'http://devserver_1:8082', 492 dev_server.DevServer.CPU_LOAD: 100.0, 493 dev_server.DevServer.NETWORK_IO: 1024*1024*1.0, 494 dev_server.DevServer.DISK_IO: 1024*1024.0} 495 load_high_network = {'devserver': 'http://devserver_1:8082', 496 dev_server.DevServer.CPU_LOAD: 1.0, 497 dev_server.DevServer.NETWORK_IO: 1024*1024*100.0, 498 dev_server.DevServer.DISK_IO: 1024*1024*1.0} 499 load_1 = {'devserver': 'http://devserver_1:8082', 500 dev_server.DevServer.CPU_LOAD: 1.0, 501 dev_server.DevServer.NETWORK_IO: 1024*1024*1.0, 502 dev_server.DevServer.DISK_IO: 1024*1024*2.0} 503 load_2 = {'devserver': 'http://devserver_1:8082', 504 dev_server.DevServer.CPU_LOAD: 1.0, 505 dev_server.DevServer.NETWORK_IO: 1024*1024*1.0, 506 dev_server.DevServer.DISK_IO: 1024*1024*1.0} 507 self.assertFalse(dev_server._is_load_healthy(load_high_cpu)) 508 self.assertFalse(dev_server._is_load_healthy(load_high_network)) 509 self.assertTrue(dev_server._compare_load(load_1, load_2) > 0) 510 511 512if __name__ == "__main__": 513 unittest.main() 514