From: Mike Marchetti Date: Fri, 30 Jun 2017 19:12:26 +0000 (-0400) Subject: restructure into ci-pipelines X-Git-Tag: v2.0.2~3 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=8343e3fc441f1669ae906e85699e3f244dddc0b0;p=osm%2Fdevops.git restructure into ci-pipelines Change-Id: I8fcec31e9295ad7876331da49c3832f3b4863f44 Signed-off-by: Mike Marchetti --- diff --git a/jenkins/ci-pipelines/ci_helper.groovy b/jenkins/ci-pipelines/ci_helper.groovy new file mode 100644 index 00000000..974cd8d0 --- /dev/null +++ b/jenkins/ci-pipelines/ci_helper.groovy @@ -0,0 +1,156 @@ +/* Copyright 2017 Sandvine + * + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +artifactory_server_id = 'artifactory-osm' + +def get_archive(mdg, branch, build_name, build_number, pattern='*') { + server = Artifactory.server artifactory_server_id + + println("retrieve archive for ${mdg}/${branch}/${build_name}/${build_number}/${pattern}") + + def repo_prefix = 'osm-' + def downloadSpec = """{ + "files": [ + { + "target": "./", + "pattern": "${repo_prefix}${mdg}/${pattern}", + "build": "${build_name}/${build_number}" + } + ] + }""" + + server.download(downloadSpec) + // workaround. flatten and repo the specific build num from the directory + sh "cp -R ${build_num}/* ." + sh "rm -rf ${build_num}" +} + +def get_env_value(build_env_file,key) { + return sh(returnStdout:true, script: "cat ${build_env_file} | awk -F= '/${key}/{print \$2}'").trim() +} + +def lxc_run(container_name,cmd) { + return sh(returnStdout: true, script: "lxc exec ${container_name} -- ${cmd}").trim() +} + +// start a http server +// return the http server URL +def start_http_server(repo_dir,server_name) { + sh "docker run -dit --name ${server_name} -v ${repo_dir}:/usr/local/apache2/htdocs/ httpd:2.4" + def http_server_ip = sh(returnStdout:true, script: "docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${server_name}").trim() + return "-u http://${http_server_ip}/" +} + +def lxc_get_file(container_name,file,destination) { + sh "lxc file pull ${container_name}/${file} ${destination}" +} + +def systest_run(container_name, test) { + // need to get the SO IP inside the running container + so_ip = lxc_run(container_name,"lxc list SO-ub -c 4|grep eth0 |awk '{print \$2}'") + //container_ip = get_ip_from_container(container_name) + // + lxc_run(container_name, "make -C devops/systest OSM_HOSTNAME=${so_ip} ${test}") + lxc_get_file(container_name, "/root/devops/systest/reports/pytest-${test}.xml",'.') +} + +def get_ip_from_container( container_name ) { + return sh(returnStdout: true, script: "lxc list ${container_name} -c 4|grep eth0 |awk '{print \$2}'").trim() +} + +def archive(mdg,branch,status) { + server = Artifactory.server artifactory_server_id + + def properties = "branch=${branch};status=${status}" + def repo_prefix = 'osm-' + def uploadSpec = """{ + "files": [ + { + "pattern": "dists/*.gz", + "target": "${repo_prefix}${mdg}/${BUILD_NUMBER}/", + "props": "${properties}", + "flat": false + }, + { + "pattern": "dists/*Packages", + "target": "${repo_prefix}${mdg}/${BUILD_NUMBER}/", + "props": "${properties}", + "flat": false + }, + { + "pattern": "pool/*/*.deb", + "target": "${repo_prefix}${mdg}/${BUILD_NUMBER}/", + "props": "${properties}", + "flat": false + }] + }""" + + buildInfo = server.upload(uploadSpec) + //buildInfo.retention maxBuilds: 4 + //buildInfo.retention deleteBuildArtifacts: false + + server.publishBuildInfo(buildInfo) + + // store the build environment into the jenkins artifact storage + sh 'env > build.env' + archiveArtifacts artifacts: "build.env", fingerprint: true +} + + +//CANNOT use build promotion with OSS version of artifactory +// For now, will publish downloaded artifacts into a new repo. +def promote_build(mdg,branch,buildInfo) { + println("Promoting build: mdg: ${mdg} branch: ${branch} build: ${buildInfo.name}/${buildInfo.number}") + + server = Artifactory.server artifactory_server_id + + //def properties = "branch=${branch};status=${status}" + def repo_prefix = 'osm-' + def build_name = "${mdg}-stage_2 :: ${branch}" + + def promotionConfig = [ + // Mandatory parameters + "buildName" : buildInfo.name, + "buildNumber" : buildInfo.number, + 'targetRepo' : 'osm-release', + + // Optional parameters + 'comment' : 'this is the promotion comment', + 'sourceRepo' : "${repo_prefix}${mdg}", + 'status' : 'Testing', + 'includeDependencies': true, + 'copy' : true, + // 'failFast' is true by default. + // Set it to false, if you don't want the promotion to abort upon receiving the first error. + 'failFast' : true + ] + + server.promote promotionConfig +} + +def get_mdg_from_project(project) { + // split the project. + def values = project.split('/') + if ( values.size() > 1 ) { + return values[1] + } + // no prefix, likely just the project name then + return project +} + + +return this diff --git a/jenkins/ci-pipelines/ci_stage_1.groovy b/jenkins/ci-pipelines/ci_stage_1.groovy new file mode 100644 index 00000000..c345e940 --- /dev/null +++ b/jenkins/ci-pipelines/ci_stage_1.groovy @@ -0,0 +1,58 @@ +/* Copyright 2017 Sandvine + * + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +def Get_MDG(project) { + // split the project. + def values = project.split('/') + if ( values.size() > 1 ) { + return values[1] + } + // no prefix, likely just the project name then + return project +} + +node { + mdg = Get_MDG("${GERRIT_PROJECT}") + println("MDG is ${mdg}") + + if ( params.PROJECT_URL_PREFIX == null ) + { + params.PROJECT_URL_PREFIX = 'https://osm.etsi.org/gerrit' + } + + // pipeline running from gerrit trigger. + // kickoff the downstream multibranch pipeline + def downstream_params = [ + string(name: 'GERRIT_BRANCH', value: GERRIT_BRANCH), + string(name: 'GERRIT_PROJECT', value: GERRIT_PROJECT), + string(name: 'GERRIT_REFSPEC', value: GERRIT_REFSPEC), + string(name: 'GERRIT_PATCHSET_REVISION', value: GERRIT_PATCHSET_REVISION), + string(name: 'PROJECT_URL_PREFIX', value: params.PROJECT_URL_PREFIX), + booleanParam(name: 'TEST_INSTALL', value: params.TEST_INSTALL), + ] + + println("TEST_INSTALL = ${params.TEST_INSTALL}") + // callout to stage_2. This is a multi-branch pipeline. + upstream_job_name = "${mdg}-stage_2/${GERRIT_BRANCH}" + + stage_2_result = build job: "${upstream_job_name}", parameters: downstream_params, propagate: true + if (stage_2_result.getResult() != 'SUCCESS') { + project = stage_2_result.getProjectName() + build = stage_2_result.getNumber() + error("${project} build ${build} failed") + } +} diff --git a/jenkins/ci-pipelines/ci_stage_2.groovy b/jenkins/ci-pipelines/ci_stage_2.groovy new file mode 100644 index 00000000..c17a7310 --- /dev/null +++ b/jenkins/ci-pipelines/ci_stage_2.groovy @@ -0,0 +1,88 @@ +/* Copyright 2017 Sandvine + * + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +def project_checkout(url_prefix,project,refspec,revision) { + // checkout the project + // this is done automaticaly by the multibranch pipeline plugin + // git url: "${url_prefix}/${project}" + + sh "git fetch origin ${refspec}" + if (GERRIT_PATCHSET_REVISION.size() > 0 ) { + sh "git checkout -f ${revision}" + } +} + +def ci_pipeline(mdg,url_prefix,project,branch,refspec,revision,build_system) { + println("build_system = ${build_system}") + ci_helper = load "devops/jenkins/ci-pipelines/ci_helper.groovy" + + stage('Prepare') { + sh 'env' + } + + stage('Checkout') { + project_checkout(url_prefix,project,refspec,revision) + } + + container_name = "${project}-${branch}".toLowerCase() + + stage('Docker-Build') { + sh "docker build -t ${container_name} ." + } + + withDockerContainer("${container_name}") { + stage('Docker-Setup') { + sh ''' + groupadd -o -g $(id -g) -r jenkins + useradd -o -u $(id -u) --create-home -r -g jenkins jenkins + ''' + } + stage('Test') { + sh 'devops-stages/stage-test.sh' + } + stage('Build') { + sh(returnStdout:true, script: 'devops-stages/stage-build.sh').trim() + } + } + + stage('Archive') { + sh(returnStdout:true, script: 'devops-stages/stage-archive.sh').trim() + ci_helper.archive(mdg,branch,'untested') + } + + if ( build_system ) { + + stage('Build System') { + def downstream_params_stage_3 = [ + string(name: 'GERRIT_BRANCH', value: "${branch}"), + string(name: 'UPSTREAM_JOB_NAME', value: "${JOB_NAME}" ), + string(name: 'UPSTREAM_JOB_NUMBER', value: "${BUILD_NUMBER}" ), + ] + + // callout to stage_3. This is the system build + result = build job: "osm-stage_3/${branch}", parameters: downstream_params_stage_3, propagate: true + if (result.getResult() != 'SUCCESS') { + project = result.getProjectName() + build = result.getNumber() + error("${project} build ${build} failed") + } + } + } + +} + +return this diff --git a/jenkins/ci-pipelines/ci_stage_3.groovy b/jenkins/ci-pipelines/ci_stage_3.groovy new file mode 100644 index 00000000..0d2203ee --- /dev/null +++ b/jenkins/ci-pipelines/ci_stage_3.groovy @@ -0,0 +1,205 @@ +/* Copyright 2017 Sandvine + * + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +properties([ + parameters([ + string(defaultValue: env.GERRIT_BRANCH, description: '', name: 'GERRIT_BRANCH'), + string(defaultValue: 'pipeline', description: '', name: 'NODE'), + string(defaultValue: '', description: '', name: 'BUILD_FROM_SOURCE'), + string(defaultValue: 'unstable', description: '', name: 'REPO_DISTRO'), + string(defaultValue: '', description: '', name: 'COMMIT_ID'), + string(defaultValue: '-stage_2', description: '', name: 'UPSTREAM_SUFFIX'), + string(defaultValue: 'pubkey.asc', description: '', name: 'REPO_KEY_NAME'), + string(defaultValue: 'release', description: '', name: 'RELEASE'), + string(defaultValue: '', description: '', name: 'UPSTREAM_JOB_NAME'), + string(defaultValue: '', description: '', name: 'UPSTREAM_JOB_NUMBER'), + string(defaultValue: '', description: '', name: 'UPSTREAM_JOB_NUMBER'), + string(defaultValue: 'dpkg1', description: '', name: 'GPG_KEY_NAME'), + booleanParam(defaultValue: false, description: '', name: 'SAVE_CONTAINER_ON_FAIL'), + booleanParam(defaultValue: false, description: '', name: 'SAVE_CONTAINER_ON_PASS'), + ]) +]) + +node("${params.NODE}") { + + sh 'env' + + tag_or_branch = params.GERRIT_BRANCH.replaceAll(/\./,"") + container_name_prefix = "osm-${tag_or_branch}" + container_name = "${container_name_prefix}-${BUILD_NUMBER}" + + stage("Checkout") { + checkout scm + } + + ci_helper = load "jenkins/ci-pipelines/ci_helper.groovy" + + // Copy the artifacts from the upstream jobs + stage("Copy Artifacts") { + // cleanup any previous repo + sh 'rm -rf repo' + if ( params.UPSTREAM_SUFFIX ) { + + dir("repo") { + // grab all stable upstream builds based on the + // given target UPSTREAM_SUFFIX + + dir("${RELEASE}") { + def list = ["SO", "UI", "RO", "openvim", "osmclient"] + for (component in list) { + step ([$class: 'CopyArtifact', + projectName: "${component}${params.UPSTREAM_SUFFIX}/${GERRIT_BRANCH}"]) + + // grab the build name/number + //options = get_env_from_build('build.env') + build_num = ci_helper.get_env_value('build.env','BUILD_NUMBER') + //build_num = sh(returnStdout:true, script: "cat build.env | awk -F= '/BUILD_NUMBER/{print \$2}'").trim() + ci_helper.get_archive(component,GERRIT_BRANCH, "${component}-stage_2 :: ${GERRIT_BRANCH}", build_num) + + // cleanup any prevously defined dists + sh "rm -rf dists" + } + + // check if an upstream artifact based on specific build number has been requested + // This is the case of a merge build and the upstream merge build is not yet complete (it is not deemed + // a successful build yet). The upstream job is calling this downstream job (with the its build artifiact) + if ( params.UPSTREAM_JOB_NAME ) { + step ([$class: 'CopyArtifact', + projectName: "${params.UPSTREAM_JOB_NAME}", + selector: [$class: 'SpecificBuildSelector', buildNumber: "${params.UPSTREAM_JOB_NUMBER}"] + ]) + + //options = get_env_from_build('build.env') + // grab the build name/number + //build_num = sh(returnStdout:true, script: "cat build.env | awk -F= '/BUILD_NUMBER/{print \$2}'").trim() + build_num = ci_helper.get_env_value('build.env','BUILD_NUMBER') + component = ci_helper.get_mdg_from_project(ci_helper.get_env_value('build.env','GERRIT_PROJECT')) + + ci_helper.get_archive(component,GERRIT_BRANCH, "${component}-stage_2 :: ${GERRIT_BRANCH}", build_num) + + sh "rm -rf dists" + } + + // sign all the components + for (component in list) { + sh "dpkg-sig --sign builder -k ${GPG_KEY_NAME} pool/${component}/*" + } + + // now create the distro + for (component in list) { + sh "mkdir -p dists/${params.REPO_DISTRO}/${component}/binary-amd64/" + sh "apt-ftparchive packages pool/${component} > dists/${params.REPO_DISTRO}/${component}/binary-amd64/Packages" + sh "gzip -9fk dists/${params.REPO_DISTRO}/${component}/binary-amd64/Packages" + } + + // create and sign the release file + sh "apt-ftparchive release dists/${params.REPO_DISTRO} > dists/${params.REPO_DISTRO}/Release" + sh "gpg --yes -abs -u ${GPG_KEY_NAME} -o dists/${params.REPO_DISTRO}/Release.gpg dists/${params.REPO_DISTRO}/Release" + + // copy the public key into the release folder + // this pulls the key from the home dir of the current user (jenkins) + sh "cp ~/${REPO_KEY_NAME} ." + } + // start an apache server to serve up the images + http_server_name = "${container_name}-apache" + + pwd = sh(returnStdout:true, script: 'pwd').trim() + repo_base_url = ci_helper.start_http_server(pwd,http_server_name) + } + } + } + + error = null + + try { + stage("Install") { + + //will by default always delete containers on complete + //sh "jenkins/system/delete_old_containers.sh ${container_name_prefix}" + + commit_id = '' + repo_distro = '' + repo_key_name = '' + release = '' + + if ( params.COMMIT_ID ) + { + commit_id = "-b ${params.COMMIT_ID}" + } + + if ( params.REPO_DISTRO ) + { + repo_distro = "-r ${params.REPO_DISTRO}" + } + + if ( params.REPO_KEY_NAME ) + { + repo_key_name = "-k ${params.REPO_KEY_NAME}" + } + + if ( params.RELEASE ) + { + release = "-R ${params.RELEASE}" + } + + sh """ + export OSM_USE_LOCAL_DEVOPS=true + jenkins/host/start_build system --build-container ${container_name} \ + ${commit_id} \ + ${repo_distro} \ + ${repo_base_url} \ + ${repo_key_name} \ + ${release} \ + ${params.BUILD_FROM_SOURCE} + """ + } + + stage("Test") { + ci_helper.systest_run(container_name, 'smoke') + junit '*.xml' + } + + stage("Archive") { + sh "echo ${container_name} > build_version.txt" + archiveArtifacts artifacts: "build_version.txt", fingerprint: true + + // Archive the tested repo + dir("repo/${RELEASE}") { + ci_helper.archive(RELEASE,GERRIT_BRANCH,'tested') + } + } + } + catch(caughtError) { + error = caughtError + currentBuild.result = 'FAILURE' + } + finally { + sh "docker stop ${http_server_name}" + + if (error) { + if ( !params.SAVE_CONTAINER_ON_FAIL ) { + sh "lxc delete ${container_name} --force" + } + throw error + } + else { + if ( !params.SAVE_CONTAINER_ON_PASS ) { + sh "lxc delete ${container_name} --force" + } + } + } +} diff --git a/jenkins/ci-pipelines/ci_stage_4.groovy b/jenkins/ci-pipelines/ci_stage_4.groovy new file mode 100644 index 00000000..928fdd8d --- /dev/null +++ b/jenkins/ci-pipelines/ci_stage_4.groovy @@ -0,0 +1,55 @@ +/* Copyright 2017 Sandvine + * + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +properties([ + parameters([ + string(defaultValue: 'osm-stage_3', description: '', name: 'UPSTREAM_PROJECT'), + string(defaultValue: 'release', description: '', name: 'RELEASE'), + string(defaultValue: 'pipeline', description: '', name: 'NODE'), + ]) +]) + +node("${params.NODE}") { + + stage("checkout") { + checkout scm + } + + ci_helper = load "jenkins/ci-pipelines/ci_helper.groovy" + + stage("get artifacts") { + // grab the upstream artifact name + step ([$class: 'CopyArtifact', + projectName: "${params.UPSTREAM_PROJECT}/${BRANCH_NAME}"]) + } + + container_name = sh(returnStdout: true, script: 'cat build_version.txt').trim() + + stage("Test") { + ci_helper.systest_run(container_name, 'smoke') + junit '*.xml' + } + +/* os_credentials = "OS_AUTH_URL=${params.OS_AUTH_URL} OS_USERNAME=${params.OS_USERNAME} OS_PASSWORD=${params.OS_PASSWORD} OS_PROJECT_NAME=${params.OS_PROJECT_NAME}" + stage("cirros-test") { + sh """ + make -C systest OSM_HOSTNAME=${osm_ip} ${os_credentials} cirros + """ + junit 'systest/reports/pytest-cirros.xml' + } +*/ +} diff --git a/jenkins/system/start_build b/jenkins/system/start_build index 7f259c72..1591739a 100755 --- a/jenkins/system/start_build +++ b/jenkins/system/start_build @@ -19,6 +19,7 @@ HERE=$(realpath $(dirname $0)) OSM_JENKINS=$(dirname $HERE) . $OSM_JENKINS/common/all_funcs +. $OSM_JENKINS/common/install_common INFO "Installing packages" apt-get update @@ -53,9 +54,7 @@ systemctl start lxd-bridge apt-get install -y python-pip python python-pycurl charm-tools python-pytest -# TODO: use package when available on osm repo -git clone https://osm.etsi.org/gerrit/osm/osmclient -pip install osmclient/. +apt install -y python-osmclient export OSM_USE_LOCAL_DEVOPS=true devops/installers/install_osm.sh --test $* @@ -72,6 +71,8 @@ if [ $RC == 0 ]; then TO_ADD="export OSM_HOSTNAME=$SO_CONTAINER_IP" grep -q OSM_HOSTNAME ~/.bashrc && sed -i "s/.*OSM_HOSTNAME.*/$TO_ADD/" ~/.bashrc || echo -e "$TO_ADD\n$(cat ~/.bashrc)" > ~/.bashrc + TO_ADD="export OSM_RO_HOSTNAME=$RO_CONTAINER_IP" + grep -q OSM_RO_HOSTNAME ~/.bashrc && sed -i "s/.*OSM_RO_HOSTNAME.*/$TO_ADD/" ~/.bashrc || echo -e "$TO_ADD\n$(cat ~/.bashrc)" > ~/.bashrc fi INFO "done, RC=$RC" diff --git a/systest/Dockerfile b/systest/Dockerfile index ec304867..23ed1d01 100644 --- a/systest/Dockerfile +++ b/systest/Dockerfile @@ -3,4 +3,9 @@ FROM ubuntu:16.04 RUN apt-get update && apt-get -y install python \ libcurl4-gnutls-dev libgnutls-dev \ python-setuptools python-pip git python-pytest \ - charm-tools + charm-tools sudo + +# allow users to sudo. This will allow packages to be installed +# inside the container +RUN echo "ALL ALL = NOPASSWD: ALL" > /etc/sudoers.d/user && \ + chmod 0440 /etc/sudoers.d/user diff --git a/systest/Jenkinsfile b/systest/Jenkinsfile deleted file mode 100644 index 31372bce..00000000 --- a/systest/Jenkinsfile +++ /dev/null @@ -1,58 +0,0 @@ -// input parameters: -// string: UPSTREAM_PROJECT -// string: NODE -// -// OpenStack VIM Credentials -// string: OS_AUTH_URL -// string: OS_USERNAME -// string: OS_PASSWORD -// string: OS_PROJECT_NAME - -node("${params.NODE}") { - - // grab the upstream artifact name - step ([$class: 'CopyArtifact', - projectName: params.UPSTREAM_PROJECT]) - - container_name = sh(returnStdout: true, script: 'cat build_version.txt').trim() - - stage("get osm") { - // get the IP of the osm container - OSM_IP = sh(returnStdout: true, script: "lxc list ${container_name} -c 4|grep eth0 |awk '{print \$2}'").trim() - } - stage("checkout") { - checkout scm - } - - // build the pytest container - stage("build-docker") { - sh 'docker build -t osmclient systest/.' - } - - os_credentials = "OS_AUTH_URL=${params.OS_AUTH_URL} OS_USERNAME=${params.OS_USERNAME} OS_PASSWORD=${params.OS_PASSWORD} OS_PROJECT_NAME=${params.OS_PROJECT_NAME}" - - // now run the built container. - withDockerContainer('osmclient') { - - // install the osmclient - stage("install-osmclient") { - sh 'pip install git+https://osm.etsi.org/gerrit/osm/osmclient' - } - - stage("build-descriptors") { - sh "make -C systest descriptors" - } - - stage("smoke-test") { - sh "make -C systest OSM_HOSTNAME=${OSM_IP} smoke" - junit 'systest/reports/pytest-smoke.xml' - } - - stage("cirros-test") { - sh """ - make -C systest OSM_HOSTNAME=${OSM_IP} ${os_credentials} cirros - """ - junit 'systest/reports/pytest-cirros.xml' - } - } -}