diff --git a/Jenkinsfile b/Jenkinsfile index c593b2e9162c7ec96d8176ec888b4bb51a470178..f0f69cae08964c6947726dc5ec6b47d1f825c972 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,42 +1,46 @@ def runPythonTests() { - ansiColor('gnome-terminal') { - sshagent(credentials: ['jenkins-worker'], ignoreMissing: true) { - checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '${sha1}']], - doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], - userRemoteConfigs: [[credentialsId: 'jenkins-worker', - refspec: '+refs/heads/*:refs/remotes/origin/* +refs/pull/*:refs/remotes/origin/pr/*', - url: 'git@github.com:edx/edx-platform.git']]] - console_output = sh(returnStdout: true, script: 'bash scripts/all-tests.sh').trim() - dir('stdout') { - writeFile file: "${TEST_SUITE}-${SHARD}-stdout.log", text: console_output - } - stash includes: 'reports/**/*coverage*', name: "${TEST_SUITE}-${SHARD}-reports" + sshagent(credentials: ['jenkins-worker', 'jenkins-worker-pem'], ignoreMissing: true) { + checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '${ghprbActualCommit}']], + doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], + userRemoteConfigs: [[credentialsId: 'jenkins-worker', + refspec: '+refs/heads/*:refs/remotes/origin/* +refs/pull/*:refs/remotes/origin/pr/*', + url: 'git@github.com:edx/edx-platform.git']]] + console_output = sh(returnStdout: true, script: 'bash scripts/all-tests.sh').trim() + dir('stdout') { + writeFile file: "${TEST_SUITE}-stdout.log", text: console_output } + stash includes: 'reports/**/*coverage*', name: "${TEST_SUITE}-reports" } } -def savePythonTestArtifacts() { +def pythonTestCleanup() { archiveArtifacts allowEmptyArchive: true, artifacts: 'reports/**/*,test_root/log/**/*.log,**/nosetests.xml,stdout/*.log,*.log' junit '**/nosetests.xml' + sh '''source $HOME/edx-venv/bin/activate + bash scripts/xdist/terminate_xdist_nodes.sh''' } pipeline { - agent { label "coverage-worker" } - options { timestamps() - timeout(75) + timeout(60) + } + environment { + XDIST_CONTAINER_SUBNET = credentials('XDIST_CONTAINER_SUBNET') + XDIST_CONTAINER_SECURITY_GROUP = credentials('XDIST_CONTAINER_SECURITY_GROUP') + XDIST_CONTAINER_TASK_NAME = "jenkins-worker-task" + XDIST_GIT_BRANCH = "${ghprbActualCommit}" } - stages { stage('Run Tests') { parallel { - stage('lms-unit-1') { + stage("lms-unit") { agent { label "jenkins-worker" } environment { - SHARD = 1 - TEST_SUITE = 'lms-unit' + TEST_SUITE = "lms-unit" + XDIST_NUM_TASKS = 10 + XDIST_REMOTE_NUM_PROCESSES = 2 } steps { script { @@ -46,35 +50,17 @@ pipeline { post { always { script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-2') { - agent { label "jenkins-worker" } - environment { - SHARD = 2 - TEST_SUITE = 'lms-unit' - } - steps{ - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() + pythonTestCleanup() } } } } - stage('lms-unit-3') { + stage("cms-unit") { agent { label "jenkins-worker" } environment { - SHARD = 3 - TEST_SUITE = 'lms-unit' + TEST_SUITE = "cms-unit" + XDIST_NUM_TASKS = 4 + XDIST_REMOTE_NUM_PROCESSES = 2 } steps { script { @@ -84,16 +70,17 @@ pipeline { post { always { script { - savePythonTestArtifacts() + pythonTestCleanup() } } } } - stage('lms-unit-4') { + stage("commonlib-unit") { agent { label "jenkins-worker" } environment { - SHARD = 4 - TEST_SUITE = 'lms-unit' + TEST_SUITE = "commonlib-unit" + XDIST_NUM_TASKS = 3 + XDIST_REMOTE_NUM_PROCESSES = 2 } steps { script { @@ -103,216 +90,7 @@ pipeline { post { always { script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-5') { - agent { label "jenkins-worker" } - environment { - SHARD = 5 - TEST_SUITE = 'lms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-6') { - agent { label "jenkins-worker" } - environment { - SHARD = 6 - TEST_SUITE = 'lms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-7') { - agent { label "jenkins-worker" } - environment { - SHARD = 7 - TEST_SUITE = 'lms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-8') { - agent { label "jenkins-worker" } - environment { - SHARD = 8 - TEST_SUITE = 'lms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-9') { - agent { label "jenkins-worker" } - environment { - SHARD = 9 - TEST_SUITE = 'lms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('lms-unit-10') { - agent { label "jenkins-worker" } - environment { - SHARD = 10 - TEST_SUITE = 'lms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('cms-unit-1') { - agent { label "jenkins-worker" } - environment { - SHARD = 1 - TEST_SUITE = 'cms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('cms-unit-2') { - agent { label "jenkins-worker" } - environment { - SHARD = 2 - TEST_SUITE = 'cms-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('commonlib-unit-1') { - agent { label "jenkins-worker" } - environment { - SHARD = 1 - TEST_SUITE = 'commonlib-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('commonlib-unit-2') { - agent { label "jenkins-worker" } - environment { - SHARD = 2 - TEST_SUITE = 'commonlib-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() - } - } - } - } - stage('commonlib-unit-3') { - agent { label "jenkins-worker" } - environment { - SHARD = 3 - TEST_SUITE = 'commonlib-unit' - } - steps { - script { - runPythonTests() - } - } - post { - always { - script { - savePythonTestArtifacts() + pythonTestCleanup() } } } @@ -327,30 +105,16 @@ pipeline { SUBSET_JOB = "null" // Keep this variable until we can remove the $SUBSET_JOB path from .coveragerc } steps { - ansiColor('gnome-terminal') { - sshagent(credentials: ['jenkins-worker'], ignoreMissing: true) { - checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '${sha1}']], - doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], - userRemoteConfigs: [[credentialsId: 'jenkins-worker', - refspec: '+refs/heads/*:refs/remotes/origin/* +refs/pull/*:refs/remotes/origin/pr/*', - url: 'git@github.com:edx/edx-platform.git']]] - unstash 'lms-unit-1-reports' - unstash 'lms-unit-2-reports' - unstash 'lms-unit-3-reports' - unstash 'lms-unit-4-reports' - unstash 'lms-unit-5-reports' - unstash 'lms-unit-6-reports' - unstash 'lms-unit-7-reports' - unstash 'lms-unit-8-reports' - unstash 'lms-unit-9-reports' - unstash 'lms-unit-10-reports' - unstash 'cms-unit-1-reports' - unstash 'cms-unit-2-reports' - unstash 'commonlib-unit-1-reports' - unstash 'commonlib-unit-2-reports' - unstash 'commonlib-unit-3-reports' - sh "./scripts/jenkins-report.sh" - } + sshagent(credentials: ['jenkins-worker'], ignoreMissing: true) { + checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '${ghprbActualCommit}']], + doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], + userRemoteConfigs: [[credentialsId: 'jenkins-worker', + refspec: '+refs/heads/*:refs/remotes/origin/* +refs/pull/*:refs/remotes/origin/pr/*', + url: 'git@github.com:edx/edx-platform.git']]] + unstash 'lms-unit-reports' + unstash 'cms-unit-reports' + unstash 'commonlib-unit-reports' + sh "./scripts/jenkins-report.sh" } } post { diff --git a/pavelib/paver_tests/test_paver_pytest_cmds.py b/pavelib/paver_tests/test_paver_pytest_cmds.py new file mode 100644 index 0000000000000000000000000000000000000000..70dd8494aafc100d37b70f50d6f5ca92e56b76e1 --- /dev/null +++ b/pavelib/paver_tests/test_paver_pytest_cmds.py @@ -0,0 +1,163 @@ +""" +Tests for the pytest paver commands themselves. +Run just this test with: paver test_lib -t pavelib/paver_tests/test_paver_pytest_cmds.py +""" +import unittest +import os +import ddt + +from pavelib.utils.test.suites import SystemTestSuite, LibTestSuite +from pavelib.utils.envs import Env + + +XDIST_TESTING_IP_ADDRESS_LIST = '0.0.0.1,0.0.0.2,0.0.0.3' + + +@ddt.ddt +class TestPaverPytestCmd(unittest.TestCase): + """ + Test Paver pytest commands + """ + + def _expected_command(self, root, test_id, pytestSubclass, run_under_coverage=True, + processes=0, xdist_ip_addresses=None): + """ + Returns the command that is expected to be run for the given test spec + and store. + """ + report_dir = Env.REPORT_DIR / root + shard = os.environ.get('SHARD') + if shard: + report_dir = report_dir / 'shard_' + shard + + expected_statement = [ + "python", + "-Wd", + "-m", + "pytest" + ] + if pytestSubclass == "SystemTestSuite": + expected_statement.append("--ds={}".format('{}.envs.{}'.format(root, Env.TEST_SETTINGS))) + expected_statement.append("--junitxml={}".format(report_dir / "nosetests.xml")) + + if xdist_ip_addresses: + expected_statement.append('--dist=loadscope') + for ip in xdist_ip_addresses.split(','): + if processes <= 0: + processes = 1 + + if pytestSubclass == "SystemTestSuite": + django_env_var_cmd = "export DJANGO_SETTINGS_MODULE={}.envs.test".format(root) + elif pytestSubclass == "LibTestSuite": + if 'pavelib/paver_tests' in test_id: + django_env_var_cmd = "export DJANGO_SETTINGS_MODULE={}.envs.test".format(root) + else: + django_env_var_cmd = "export DJANGO_SETTINGS_MODULE='openedx.tests.settings'" + + xdist_string = '--tx {}*ssh="ubuntu@{} -o StrictHostKeyChecking=no"' \ + '//python="source /edx/app/edxapp/edxapp_env; {}; python"' \ + '//chdir="/edx/app/edxapp/edx-platform"' \ + .format(processes, ip, django_env_var_cmd) + expected_statement.append(xdist_string) + for rsync_dir in Env.rsync_dirs(): + expected_statement.append('--rsyncdir {}'.format(rsync_dir)) + else: + if processes == -1: + expected_statement.append('-n auto') + expected_statement.append('--dist=loadscope') + elif processes != 0: + expected_statement.append('-n {}'.format(processes)) + expected_statement.append('--dist=loadscope') + + expected_statement.extend([ + "-p no:randomly", + test_id + ]) + + if run_under_coverage: + if xdist_ip_addresses: + for module in Env.covered_modules(): + expected_statement.append('--cov') + expected_statement.append(module) + else: + expected_statement.append('--cov') + expected_statement.append('--cov-report=') + return expected_statement + + @ddt.data('lms', 'cms') + def test_SystemTestSuite_suites(self, system): + test_id = 'tests' + suite = SystemTestSuite(system, test_id=test_id) + assert suite.cmd == self._expected_command(system, test_id, "SystemTestSuite") + + @ddt.data('lms', 'cms') + def test_SystemTestSuite_auto_processes(self, system): + test_id = 'tests' + suite = SystemTestSuite(system, test_id=test_id, processes=-1) + assert suite.cmd == self._expected_command(system, test_id, "SystemTestSuite", processes=-1) + + @ddt.data('lms', 'cms') + def test_SystemTestSuite_multi_processes(self, system): + test_id = 'tests' + suite = SystemTestSuite(system, test_id=test_id, processes=3) + assert suite.cmd == self._expected_command(system, test_id, "SystemTestSuite", processes=3) + + @ddt.data('lms', 'cms') + def test_SystemTestSuite_with_xdist(self, system): + test_id = 'tests' + suite = SystemTestSuite(system, test_id=test_id, xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + assert suite.cmd == self._expected_command(system, test_id, "SystemTestSuite", + xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + + @ddt.data('lms', 'cms') + def test_SystemTestSuite_with_xdist_multi_processes(self, system): + test_id = 'tests' + suite = SystemTestSuite(system, test_id=test_id, processes=2, xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + assert suite.cmd == self._expected_command(system, test_id, "SystemTestSuite", processes=2, + xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + + @ddt.data('lms', 'cms') + def test_SystemTestSuite_with_xdist_negative_processes(self, system): + test_id = 'tests' + suite = SystemTestSuite(system, test_id=test_id, processes=-1, xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + assert suite.cmd == self._expected_command(system, test_id, "SystemTestSuite", processes=-1, + xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + + @ddt.data('common/lib/xmodule', 'pavelib/paver_tests') + def test_LibTestSuite_suites(self, system): + test_id = 'tests' + suite = LibTestSuite(system, test_id=test_id) + assert suite.cmd == self._expected_command(system, test_id, "LibTestSuite") + + @ddt.data('common/lib/xmodule', 'pavelib/paver_tests') + def test_LibTestSuite_auto_processes(self, system): + test_id = 'tests' + suite = LibTestSuite(system, test_id=test_id, processes=-1) + assert suite.cmd == self._expected_command(system, test_id, "LibTestSuite", processes=-1) + + @ddt.data('common/lib/xmodule', 'pavelib/paver_tests') + def test_LibTestSuite_multi_processes(self, system): + test_id = 'tests' + suite = LibTestSuite(system, test_id=test_id, processes=3) + assert suite.cmd == self._expected_command(system, test_id, "LibTestSuite", processes=3) + + @ddt.data('common/lib/xmodule', 'pavelib/paver_tests') + def test_LibTestSuite_with_xdist(self, system): + test_id = 'tests' + suite = LibTestSuite(system, test_id=test_id, xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + assert suite.cmd == self._expected_command(system, test_id, "LibTestSuite", + xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + + @ddt.data('common/lib/xmodule', 'pavelib/paver_tests') + def test_LibTestSuite_with_xdist_multi_processes(self, system): + test_id = 'tests' + suite = LibTestSuite(system, test_id=test_id, processes=2, xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + assert suite.cmd == self._expected_command(system, test_id, "LibTestSuite", processes=2, + xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + + @ddt.data('common/lib/xmodule', 'pavelib/paver_tests') + def test_LibTestSuite_with_xdist_negative_processes(self, system): + test_id = 'tests' + suite = LibTestSuite(system, test_id=test_id, processes=-1, xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) + assert suite.cmd == self._expected_command(system, test_id, "LibTestSuite", processes=-1, + xdist_ip_addresses=XDIST_TESTING_IP_ADDRESS_LIST) diff --git a/pavelib/tests.py b/pavelib/tests.py index c19ebad9303e69a62f1888fa433a44757cd4f8ca..587e793aa66cab801e021a028c89f7110dd94d5a 100644 --- a/pavelib/tests.py +++ b/pavelib/tests.py @@ -72,7 +72,7 @@ __test__ = False # do not collect make_option( '--xdist_ip_addresses', dest='xdist_ip_addresses', - help="Space separated string of ip addresses to shard tests to via xdist." + help="Comma separated string of ip addresses to shard tests to via xdist." ) ], share_with=['pavelib.utils.test.utils.clean_reports_dir']) @PassthroughTask @@ -160,8 +160,10 @@ def test_system(options, passthrough_options): make_option( '--xdist_ip_addresses', dest='xdist_ip_addresses', - help="Space separated string of ip addresses to shard tests to via xdist." - ) + help="Comma separated string of ip addresses to shard tests to via xdist." + ), + make_option('-p', '--processes', dest='processes', default=0, help='number of processes to use running tests'), + make_option('-r', '--randomize', action='store_true', help='run the tests in a random order'), ], share_with=['pavelib.utils.test.utils.clean_reports_dir']) @PassthroughTask @timed diff --git a/pavelib/utils/test/suites/pytest_suite.py b/pavelib/utils/test/suites/pytest_suite.py index 6f2cc120a8e88dcbb8d8fca92a3b783f33d0e749..e814e75608aa23fb73532ffda993766b3eeb4a06 100644 --- a/pavelib/utils/test/suites/pytest_suite.py +++ b/pavelib/utils/test/suites/pytest_suite.py @@ -158,12 +158,20 @@ class SystemTestSuite(PytestSuite): if self.disable_capture: cmd.append("-s") - if self.xdist_ip_addresses: cmd.append('--dist=loadscope') - for ip in self.xdist_ip_addresses.split(' '): - xdist_string = '--tx ssh=ubuntu@{}//python="source /edx/app/edxapp/edxapp_env; ' \ - 'python"//chdir="/edx/app/edxapp/edx-platform"'.format(ip) + if self.processes <= 0: + xdist_remote_processes = 1 + else: + xdist_remote_processes = self.processes + for ip in self.xdist_ip_addresses.split(','): + # The django settings runtime command does not propagate to xdist remote workers + django_env_var_cmd = 'export DJANGO_SETTINGS_MODULE={}' \ + .format('{}.envs.{}'.format(self.root, self.settings)) + xdist_string = '--tx {}*ssh="ubuntu@{} -o StrictHostKeyChecking=no"' \ + '//python="source /edx/app/edxapp/edxapp_env; {}; python"' \ + '//chdir="/edx/app/edxapp/edx-platform"' \ + .format(xdist_remote_processes, ip, django_env_var_cmd) cmd.append(xdist_string) for rsync_dir in Env.rsync_dirs(): cmd.append('--rsyncdir {}'.format(rsync_dir)) @@ -239,6 +247,14 @@ class LibTestSuite(PytestSuite): self.test_id = kwargs.get('test_id', self.root) self.eval_attr = kwargs.get('eval_attr', None) self.xdist_ip_addresses = kwargs.get('xdist_ip_addresses', None) + self.randomize = kwargs.get('randomize', None) + self.processes = kwargs.get('processes', None) + + if self.processes is None: + # Don't use multiprocessing by default + self.processes = 0 + + self.processes = int(self.processes) @property def cmd(self): @@ -251,8 +267,6 @@ class LibTestSuite(PytestSuite): '-Wd', '-m', 'pytest', - '-p', - 'no:randomly', '--junitxml={}'.format(self.xunit_report), ]) cmd.extend(self.passthrough_options + self.test_options_flags) @@ -265,13 +279,33 @@ class LibTestSuite(PytestSuite): if self.xdist_ip_addresses: cmd.append('--dist=loadscope') - for ip in self.xdist_ip_addresses.split(' '): - xdist_string = '--tx ssh=ubuntu@{}//python="source /edx/app/edxapp/edxapp_env; ' \ - 'python"//chdir="/edx/app/edxapp/edx-platform"'.format(ip) + if self.processes <= 0: + xdist_remote_processes = 1 + else: + xdist_remote_processes = self.processes + for ip in self.xdist_ip_addresses.split(','): + # The django settings runtime command does not propagate to xdist remote workers + if 'pavelib/paver_tests' in self.test_id: + django_env_var_cmd = "export DJANGO_SETTINGS_MODULE='lms.envs.test'" + else: + django_env_var_cmd = "export DJANGO_SETTINGS_MODULE='openedx.tests.settings'" + xdist_string = '--tx {}*ssh="ubuntu@{} -o StrictHostKeyChecking=no"' \ + '//python="source /edx/app/edxapp/edxapp_env; {}; python"' \ + '//chdir="/edx/app/edxapp/edx-platform"' \ + .format(xdist_remote_processes, ip, django_env_var_cmd) cmd.append(xdist_string) for rsync_dir in Env.rsync_dirs(): cmd.append('--rsyncdir {}'.format(rsync_dir)) + else: + if self.processes == -1: + cmd.append('-n auto') + cmd.append('--dist=loadscope') + elif self.processes != 0: + cmd.append('-n {}'.format(self.processes)) + cmd.append('--dist=loadscope') + if not self.randomize: + cmd.append("-p no:randomly") if self.eval_attr: cmd.append("-a '{}'".format(self.eval_attr)) diff --git a/scripts/unit-tests.sh b/scripts/unit-tests.sh index 837dd8e73e70a7a48eb20788e1de0dbc3dbecaa2..be47bdf0e2e807c332e869401989502beda07dc7 100755 --- a/scripts/unit-tests.sh +++ b/scripts/unit-tests.sh @@ -29,8 +29,6 @@ set -e # ############################################################################### -PAVER_ARGS="-v" -PARALLEL="--processes=-1" export SKIP_NPM_INSTALL="True" # Skip re-installation of Python prerequisites inside a tox execution. @@ -38,6 +36,20 @@ if [[ -n "$TOXENV" ]]; then export NO_PREREQ_INSTALL="True" fi +if [[ -n "$XDIST_NUM_TASKS" ]]; then + bash scripts/xdist/prepare_xdist_nodes.sh + PAVER_ARGS="-v --xdist_ip_addresses="$(<pytest_task_ips.txt)"" + export SHARD="all" + if [[ -n "$XDIST_REMOTE_NUM_PROCESSES" ]]; then + PARALLEL="--processes=$XDIST_REMOTE_NUM_PROCESSES" + else + PARALLEL="--processes=1" + fi +else + PAVER_ARGS="-v" + PARALLEL="--processes=-1" +fi + case "${TEST_SUITE}" in "lms-unit") @@ -65,7 +77,7 @@ case "${TEST_SUITE}" in "cms-unit") case "$SHARD" in "all") - paver test_system -s cms --disable_capture ${PAVER_ARGS} 2> cms-tests.log + paver test_system -s cms --disable_capture ${PAVER_ARGS} ${PARALLEL} 2> cms-tests.log ;; 1) paver test_system -s cms --disable_capture --eval-attr="shard==$SHARD" ${PAVER_ARGS} 2> cms-tests.${SHARD}.log @@ -87,7 +99,7 @@ case "${TEST_SUITE}" in "commonlib-unit") case "$SHARD" in "all") - paver test_lib --disable_capture ${PAVER_ARGS} 2> common-tests.log + paver test_lib --disable_capture ${PAVER_ARGS} ${PARALLEL} 2> common-tests.log ;; [1-2]) paver test_lib -l common/lib/xmodule --disable_capture --eval-attr="shard==$SHARD" ${PAVER_ARGS} 2> common-tests.${SHARD}.log diff --git a/scripts/xdist/prepare_xdist_nodes.sh b/scripts/xdist/prepare_xdist_nodes.sh index 3e41956386c7eca7d8982e7ce1518cabf1415585..763fca9b51ba3dcd38f58ebbe0965a388548eda8 100644 --- a/scripts/xdist/prepare_xdist_nodes.sh +++ b/scripts/xdist/prepare_xdist_nodes.sh @@ -1,16 +1,16 @@ #!/bin/bash set -e +echo "Spinning up xdist containers with pytest_container_manager.py" python scripts/xdist/pytest_container_manager.py -a up -n ${XDIST_NUM_TASKS} \ -t ${XDIST_CONTAINER_TASK_NAME} \ -s ${XDIST_CONTAINER_SUBNET} \ -sg ${XDIST_CONTAINER_SECURITY_GROUP} ip_list=$(<pytest_task_ips.txt) - -for ip in $ip_list +for ip in $(echo $ip_list | sed "s/,/ /g") do - container_reqs_cmd="ssh ubuntu@$ip 'cd /edx/app/edxapp/edx-platform; + container_reqs_cmd="ssh -o StrictHostKeyChecking=no ubuntu@$ip 'cd /edx/app/edxapp/edx-platform; git pull -q; git checkout -q ${XDIST_GIT_BRANCH}; source /edx/app/edxapp/edxapp_env; pip install -qr requirements/edx/testing.txt' & " diff --git a/scripts/xdist/pytest_container_manager.py b/scripts/xdist/pytest_container_manager.py index e7dd0b384b9373cc40148e62ca5c56a3ee9cac30..4fc22e3397be0a6777720f696543745de4eb587c 100644 --- a/scripts/xdist/pytest_container_manager.py +++ b/scripts/xdist/pytest_container_manager.py @@ -104,13 +104,13 @@ class PytestContainerManager(): logger.info("Successfully booted up {} tasks.".format(number_of_tasks)) # Generate .txt files containing IP addresses and task arns - ip_list_string = " ".join(ip_addresses) + ip_list_string = ",".join(ip_addresses) logger.info("Task IP list: {}".format(ip_list_string)) ip_list_file = open("pytest_task_ips.txt", "w") ip_list_file.write(ip_list_string) ip_list_file.close() - task_arn_list_string = " ".join(task_arns) + task_arn_list_string = ",".join(task_arns) logger.info("Task arn list: {}".format(task_arn_list_string)) task_arn_file = open("pytest_task_arns.txt", "w") task_arn_file.write(task_arn_list_string) @@ -120,7 +120,7 @@ class PytestContainerManager(): """ Terminates tasks based on a list of task_arns. """ - for task_arn in task_arns: + for task_arn in task_arns.split(','): response = self.ecs.stop_task( cluster=self.cluster_name, task=task_arn, @@ -134,23 +134,26 @@ if __name__ == "__main__": description="PytestContainerManager, manages ECS tasks in an AWS cluster." ) - parser.add_argument('--region', '-g', default='us-east-1', - help="AWS region where ECS infrastructure lives. Defaults to us-east-1") + parser.add_argument('--action', '-a', choices=['up', 'down'], default=None, + help="Action for PytestContainerManager to perform. " + "Either up for spinning up AWS ECS tasks or down for stopping them") parser.add_argument('--cluster', '-c', default="jenkins-worker-containers", help="AWS Cluster name where the tasks run. Defaults to" "the testeng cluster: jenkins-worker-containers") - parser.add_argument('--action', '-a', choices=['up', 'down'], default=None, - help="Action for PytestContainerManager to perform. " - "Either up for spinning up AWS ECS tasks or down for stopping them") + parser.add_argument('--region', '-g', default='us-east-1', + help="AWS region where ECS infrastructure lives. Defaults to us-east-1") # Spinning up tasks + parser.add_argument('--launch_type', default='FARGATE', choices=['EC2', 'FARGATE'], + help="ECS launch type for tasks. Defaults to FARGATE") + parser.add_argument('--num_tasks', '-n', type=int, default=None, help="Number of ECS tasks to spin up") - parser.add_argument('--task_name', '-t', default=None, - help="Name of the task definition") + parser.add_argument('--public_ip', choices=['ENABLED', 'DISABLED'], + default='DISABLED', help="Whether the tasks should have a public IP") parser.add_argument('--subnets', '-s', nargs='+', default=None, help="List of subnets for the tasks to exist in") @@ -158,19 +161,16 @@ if __name__ == "__main__": parser.add_argument('--security_groups', '-sg', nargs='+', default=None, help="List of security groups to apply to the tasks") - parser.add_argument('--public_ip', choices=['ENABLED', 'DISABLED'], - default='DISABLED', help="Whether the tasks should have a public IP") - - parser.add_argument('--launch_type', default='FARGATE', choices=['EC2', 'FARGATE'], - help="ECS launch type for tasks. Defaults to FARGATE") + parser.add_argument('--task_name', '-t', default=None, + help="Name of the task definition") # Terminating tasks - parser.add_argument('--task_arns', '-arns', nargs='+', default=None, - help="Task arns to terminate") - parser.add_argument('--reason', '-r', default="Finished executing tests", help="Reason for terminating tasks") + parser.add_argument('--task_arns', '-arns', default=None, + help="Task arns to terminate") + args = parser.parse_args() containerManager = PytestContainerManager(args.region, args.cluster) diff --git a/scripts/xdist/terminate_xdist_nodes.sh b/scripts/xdist/terminate_xdist_nodes.sh new file mode 100644 index 0000000000000000000000000000000000000000..ee077a2c5739434fda772b5e65b45563ace27b18 --- /dev/null +++ b/scripts/xdist/terminate_xdist_nodes.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +if [ -f pytest_task_arns.txt ]; then + echo "Terminating xdist containers with pytest_container_manager.py" + xdist_task_arns=$(<pytest_task_arns.txt) + python scripts/xdist/pytest_container_manager.py -a down --task_arns ${xdist_task_arns} +else + echo "File: pytest_task_arns.txt not found" +fi diff --git a/tox.ini b/tox.ini index c0b3d9475f531be991cd0a34d17f89ad54c737c0..4e592b2917c81cc127e6ef1c25bc4f3a99369778 100644 --- a/tox.ini +++ b/tox.ini @@ -51,6 +51,7 @@ passenv = XDIST_CONTAINER_TASK_NAME XDIST_GIT_BRANCH XDIST_NUM_TASKS + XDIST_REMOTE_NUM_PROCESSES deps = django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10