[autotest] Add create_suite_job() site RPC.
Add create_suite_job() RPC to autotest using the site_rpc mechanism.
Also add support to atest to call this RPC.
The purpose of this RPC is to stage a build on the dev server (if
necessary), image N machines with it, and then run the desired
suite on those machines.
BUG=chromium-os:25573
TEST=atest suite create -b x86-mario -i x86-mario-release/R19-1675.0.0-a1-b1588 test_suites/control.dummy
Change-Id: I09288fe6ffc675e5b111f7f59e349015f52ebb8e
Reviewed-on: https://gerrit.chromium.org/gerrit/15279
Tested-by: Chris Masone <cmasone@chromium.org>
Commit-Ready: Scott Zawalski <scottz@chromium.org>
Reviewed-by: Scott Zawalski <scottz@chromium.org>
Tested-by: Scott Zawalski <scottz@chromium.org>
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
new file mode 100644
index 0000000..6793645
--- /dev/null
+++ b/frontend/afe/site_rpc_interface.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+__author__ = 'cmasone@chromium.org (Chris Masone)'
+
+import common
+import logging
+import sys
+from autotest_lib.client.common_lib import global_config
+from autotest_lib.client.common_lib.cros import dev_server
+# rpc_utils initializes django, which we can't do in unit tests.
+if 'unittest' not in sys.modules.keys():
+ # So, only load that module if we're not running unit tests.
+ from autotest_lib.frontend.afe import rpc_utils
+from autotest_lib.server.cros import control_file_getter, dynamic_suite
+
+
+class StageBuildFailure(Exception):
+ """Raised when the dev server throws 500 while staging a build."""
+ pass
+
+
+class ControlFileEmpty(Exception):
+ """Raised when the control file exists on the server, but can't be read."""
+ pass
+
+
+def _rpc_utils():
+ """Returns the rpc_utils module. MUST be mocked for unit tests."""
+ return rpc_utils
+
+
+def create_suite_job(suite_name, board, build):
+ """
+ Create a job to run a test suite on the given device with the given image.
+
+ When the timeout specified in the control file is reached, the
+ job is guaranteed to have completed and results will be available.
+
+ @param suite_name: the test suite to run, e.g. 'control.bvt'.
+ @param board: the kind of device to run the tests on.
+ @param build: unique name by which to refer to the image from now on.
+
+ @throws ControlFileNotFound if a unique suite control file doesn't exist.
+ @throws NoControlFileList if we can't list the control files at all.
+ @throws StageBuildFailure if the dev server throws 500 while staging build.
+ @throws ControlFileEmpty if the control file exists on the server, but
+ can't be read.
+
+ @return: the job ID of the suite; -1 on error.
+ """
+ # Ensure |build| is staged is on the dev server.
+ ds = dev_server.DevServer.create()
+ if not ds.trigger_download(build):
+ raise StageBuildFailure("Server error while staging " + build)
+
+ getter = control_file_getter.DevServerGetter.create(build, ds)
+ # Get the control file for the suite.
+ control_file_in = getter.get_control_file_contents_by_name(suite_name)
+ if not control_file_in:
+ raise ControlFileEmpty("Fetching %s returned no data." % suite_name)
+
+ # prepend build and board to the control file
+ control_file = dynamic_suite.inject_vars({'board': board, 'build': build},
+ control_file_in)
+
+ return _rpc_utils().create_job_common('%s-%s' % (build, suite_name),
+ priority='Medium',
+ control_type='Server',
+ control_file=control_file,
+ hostless=True)
diff --git a/frontend/afe/site_rpc_interface_unittest.py b/frontend/afe/site_rpc_interface_unittest.py
new file mode 100644
index 0000000..e784a06
--- /dev/null
+++ b/frontend/afe/site_rpc_interface_unittest.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for frontend/afe/site_rpc_interface.py."""
+
+import common
+import mox
+import unittest
+from autotest_lib.client.common_lib.cros import dev_server
+from autotest_lib.frontend.afe import site_rpc_interface
+from autotest_lib.server.cros import control_file_getter
+
+
+class SiteRpcInterfaceTest(mox.MoxTestBase):
+ """Unit tests for functions in site_rpc_interface.py.
+
+ @var _NAME: fake suite name.
+ @var _BOARD: fake board to reimage.
+ @var _BUILD: fake build with which to reimage.
+ """
+ _NAME = 'name'
+ _BOARD = 'board'
+ _BUILD = 'build'
+
+
+ class rpc_utils(object):
+ def create_job_common(self, name, **kwargs):
+ pass
+
+
+ def setUp(self):
+ super(SiteRpcInterfaceTest, self).setUp()
+ self.dev_server = self.mox.CreateMock(dev_server.DevServer)
+ self.mox.StubOutWithMock(dev_server.DevServer, 'create')
+ dev_server.DevServer.create().AndReturn(self.dev_server)
+
+
+ def _mockDevServerGetter(self):
+ self.getter = self.mox.CreateMock(control_file_getter.DevServerGetter)
+ self.mox.StubOutWithMock(control_file_getter.DevServerGetter, 'create')
+ control_file_getter.DevServerGetter.create(
+ mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(self.getter)
+
+
+ def _mockRpcUtils(self, to_return):
+ """Fake out the autotest rpc_utils module with a mockable class.
+
+ @param to_return: the value that rpc_utils.create_job_common() should
+ be mocked out to return.
+ """
+ r = self.mox.CreateMock(SiteRpcInterfaceTest.rpc_utils)
+ r.create_job_common(mox.And(mox.StrContains(self._NAME),
+ mox.StrContains(self._BUILD)),
+ priority='Medium',
+ control_type='Server',
+ control_file=mox.And(mox.StrContains(self._BOARD),
+ mox.StrContains(self._BUILD)),
+ hostless=True).AndReturn(to_return)
+ self.mox.StubOutWithMock(site_rpc_interface, '_rpc_utils')
+ site_rpc_interface._rpc_utils().AndReturn(r)
+
+
+ def testStageBuildFail(self):
+ """Ensure that a failure to stage the desired build fails the RPC."""
+ self.dev_server.trigger_download(self._BUILD).AndReturn(False)
+ self.mox.ReplayAll()
+ self.assertRaises(site_rpc_interface.StageBuildFailure,
+ site_rpc_interface.create_suite_job,
+ self._NAME,
+ self._BOARD,
+ self._BUILD)
+
+
+ def testGetControlFileFail(self):
+ """Ensure that a failure to get needed control file fails the RPC."""
+ self._mockDevServerGetter()
+ self.dev_server.trigger_download(self._BUILD).AndReturn(True)
+ self.getter.get_control_file_contents_by_name(self._NAME).AndReturn(
+ None)
+ self.mox.ReplayAll()
+ self.assertRaises(site_rpc_interface.ControlFileEmpty,
+ site_rpc_interface.create_suite_job,
+ self._NAME,
+ self._BOARD,
+ self._BUILD)
+
+
+ def testGetControlFileListFail(self):
+ """Ensure that a failure to get needed control file fails the RPC."""
+ self._mockDevServerGetter()
+ self.dev_server.trigger_download(self._BUILD).AndReturn(True)
+ self.getter.get_control_file_contents_by_name(self._NAME).AndRaise(
+ control_file_getter.NoControlFileList())
+ self.mox.ReplayAll()
+ self.assertRaises(control_file_getter.NoControlFileList,
+ site_rpc_interface.create_suite_job,
+ self._NAME,
+ self._BOARD,
+ self._BUILD)
+
+
+ def testCreateSuiteJobFail(self):
+ """Ensure that failure to schedule the suite job fails the RPC."""
+ self._mockDevServerGetter()
+ self.dev_server.trigger_download(self._BUILD).AndReturn(True)
+ self.getter.get_control_file_contents_by_name(self._NAME).AndReturn('f')
+ self._mockRpcUtils(-1)
+ self.mox.ReplayAll()
+ self.assertEquals(site_rpc_interface.create_suite_job(self._NAME,
+ self._BOARD,
+ self._BUILD),
+ -1)
+
+
+ def testCreateSuiteJobSuccess(self):
+ """Ensures that success results in a successful RPC."""
+ self._mockDevServerGetter()
+ self.dev_server.trigger_download(self._BUILD).AndReturn(True)
+ self.getter.get_control_file_contents_by_name(self._NAME).AndReturn('f')
+ job_id = 5
+ self._mockRpcUtils(job_id)
+ self.mox.ReplayAll()
+ self.assertEquals(site_rpc_interface.create_suite_job(self._NAME,
+ self._BOARD,
+ self._BUILD),
+ job_id)
+
+
+if __name__ == '__main__':
+ unittest.main()