Shows the image when clone a suite job or its child job.
BUG=chromium:485813
TEST=unit test and manual test.
Change-Id: I044991928513cea2c06eb6db8b31eeba6ea29289
Reviewed-on: https://chromium-review.googlesource.com/348482
Commit-Ready: Michael Tang <ntang@chromium.org>
Tested-by: Michael Tang <ntang@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Reviewed-by: Michael Tang <ntang@chromium.org>
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index f7c8dcb..dff4165 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -31,12 +31,14 @@
__author__ = 'showard@google.com (Steve Howard)'
+import ast
import sys
import datetime
import logging
from django.db.models import Count
import common
+from autotest_lib.client.common_lib import control_data
from autotest_lib.client.common_lib import priorities
from autotest_lib.client.common_lib.cros import dev_server
from autotest_lib.client.common_lib.cros.graphite import autotest_stats
@@ -890,7 +892,7 @@
def create_job_page_handler(name, priority, control_file, control_type,
image=None, hostless=False, firmware_rw_build=None,
firmware_ro_build=None, test_source_build=None,
- **kwargs):
+ is_cloning = False, **kwargs):
"""\
Create and enqueue a job.
@@ -905,11 +907,16 @@
None, i.e., RO firmware will not be updated.
@param test_source_build: Build to be used to retrieve test code. Default
to None.
+ @param is_cloning: True if creating a cloning job.
@param kwargs extra args that will be required by create_suite_job or
create_job.
@returns The created Job id number.
"""
+ if is_cloning:
+ logging.info('Start to clone a new job')
+ else:
+ logging.info('Start to create a new job')
control_file = rpc_utils.encode_ascii(control_file)
if not control_file:
raise model_logic.ValidationError({
@@ -924,9 +931,10 @@
builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
return site_rpc_interface.create_suite_job(
name=name, control_file=control_file, priority=priority,
- builds=builds, test_source_build=test_source_build, **kwargs)
+ builds=builds, test_source_build=test_source_build,
+ is_cloning=is_cloning, **kwargs)
return create_job(name, priority, control_file, control_type, image=image,
- hostless=hostless, **kwargs)
+ hostless=hostless, is_cloning=is_cloning, **kwargs)
@rpc_utils.route_rpc_to_master
@@ -938,7 +946,7 @@
reboot_before=None, reboot_after=None, parse_failed_repair=None,
hostless=False, keyvals=None, drone_set=None, image=None,
parent_job_id=None, test_retry=0, run_reset=True,
- require_ssp=None, args=(), **kwargs):
+ require_ssp=None, args=(), is_cloning=False, **kwargs):
"""\
Create and enqueue a job.
@@ -981,6 +989,7 @@
image is not set, drone will run the test without server-
side packaging. Default is None.
@param args A list of args to be injected into control file.
+ @param is_cloning: True if creating a cloning job.
@param kwargs extra keyword args. NOT USED.
@returns The created Job id number.
@@ -1266,13 +1275,58 @@
info['hostless'] = job_info['hostless']
info['drone_set'] = job.drone_set and job.drone_set.name
- if job.parameterized_job:
- info['job']['image'] = get_parameterized_autoupdate_image_url(job)
+ image = _get_image_for_job(job, job_info['hostless'])
+ if image:
+ info['job']['image'] = image
return rpc_utils.prepare_for_serialization(info)
-# host queue entries
+def _get_image_for_job(job, hostless):
+ """ Gets the image used for a job.
+
+ Gets the image used for an AFE job. If the job is a parameterized job, get
+ the image from the job parameter; otherwise, tries to get the image from
+ the job's keyvals 'build' or 'builds'. As a last resort, if the job is a
+ hostless job, tries to get the image from its control file attributes
+ 'build' or 'builds'.
+
+ TODO(ntang): Needs to handle FAFT with two builds for ro/rw.
+
+ @param job An AFE job object.
+ @param hostless Boolean on of the job is hostless.
+
+ @returns The image build used for the job.
+ """
+ image = None
+ if job.parameterized_job:
+ image = get_parameterized_autoupdate_image_url(job)
+ else:
+ keyvals = job.keyval_dict()
+ image = keyvals.get('build')
+ if not image:
+ value = keyvals.get('builds')
+ builds = None
+ if isinstance(value, dict):
+ builds = value
+ elif isinstance(value, basestring):
+ builds = ast.literal_eval(value)
+ if builds:
+ image = builds.get('cros-version')
+ if not image and hostless and job.control_file:
+ try:
+ control_obj = control_data.parse_control_string(
+ job.control_file)
+ if hasattr(control_obj, 'build'):
+ image = getattr(control_obj, 'build')
+ if not image and hasattr(control_obj, 'builds'):
+ builds = getattr(control_obj, 'builds')
+ image = builds.get('cros-version')
+ except:
+ logging.warning('Failed to parse control file for job: %s',
+ job.name)
+ return image
+
def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
"""\
diff --git a/frontend/afe/rpc_interface_unittest.py b/frontend/afe/rpc_interface_unittest.py
index 0cb5cb0..7a73860 100755
--- a/frontend/afe/rpc_interface_unittest.py
+++ b/frontend/afe/rpc_interface_unittest.py
@@ -629,5 +629,74 @@
self.god.check_playback()
+ def test_get_image_for_job_parameterized(self):
+ test = models.Test.objects.create(
+ name='name', author='author', test_class='class',
+ test_category='category',
+ test_type=control_data.CONTROL_TYPE.SERVER, path='path')
+ parameterized_job = models.ParameterizedJob.objects.create(test=test)
+ job = self._create_job(hosts=[1])
+ job.parameterized_job = parameterized_job
+ self.god.stub_function_to_return(rpc_interface,
+ 'get_parameterized_autoupdate_image_url', 'cool-image')
+ image = rpc_interface._get_image_for_job(job, True)
+ self.assertEquals('cool-image', image)
+ self.god.check_playback()
+
+
+ def test_get_image_for_job_with_keyval_build(self):
+ keyval_dict = {'build': 'cool-image'}
+ job_id = rpc_interface.create_job(name='test', priority='Medium',
+ control_file='foo',
+ control_type=CLIENT,
+ hosts=['host1'],
+ keyvals=keyval_dict)
+ job = models.Job.objects.get(id=job_id)
+ self.assertIsNotNone(job)
+ image = rpc_interface._get_image_for_job(job, True)
+ self.assertEquals('cool-image', image)
+
+
+ def test_get_image_for_job_with_keyval_builds(self):
+ keyval_dict = {'builds': {'cros-version': 'cool-image'}}
+ job_id = rpc_interface.create_job(name='test', priority='Medium',
+ control_file='foo',
+ control_type=CLIENT,
+ hosts=['host1'],
+ keyvals=keyval_dict)
+ job = models.Job.objects.get(id=job_id)
+ self.assertIsNotNone(job)
+ image = rpc_interface._get_image_for_job(job, True)
+ self.assertEquals('cool-image', image)
+
+
+ def test_get_image_for_job_with_control_build(self):
+ CONTROL_FILE = """build='cool-image'
+ """
+ job_id = rpc_interface.create_job(name='test', priority='Medium',
+ control_file='foo',
+ control_type=CLIENT,
+ hosts=['host1'])
+ job = models.Job.objects.get(id=job_id)
+ self.assertIsNotNone(job)
+ job.control_file = CONTROL_FILE
+ image = rpc_interface._get_image_for_job(job, True)
+ self.assertEquals('cool-image', image)
+
+
+ def test_get_image_for_job_with_control_builds(self):
+ CONTROL_FILE = """builds={'cros-version': 'cool-image'}
+ """
+ job_id = rpc_interface.create_job(name='test', priority='Medium',
+ control_file='foo',
+ control_type=CLIENT,
+ hosts=['host1'])
+ job = models.Job.objects.get(id=job_id)
+ self.assertIsNotNone(job)
+ job.control_file = CONTROL_FILE
+ image = rpc_interface._get_image_for_job(job, True)
+ self.assertEquals('cool-image', image)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index a629f58..6077326 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -153,7 +153,7 @@
max_retries=None, max_runtime_mins=None, suite_min_duts=0,
offload_failures_only=False, builds={},
test_source_build=None, run_prod_code=False,
- delay_minutes=0, **kwargs):
+ delay_minutes=0, is_cloning=False, **kwargs):
"""
Create a job to run a test suite on the given device with the given image.
@@ -201,6 +201,7 @@
build artifacts.
@param delay_minutes: Delay the creation of test jobs for a given number of
minutes.
+ @param is_cloning: True if creating a cloning job.
@param kwargs: extra keyword args. NOT USED.
@raises ControlFileNotFound: if a unique suite control file doesn't exist.
@@ -292,6 +293,8 @@
'delay_minutes': delay_minutes,
}
+ if is_cloning:
+ control_file = tools.remove_injection(control_file)
control_file = tools.inject_vars(inject_dict, control_file)
return rpc_utils.create_job_common(name,
@@ -1029,7 +1032,7 @@
try:
control_obj = control_data.parse_control_string(control_file)
except:
- logging.info('Failed to parse congtrol file: %s', control_file_path)
+ logging.info('Failed to parse control file: %s', control_file_path)
if not ignore_invalid_tests:
raise
diff --git a/frontend/client/src/autotest/afe/AfeClient.java b/frontend/client/src/autotest/afe/AfeClient.java
index 2380396..257810c 100644
--- a/frontend/client/src/autotest/afe/AfeClient.java
+++ b/frontend/client/src/autotest/afe/AfeClient.java
@@ -86,8 +86,10 @@
public void onCloneJob(JSONValue cloneInfo) {
createJob.ensureInitialized();
- createJob.cloneJob(cloneInfo);
mainTabPanel.selectTabView(createJob);
+ // Makes sure we set cloning mode after the tab is selected. Otherwise,
+ // the mode will be reset.
+ createJob.cloneJob(cloneInfo);
}
public void onCreateRecurringJob(int jobId) {
diff --git a/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java b/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java
index 1f149a4..0a37005 100644
--- a/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java
+++ b/frontend/client/src/autotest/afe/create/CreateJobViewPresenter.java
@@ -149,6 +149,7 @@
private JSONArray dependencies = new JSONArray();
private Display display;
+ private boolean cloning;
public void bindDisplay(Display display) {
this.display = display;
@@ -158,7 +159,16 @@
this.listener = listener;
}
+ public void setCloning(boolean cloning) {
+ this.cloning = cloning;
+ }
+
+ public boolean isCloning() {
+ return cloning;
+ }
+
public void cloneJob(JSONValue cloneInfo) {
+ setCloning(true);
// reset() fires the TestSelectorListener, which will generate a new control file. We do
// no want this, so we'll stop listening to it for a bit.
testSelector.setListener(null);
@@ -825,6 +835,7 @@
args.put(TEST_SOURCE_BUILD, new JSONString(testSourceBuild));
}
+ args.put("is_cloning", JSONBoolean.getInstance(isCloning()));
rpcProxy.rpcCall("create_job_page_handler", args, new JsonRpcCallback() {
@Override
public void onSuccess(JSONValue result) {
diff --git a/frontend/client/src/autotest/afe/create/CreateJobViewTab.java b/frontend/client/src/autotest/afe/create/CreateJobViewTab.java
index 8a30d40..fd2701a 100644
--- a/frontend/client/src/autotest/afe/create/CreateJobViewTab.java
+++ b/frontend/client/src/autotest/afe/create/CreateJobViewTab.java
@@ -24,6 +24,13 @@
}
@Override
+ public void ensureInitialized() {
+ super.ensureInitialized();
+ // Makes sure cloning mode is turned off.
+ getPresenter().setCloning(false);
+ }
+
+ @Override
public void initialize() {
super.initialize();
getDisplay().initialize((HTMLPanel) getWidget());