Bug 1142779 - Enable testdroid devices to be used within test tasks r=lightsofapollo
authorGregory Arndt <garndt@mozilla.com>
Thu, 12 Mar 2015 17:43:28 -0500
changeset 265993 3d8168ff207292ea48558174e6f7ac99be035adf
parent 265992 30236ca212cb40e005df12f2b989d80402d5b3e1
child 265994 06367ce03cce6d9c11aa0910ddd11838e36da108
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslightsofapollo
bugs1142779
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1142779 - Enable testdroid devices to be used within test tasks r=lightsofapollo
testing/docker/tester-device/Dockerfile
testing/docker/tester-device/VERSION
testing/docker/tester-device/bin/entrypoint
testing/docker/tester-device/bin/validate_task.py
testing/docker/tester-device/build.sh
testing/docker/tester-device/tests/invalid_base_repo.yml
testing/docker/tester-device/tests/invalid_build.yml
testing/docker/tester-device/tests/invalid_head_repo.yml
testing/docker/tester-device/tests/public.yml
testing/docker/tester-device/tests/test_validation.py
testing/docker/tester-device/tests/valid.yml
testing/taskcluster/mach_commands.py
testing/taskcluster/tasks/branches/b2g-inbound/job_flags.yml
testing/taskcluster/tasks/branches/base_job_flags.yml
testing/taskcluster/tasks/builds/b2g_flame_kk_eng.yml
testing/taskcluster/tasks/phone_test.yml
testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_sanity.yml
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/Dockerfile
@@ -0,0 +1,48 @@
+FROM        ubuntu:14.04
+MAINTAINER  Greg Arndt <garndt@mozilla.com>
+
+RUN mkdir -p /home/worker/upload/logs
+
+WORKDIR /home/worker
+
+RUN apt-get install -y curl
+
+# Add PPA for latest nodejs versions. Do not need to run apt-get update after this
+# as the script already does it.
+RUN curl -sL https://deb.nodesource.com/setup | sudo bash -
+
+RUN apt-get upgrade -y && apt-get install -y \
+  build-essential \
+  ca-certificates \
+  nodejs \
+  python-dev \
+  mercurial \
+  git \
+  android-tools-adb \
+  android-tools-fastboot\
+  jq
+
+# Get pip and virtualenv
+RUN curl https://bootstrap.pypa.io/get-pip.py | python
+RUN pip install virtualenv
+
+RUN git config --global user.email "mozilla@example.com" && \
+    git config --global user.name "mozilla"
+
+
+# Get node packages
+RUN npm install -g taskcluster-vcs@2.3.0
+
+WORKDIR /home/worker
+
+ADD bin /home/worker/bin
+ADD data /home/worker/data
+ADD https://raw.githubusercontent.com/taskcluster/buildbot-step/master/buildbot_step /home/worker/bin/buildbot_step
+RUN chmod u+x /home/worker/bin/*
+
+ENV HOME /home/worker
+ENV SHELL /bin/bash
+ENV PATH $PATH:/home/worker/bin
+ENV CLOUD_HOST testdroid
+
+ENTRYPOINT ["entrypoint"]
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/VERSION
@@ -0,0 +1,1 @@
+0.0.2
new file mode 100755
--- /dev/null
+++ b/testing/docker/tester-device/bin/entrypoint
@@ -0,0 +1,26 @@
+#! /bin/bash -e
+
+echo "Validating Task"
+python /home/worker/bin/validate_task.py
+
+echo "Retrieving device"
+res=`curl --request POST -H "Content-Type: application/json" -d "$DEVICE_CAPABILITIES" http://$CLOUD_HOST/device`
+status=`echo $res | jq .session`
+
+if [[ $status == 'null' ]]; then
+    echo "Session could not be created with a device."
+    exit -1
+fi
+
+export SESSION_ID=`echo $res | jq .session.id`
+export SERIAL_ID=`echo $res | jq -r .proxies.adb.serialId`
+export ADB_HOST=`echo $res | jq .proxies.adb.forwardHost`
+export ADB_PORT=`echo $res | jq .proxies.adb.port`
+export MARIONETTE_HOST=`echo $res | jq .proxies.marionette.forwardHost`
+export MARIONETTE_PORT=`echo $res | jq .proxies.marionette.port`
+export PROXY_HOST=`echo $res | jq -r .proxyHost`
+echo "Retrieved device.  Session: $SESSION_ID"
+
+curl -o /home/worker/data/device.json -s -H "Accept: application/json" http://$CLOUD_HOST/device/properties
+
+eval $@
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/bin/validate_task.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import os
+import os.path
+import json
+import urllib2
+import sys
+import re
+import subprocess
+
+repo_matcher = re.compile(r'[a-z]+://(hg|git)\.mozilla\.org')
+image_matcher = re.compile(r'^https:\/\/queue\.taskcluster\.net\/v1\/task\/.+\/artifacts\/private\/build\/flame-kk\.zip$')
+
+def get_task(taskid):
+    return json.load(urllib2.urlopen('https://queue.taskcluster.net/v1/task/' + taskid))
+
+def check_task(task):
+    payload = task['payload']
+
+    if 'DEVICE_CAPABILITIES' not in payload['env']:
+        print('Device capalities are required.', file=sys.stderr)
+        return -1
+
+    capabilities = json.loads(payload['env']['DEVICE_CAPABILITIES'])
+
+    if 'build' not in capabilities:
+        print('Build image url is required', file=sys.stderr)
+        return -1
+
+    image = capabilities['build']
+
+    if not image_matcher.match(image):
+        print('Invalid image url', file=sys.stderr)
+        return -1
+
+    if 'GAIA_HEAD_REPOSITORY' not in payload['env']:
+        print('Task has no head gaia repository', file=sys.stderr)
+        return -1
+
+    repo = payload['env']['GAIA_HEAD_REPOSITORY']
+    # if it is not a mozilla repository, fail
+    if not repo_matcher.match(repo):
+        print('Invalid head repository', repo, file=sys.stderr)
+        return -1
+
+    if 'GAIA_BASE_REPOSITORY' not in payload['env']:
+        print('Task has no base gaia repository', file=sys.stderr)
+        return -1
+
+    repo = payload['env']['GAIA_BASE_REPOSITORY']
+    if not repo_matcher.match(repo):
+        print('Invalid base repository', repo, file=sys.stderr)
+        return -1
+
+    if 'artifacts' in payload:
+        artifacts = payload['artifacts']
+        # If any of the artifacts makes reference to 'public',
+        # abort the task
+        if any(map(lambda a: 'public' in a, artifacts)):
+            print('Cannot upload to public', file=sys.stderr)
+            return -1
+
+    return 0
+
+def main():
+    taskid = os.getenv('TASK_ID')
+
+    # If the task id is None, we assume we are running docker locally
+    if taskid is not None:
+        task = get_task(taskid)
+        sys.exit(check_task(task))
+
+if __name__ == '__main__':
+    main()
new file mode 100755
--- /dev/null
+++ b/testing/docker/tester-device/build.sh
@@ -0,0 +1,22 @@
+#! /bin/bash -ve
+
+while getopts "t:g:" arg; do
+  case $arg in
+    t)
+      TAG=$OPTARG
+      ;;
+    g)
+      GAIA_TESTVARS=$OPTARG
+      ;;
+  esac
+done
+
+pushd $(dirname $0)
+
+test $TAG
+test -f "$GAIA_TESTVARS"
+
+cp $GAIA_TESTVARS data/gaia_testvars.json
+
+docker build -t $TAG .
+rm -f data/gaia_testvars.json
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/tests/invalid_base_repo.yml
@@ -0,0 +1,29 @@
+taskId: 1
+task:
+  metadata:
+    name: '[TC] Gaia Python Integration Tests - device'
+    description: Gaia Python Integration Tests
+  workerType: testdroid-device
+  retries: 0
+
+  payload:
+    env:
+      DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "1","build":"https://queue.taskcluster.net/v1/task/H0FPqxakT06Eg4wo4zPwMw/runs/0/artifacts/private/build/flame-kk.zip"}'
+      GAIA_HEAD_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+      GAIA_BASE_REPOSITORY: 'http://github.com/mozilla-b2g/gaia'
+    command:
+      - entrypoint
+      - >
+        tc-vcs checkout /home/worker/gaia/source $GAIA_BASE_REPOSITORY $GAIA_HEAD_REPOSITORY $GAIA_REV $GAIA_REF &&
+        cd gaia/source/tests/python/gaia-ui-tests/ &&
+        python setup.py develop &&
+        pip install py &&
+        cd /home/worker/ &&
+        gaiatest --testvars=/home/worker/data/testdroid.json --testvars=/home/worker/data/acknowledge_risks.json --testvars=/home/worker/data/common.json --testvars=/home/worker/data/device.json --adb-host=$PROXY_HOST --adb-port=$ADB_PORT --address=$PROXY_HOST:$MARIONETTE_PORT --device $SERIAL_ID --xml-output=/home/worker/upload/logs/xml_output.xml --timeout=10000 --log-html=/home/worker/upload/logs/index.html --restart --type=b2g+sanity-dsds --log-mach=-  --log-raw=/home/worker/upload/logs/raw.log gaia/source/tests/python/gaia-ui-tests/gaiatest/tests/functional/manifest.ini
+    artifacts:
+      'private/device.json':
+        type: file
+        path: '/home/worker/data/device.json'
+      'private/logs':
+        type: directory
+        path: '/home/worker/upload/logs/'
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/tests/invalid_build.yml
@@ -0,0 +1,29 @@
+taskId: 1
+task:
+  metadata:
+    name: '[TC] Gaia Python Integration Tests - device'
+    description: Gaia Python Integration Tests
+  workerType: testdroid-device
+  retries: 0
+
+  payload:
+    env:
+      DEVICE_CAPABILITIES: "{\"type\":\"flame\",\"memory\":\"319\",\"sims\": \"1\",\"build\":\"https://queue.some_other_domain.net/v1/task/fLOiHaudRkepg9yEiaM1Mg/artifacts/private/build/flame-kk.zip\"}"
+      GAIA_HEAD_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+      GAIA_BASE_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+    command:
+      - entrypoint
+      - >
+        tc-vcs checkout /home/worker/gaia/source $GAIA_BASE_REPOSITORY $GAIA_HEAD_REPOSITORY $GAIA_REV $GAIA_REF &&
+        cd gaia/source/tests/python/gaia-ui-tests/ &&
+        python setup.py develop &&
+        pip install py &&
+        cd /home/worker/ &&
+        gaiatest --testvars=/home/worker/data/testdroid.json --testvars=/home/worker/data/acknowledge_risks.json --testvars=/home/worker/data/common.json --testvars=/home/worker/data/device.json --adb-host=$PROXY_HOST --adb-port=$ADB_PORT --address=$PROXY_HOST:$MARIONETTE_PORT --device $SERIAL_ID --xml-output=/home/worker/upload/logs/xml_output.xml --timeout=10000 --log-html=/home/worker/upload/logs/index.html --restart --type=b2g+sanity-dsds --log-mach=-  --log-raw=/home/worker/upload/logs/raw.log gaia/source/tests/python/gaia-ui-tests/gaiatest/tests/functional/manifest.ini
+    artifacts:
+      'private/device.json':
+        type: file
+        path: '/home/worker/data/device.json'
+      'private/logs':
+        type: directory
+        path: '/home/worker/upload/logs/'
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/tests/invalid_head_repo.yml
@@ -0,0 +1,29 @@
+taskId: 1
+task:
+  metadata:
+    name: '[TC] Gaia Python Integration Tests - device'
+    description: Gaia Python Integration Tests
+  workerType: testdroid-device
+  retries: 0
+
+  payload:
+    env:
+      DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "1","build":"https://queue.taskcluster.net/v1/task/H0FPqxakT06Eg4wo4zPwMw/runs/0/artifacts/private/build/flame-kk.zip"}'
+      GAIA_BASE_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+      GAIA_HEAD_REPOSITORY: 'http://github.com/mozilla-b2g/gaia'
+    command:
+      - entrypoint
+      - >
+        tc-vcs checkout /home/worker/gaia/source $GAIA_BASE_REPOSITORY $GAIA_HEAD_REPOSITORY $GAIA_REV $GAIA_REF &&
+        cd gaia/source/tests/python/gaia-ui-tests/ &&
+        python setup.py develop &&
+        pip install py &&
+        cd /home/worker/ &&
+        gaiatest --testvars=/home/worker/data/testdroid.json --testvars=/home/worker/data/acknowledge_risks.json --testvars=/home/worker/data/common.json --testvars=/home/worker/data/device.json --adb-host=$PROXY_HOST --adb-port=$ADB_PORT --address=$PROXY_HOST:$MARIONETTE_PORT --device $SERIAL_ID --xml-output=/home/worker/upload/logs/xml_output.xml --timeout=10000 --log-html=/home/worker/upload/logs/index.html --restart --type=b2g+sanity-dsds --log-mach=-  --log-raw=/home/worker/upload/logs/raw.log gaia/source/tests/python/gaia-ui-tests/gaiatest/tests/functional/manifest.ini
+    artifacts:
+      'private/device.json':
+        type: file
+        path: '/home/worker/data/device.json'
+      'private/logs':
+        type: directory
+        path: '/home/worker/upload/logs/'
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/tests/public.yml
@@ -0,0 +1,29 @@
+taskId: 1
+task:
+  metadata:
+    name: '[TC] Gaia Python Integration Tests - device'
+    description: Gaia Python Integration Tests
+  workerType: testdroid-device
+  retries: 0
+
+  payload:
+    env:
+      DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "1","build":"https://queue.taskcluster.net/v1/task/H0FPqxakT06Eg4wo4zPwMw/runs/0/artifacts/private/build/flame-kk.zip"}'
+      GAIA_BASE_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+      GAIA_HEAD_REPOSITORY: 'http://github.com/mozilla-b2g/gaia'
+    command:
+      - entrypoint
+      - >
+        tc-vcs checkout /home/worker/gaia/source $GAIA_BASE_REPOSITORY $GAIA_HEAD_REPOSITORY $GAIA_REV $GAIA_REF &&
+        cd gaia/source/tests/python/gaia-ui-tests/ &&
+        python setup.py develop &&
+        pip install py &&
+        cd /home/worker/ &&
+        gaiatest --testvars=/home/worker/data/testdroid.json --testvars=/home/worker/data/acknowledge_risks.json --testvars=/home/worker/data/common.json --testvars=/home/worker/data/device.json --adb-host=$PROXY_HOST --adb-port=$ADB_PORT --address=$PROXY_HOST:$MARIONETTE_PORT --device $SERIAL_ID --xml-output=/home/worker/upload/logs/xml_output.xml --timeout=10000 --log-html=/home/worker/upload/logs/index.html --restart --type=b2g+sanity-dsds --log-mach=-  --log-raw=/home/worker/upload/logs/raw.log gaia/source/tests/python/gaia-ui-tests/gaiatest/tests/functional/manifest.ini
+    artifacts:
+      'public/device.json':
+        type: file
+        path: '/home/worker/data/device.json'
+      'private/logs':
+        type: directory
+        path: '/home/worker/upload/logs/'
new file mode 100755
--- /dev/null
+++ b/testing/docker/tester-device/tests/test_validation.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+import unittest
+import sys
+import yaml
+sys.path.append('../bin')
+from validate_task import check_task
+
+def load_task(task_file):
+    content = open(task_file, 'r')
+    return yaml.load(content)['task']
+
+class TaskValidationTest(unittest.TestCase):
+    def test_valid_task(self):
+        task = load_task('valid.yml')
+        self.assertEquals(check_task(task), 0)
+
+    def test_invalid_base_repo(self):
+        task = load_task('invalid_base_repo.yml')
+        self.assertEquals(check_task(task), -1)
+
+    def test_invalid_head_repo(self):
+        task = load_task('invalid_head_repo.yml')
+        self.assertEquals(check_task(task), -1)
+
+    def test_public_artifact(self):
+        task = load_task('public.yml')
+        self.assertEquals(check_task(task), -1)
+
+    def test_invalid_build(self):
+        task = load_task('invalid_build.yml')
+        self.assertEquals(check_task(task), -1)
+
+if __name__ == '__main__':
+    unittest.main()
new file mode 100644
--- /dev/null
+++ b/testing/docker/tester-device/tests/valid.yml
@@ -0,0 +1,29 @@
+taskId: 1
+task:
+  metadata:
+    name: '[TC] Gaia Python Integration Tests - device'
+    description: Gaia Python Integration Tests
+  workerType: testdroid-device
+  retries: 0
+
+  payload:
+    env:
+      DEVICE_CAPABILITIES: "{\"type\":\"flame\",\"memory\":\"319\",\"sims\": \"1\",\"build\":\"https://queue.taskcluster.net/v1/task/fLOiHaudRkepg9yEiaM1Mg/artifacts/private/build/flame-kk.zip\"}"
+      GAIA_HEAD_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+      GAIA_BASE_REPOSITORY: 'http://hg.mozilla.org/integration/gaia-central'
+    command:
+      - entrypoint
+      - >
+        tc-vcs checkout /home/worker/gaia/source $GAIA_BASE_REPOSITORY $GAIA_HEAD_REPOSITORY $GAIA_REV $GAIA_REF &&
+        cd gaia/source/tests/python/gaia-ui-tests/ &&
+        python setup.py develop &&
+        pip install py &&
+        cd /home/worker/ &&
+        gaiatest --testvars=/home/worker/data/testdroid.json --testvars=/home/worker/data/acknowledge_risks.json --testvars=/home/worker/data/common.json --testvars=/home/worker/data/device.json --adb-host=$PROXY_HOST --adb-port=$ADB_PORT --address=$PROXY_HOST:$MARIONETTE_PORT --device $SERIAL_ID --xml-output=/home/worker/upload/logs/xml_output.xml --timeout=10000 --log-html=/home/worker/upload/logs/index.html --restart --type=b2g+sanity-dsds --log-mach=-  --log-raw=/home/worker/upload/logs/raw.log gaia/source/tests/python/gaia-ui-tests/gaiatest/tests/functional/manifest.ini
+    artifacts:
+      'private/device.json':
+        type: file
+        path: '/home/worker/data/device.json'
+      'private/logs':
+        type: directory
+        path: '/home/worker/upload/logs/'
--- a/testing/taskcluster/mach_commands.py
+++ b/testing/taskcluster/mach_commands.py
@@ -306,16 +306,22 @@ class Graph(object):
                 build_task['task']['extra']['locations']['tests']
             )
 
             build_url = ARTIFACT_URL.format(
                 build_parameters['build_slugid'],
                 build_task['task']['extra']['locations']['build']
             )
 
+            # img_url is only necessary for device builds
+            img_url = ARTIFACT_URL.format(
+                build_parameters['build_slugid'],
+                build_task['task']['extra']['locations'].get('img', '')
+            )
+
             define_task = DEFINE_TASK.format(build_task['task']['workerType'])
 
             graph['scopes'].append(define_task)
             graph['scopes'].extend(build_task['task'].get('scopes', []))
 
             # Treeherder symbol configuration for the graph required for each
             # build so tests know which platform they belong to.
             build_treeherder_config = build_task['task']['extra']['treeherder']
@@ -334,16 +340,17 @@ class Graph(object):
             if len(build_treeherder_config['collection'].keys()) != 1:
                 message = '({}), extra.treeherder.collection must contain one type'
                 raise ValueError(message.fomrat(build['task']))
 
             for test in build['dependents']:
                 test = test['allowed_build_tasks'][build['task']]
                 test_parameters = copy.copy(build_parameters)
                 test_parameters['build_url'] = build_url
+                test_parameters['img_url'] = img_url
                 test_parameters['tests_url'] = tests_url
 
                 test_definition = templates.load(test['task'], {})['task']
                 chunk_config = test_definition['extra']['chunks']
 
                 # Allow branch configs to override task level chunking...
                 if 'chunks' in test:
                     chunk_config['total'] = test['chunks']
@@ -444,29 +451,29 @@ class CIBuild(object):
             head_repository = get_hg_url()
 
         head_rev = params['head_rev']
         if not head_rev:
             head_rev = get_latest_hg_revision(head_repository)
 
         head_ref = params['head_ref'] or head_rev
 
-        build_parameters = {
+        build_parameters = dict(gaia_info().items() + {
             'docker_image': docker_image,
             'owner': params['owner'],
             'from_now': json_time_from_now,
             'now': current_json_time(),
             'base_repository': params['base_repository'] or head_repository,
             'head_repository': head_repository,
             'head_rev': head_rev,
             'head_ref': head_ref,
             'mozharness_repository': params['mozharness_repository'],
             'mozharness_ref': params['mozharness_ref'],
             'mozharness_rev': params['mozharness_rev']
-        }
+        }.items())
 
         try:
             build_task = templates.load(params['build_task'], build_parameters)
         except IOError:
             sys.stderr.write(
                 "Could not load build task file.  Ensure path is a relative " \
                 "path from testing/taskcluster"
             )
@@ -496,44 +503,46 @@ class CITest(object):
 
         if chunk is None:
             chunk = 1
 
         if chunk < 1 or chunk > total_chunks:
             raise ValueError(
                 '"chunk" must be a value between 1 and "total_chunks (default 1)"')
 
-        build_url, tests_url = self._get_build_and_tests_url(task_id)
+        build_url, img_url, tests_url = self._get_build_and_tests_url(task_id)
 
-        test_parameters = {
+        test_parameters = dict(gaia_info().items() + {
             'docker_image': docker_image,
             'build_url': ARTIFACT_URL.format(task_id, build_url),
+            'img_url': ARTIFACT_URL.format(task_id, img_url),
             'tests_url': ARTIFACT_URL.format(task_id, tests_url),
             'total_chunks': total_chunks,
             'chunk': chunk,
             'owner': owner,
             'from_now': json_time_from_now,
             'now': current_json_time()
-        }
+        }.items())
 
         try:
-            test_task = import_yaml(test_task, test_parameters)
+            templates = Templates(ROOT)
+            test_task = templates.load(test_task, test_parameters)
         except IOError:
             sys.stderr.write(
                 "Could not load test task file.  Ensure path is a relative " \
                 "path from testing/taskcluster"
             )
             sys.exit(1)
 
         print(json.dumps(test_task['task'], indent=4))
 
     def _get_build_and_tests_url(self, task_id):
         task = get_task(task_id)
         locations = task['extra']['locations']
-        return locations['build'], locations['tests']
+        return locations['build'], locations.get('img', ''), locations['tests']
 
 @CommandProvider
 class CIDockerRun(object):
     @Command('taskcluster-docker-run', category='ci',
         description='Run a docker image and optionally mount local hg repos. ' \
                     'Repos will be mounted to /home/worker/x/source accordingly. ' \
                     'For example, to run a centos image and mount local gecko ' \
                     'and gaia repos: mach ci-docker-run --local-gecko-repo ' \
--- a/testing/taskcluster/tasks/branches/b2g-inbound/job_flags.yml
+++ b/testing/taskcluster/tasks/branches/b2g-inbound/job_flags.yml
@@ -13,8 +13,11 @@ builds:
       opt:
         task: tasks/builds/b2g_flame_kk_opt.yml
   flame-kk-eng:
     platforms:
       - b2g
     types:
       opt:
         task: tasks/builds/b2g_flame_kk_eng.yml
+
+  gaia-ui-test-sanity:
+        task: tasks/tests/flame_kk_gaia_ui_test_sanity.yml
\ No newline at end of file
--- a/testing/taskcluster/tasks/branches/base_job_flags.yml
+++ b/testing/taskcluster/tasks/branches/base_job_flags.yml
@@ -23,16 +23,17 @@ flags:
     - crashtest
     - crashtest-ipc
     - gaia-build
     - gaia-build-unit
     - gaia-js-integration
     - gaia-linter
     - gaia-unit
     - gaia-unit-oop
+    - gaia-ui-test-sanity
     - gaia-ui-test-oop
     - gaia-ui-test-accessibility
     - gaia-ui-test-functional
     - gaia-ui-test-unit
     - jetpack
     - jittests
     - jsreftest
     - marionette
--- a/testing/taskcluster/tasks/builds/b2g_flame_kk_eng.yml
+++ b/testing/taskcluster/tasks/builds/b2g_flame_kk_eng.yml
@@ -24,9 +24,12 @@ task:
       symbol: Be
       groupSymbol: Flame-KK
       groupName: Flame KitKat Device Image
       machine:
         platform: b2g-device-image
     locations:
       img: 'private/build/flame-kk.zip'
 
+      GAIA_OPTIMIZE: '1'
+      B2G_SYSTEM_APPS: '1'
+      B2G_UPDATER: '1'
 
new file mode 100644
--- /dev/null
+++ b/testing/taskcluster/tasks/phone_test.yml
@@ -0,0 +1,42 @@
+# This task is the base for most tests in gecko.
+task:
+  created: '{{now}}'
+  deadline: '{{#from_now}}24 hours{{/from_now}}'
+  metadata:
+    source: http://todo.com/soon
+    owner: mozilla-taskcluster-maintenance@mozilla.com
+  tags:
+    createdForUser: {{owner}}
+  workerType: b2gtest
+  provisionerId: aws-provisioner
+  schedulerId: task-graph-scheduler
+
+  scopes:
+    - 'docker-worker:image:{{#docker_image}}tester-device{{/docker_image}}'
+    - 'queue:create-task:aws-provisioner/testdroid-device'
+    - 'docker-worker:cache:tc-vcs'
+
+  payload:
+    image: '{{#docker_image}}tester-device{{/docker_image}}'
+    maxRunTime: 3600
+    cache:
+      tc-vcs: '/home/worker/.tc-vcs'
+    env:
+      GAIA_HEAD_REPOSITORY: '{{{gaia_head_repository}}}'
+      GAIA_BASE_REPOSITORY: '{{{gaia_base_repository}}}'
+      GAIA_REF: '{{{gaia_ref}}}'
+      GAIA_REV: '{{{gaia_rev}}}'
+      MOZHARNESS_REPOSITORY: '{{mozharness_repository}}'
+      MOZHARNESS_REV: '{{mozharness_rev}}'
+
+    # All builds share a common artifact directory for ease of uploading.
+    artifacts:
+      'private/logs':
+        type: directory
+        path: '/home/worker/upload/logs/'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+
+  extra:
+    treeherder:
+      groupSymbol: tc
+      groupName: Submitted by taskcluster
new file mode 100644
--- /dev/null
+++ b/testing/taskcluster/tasks/tests/flame_kk_gaia_ui_test_sanity.yml
@@ -0,0 +1,36 @@
+---
+$inherits:
+  from: 'tasks/phone_test.yml'
+task:
+  metadata:
+    name: '[TC] Gaia Python Sanity Integration Tests'
+    description: Gaia Python Integration Tests
+  workerType: testdroid-device
+  retries: 0
+
+  payload:
+    env:
+      DEVICE_CAPABILITIES: '{"type":"flame","memory":"319","sims": "1","build":"{{{img_url}}}"}'
+    features:
+      testdroidProxy: true
+    maxRunTime: 7200
+    command:
+      - >
+        tc-vcs checkout /home/worker/gaia/source $GAIA_BASE_REPOSITORY $GAIA_HEAD_REPOSITORY $GAIA_REV $GAIA_REF &&
+        cd gaia/source/tests/python/gaia-ui-tests/ &&
+        python setup.py develop &&
+        pip install py &&
+        cd /home/worker/ &&
+        gaiatest --testvars=/home/worker/data/gaia_testvars.json --testvars=/home/worker/data/device.json --adb-host=$PROXY_HOST --adb-port=$ADB_PORT --address=$PROXY_HOST:$MARIONETTE_PORT --device $SERIAL_ID --xml-output=/home/worker/upload/logs/xml_output.xml --timeout=10000 --log-html=/home/worker/upload/logs/index.html --restart --type=b2g+sanity-dsds --log-mach=-  --log-raw=/home/worker/upload/logs/raw.log gaia/source/tests/python/gaia-ui-tests/gaiatest/tests/functional/manifest.ini
+    artifacts:
+      'private/device.json':
+        type: file
+        path: '/home/worker/data/device.json'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+
+  extra:
+    treeherder:
+      groupName: Gaia Python Integration Tests
+      groupSymbol: tc-Gip
+      symbol: 'S'
+      productName: b2g