From 8eea39dad650e050690f0c888c08707e082d8b49 Mon Sep 17 00:00:00 2001
From: "James E. Blair" <jeblair@openstack.org>
Date: Thu, 21 Feb 2013 10:46:45 -0800
Subject: [PATCH] Add a script to test the Jenkins API.

Using the extended versions of python-jenkins that are used
by Zuul and devstack-gate, exercise all of the Jenkins API
calls that are used by by Zuul, devstack-gate, and Jenkins
Job Builder.

(JJB doesn't need any extended methods.  The extended methods
used by devstack-gate have been upstreamed, but not released.)

Change-Id: I9c3a4d7cfc96965ceb83b8c5ea4b12d4f2e07e12
Reviewed-on: https://review.openstack.org/22580
Reviewed-by: Paul Belanger <paul.belanger@polybeacon.com>
Reviewed-by: Clark Boylan <clark.boylan@gmail.com>
Reviewed-by: Jeremy Stanley <fungi@yuggoth.org>
Reviewed-by: Khai Do <zaro0508@gmail.com>
Approved: Clark Boylan <clark.boylan@gmail.com>
Tested-by: Jenkins
---
 tools/jenkins-job.xml     |  33 +++++++
 tools/test-jenkins-api.py | 186 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 219 insertions(+)
 create mode 100644 tools/jenkins-job.xml
 create mode 100755 tools/test-jenkins-api.py

diff --git a/tools/jenkins-job.xml b/tools/jenkins-job.xml
new file mode 100644
index 0000000000..e51bc57d85
--- /dev/null
+++ b/tools/jenkins-job.xml
@@ -0,0 +1,33 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- A test job config for test-jenkins-api.py -->
+<project>
+  <actions/>
+  <description></description>
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <hudson.model.ParametersDefinitionProperty>
+      <parameterDefinitions>
+        <hudson.model.StringParameterDefinition>
+          <name>UUID</name>
+          <description></description>
+          <defaultValue></defaultValue>
+        </hudson.model.StringParameterDefinition>
+      </parameterDefinitions>
+    </hudson.model.ParametersDefinitionProperty>
+  </properties>
+  <quietPeriod>%s</quietPeriod>
+  <canRoam>true</canRoam>
+  <disabled>false</disabled>
+  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+  <triggers class="vector"/>
+  <concurrentBuild>true</concurrentBuild>
+  <builders>
+    <hudson.tasks.Shell>
+      <command>sleep 30
+</command>
+    </hudson.tasks.Shell>
+  </builders>
+  <publishers/>
+  <buildWrappers/>
+</project>
diff --git a/tools/test-jenkins-api.py b/tools/test-jenkins-api.py
new file mode 100755
index 0000000000..de3955ed00
--- /dev/null
+++ b/tools/test-jenkins-api.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+#
+# Test all of the Jenkins API features used by the
+# OpenStack Infrastructure project
+#
+# Copyright (C) 2013 OpenStack Foundation
+#
+# 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.
+
+import jenkins
+import urllib
+import urllib2
+import urlparse
+from pprint import pprint
+import time
+from uuid import uuid4
+import ConfigParser
+import os
+import re
+
+import sys
+sys.path.insert(0, '../../zuul/zuul')
+from launcher.jenkins import ExtendedJenkins
+
+sys.path.insert(0, '../../devstack-gate/')
+from myjenkins import Jenkins as DGJenkins
+
+JOB_NAME = 'test-job'
+NODE_NAME = 'test-node'
+
+class JenkinsTest(object):
+    def __init__(self):
+        self.config = ConfigParser.ConfigParser()
+        if len(sys.argv) < 2:
+            print "Usage: %s zuul.conf" % sys.argv[0]
+            sys.exit(1)
+        fp = sys.argv[1]
+        if os.path.exists(os.path.expanduser(fp)):
+            self.config.read(os.path.expanduser(fp))
+
+        server = self.config.get('jenkins', 'server')
+        user = self.config.get('jenkins', 'user')
+        apikey = self.config.get('jenkins', 'apikey')
+
+        self.jenkins = ExtendedJenkins(server, user, apikey)
+        self.dgjenkins = DGJenkins(server, user, apikey)
+
+    def nodeExists(self):
+        return self.dgjenkins.node_exists(NODE_NAME)
+
+    def createNode(self):
+        assert not self.nodeExists()
+        priv_key = '/var/lib/jenkins/.ssh/id_rsa'
+        self.dgjenkins.create_node(
+            NODE_NAME, numExecutors=1,
+            nodeDescription='Test node',
+            remoteFS='/home/jenkins',
+            labels='testnode',
+            exclusive=True,
+            launcher='hudson.plugins.sshslaves.SSHLauncher',
+            launcher_params={'port': 22,
+                             'username': 'jenkins',
+                             'privatekey': priv_key,
+                             'host': 'nowhere.example.com'})
+        assert self.nodeExists()
+
+    def reconfigNode(self):
+        LABEL_RE = re.compile(r'<label>.*</label>')
+        config = self.dgjenkins.get_node_config(NODE_NAME)
+        assert '<label>testnode</label>' in config
+        config = LABEL_RE.sub('<label>devstack-used</label>', config)
+        self.dgjenkins.reconfig_node(NODE_NAME, config)
+        config = self.dgjenkins.get_node_config(NODE_NAME)
+        assert '<label>devstack-used</label>' in config
+
+    def deleteNode(self):
+        assert self.nodeExists()
+        self.dgjenkins.delete_node(NODE_NAME)
+        assert not self.nodeExists()
+
+    def findBuildInQueue(self, build):
+        for item in self.jenkins.get_queue_info():
+            if 'actions' not in item:
+                continue
+            for action in item['actions']:
+                if 'parameters' not in action:
+                    continue
+                parameters = action['parameters']
+                for param in parameters:
+                    # UUID is deprecated in favor of ZUUL_UUID
+                    if ((param['name'] in ['ZUUL_UUID', 'UUID'])
+                        and build == param['value']):
+                        return item
+        return False
+
+    def addJob(self, quiet_period):
+        assert not self.jobExists()
+        xml = open('jenkins-job.xml').read()
+        xml = xml % quiet_period
+
+        self.jenkins.create_job(JOB_NAME, xml)
+        assert self.jobExists()
+
+    def reconfigJob(self, quiet_period):
+        assert self.jobExists()
+        xml = open('jenkins-job.xml').read()
+        xml = xml % quiet_period
+
+        self.jenkins.reconfig_job(JOB_NAME, xml)
+        xml2 = self.jenkins.get_job_config(JOB_NAME)
+        s = '<quietPeriod>%s</quietPeriod>' % quiet_period
+        assert s in xml2
+
+    def jobExists(self):
+        return self.jenkins.job_exists(JOB_NAME)
+
+    def deleteJob(self):
+        assert self.jobExists()
+        self.jenkins.delete_job(JOB_NAME)
+        assert not self.jobExists()
+
+    def getJobs(self):
+        pprint(self.jenkins.get_jobs())
+
+    def testCancelQueue(self):
+        uuid = str(uuid4().hex)
+        self.jenkins.build_job(JOB_NAME, parameters=dict(UUID=uuid))
+
+        item = self.findBuildInQueue(uuid)
+        assert item
+        self.jenkins.cancel_queue(item['id'])
+        assert not self.findBuildInQueue(uuid)
+
+    def testCancelBuild(self):
+        uuid = str(uuid4().hex)
+        self.jenkins.build_job(JOB_NAME, parameters=dict(UUID=uuid))
+
+        assert self.findBuildInQueue(uuid)
+        for x in range(60):
+            if not self.findBuildInQueue(uuid):
+                break
+        assert not self.findBuildInQueue(uuid)
+        time.sleep(1)
+
+        buildno = self.jenkins.get_job_info(JOB_NAME)['lastBuild']['number']
+        info = self.jenkins.get_build_info(JOB_NAME, buildno)
+        assert info['building']
+        self.jenkins.stop_build(JOB_NAME, buildno)
+        time.sleep(1)
+        info = self.jenkins.get_build_info(JOB_NAME, buildno)
+        assert not info['building']
+
+        console_url = urlparse.urljoin(info['url'], 'consoleFull')
+        self.jenkins.jenkins_open(urllib2.Request(console_url))
+
+        self.jenkins.set_build_description(JOB_NAME, buildno,
+                                           "test description")
+
+        info = self.jenkins.get_build_info(JOB_NAME, buildno)
+        assert info['description'] == 'test description'
+
+j = JenkinsTest()
+if j.nodeExists():
+    j.deleteNode()
+j.createNode()
+j.reconfigNode()
+j.deleteNode()
+if j.jobExists():
+    j.deleteJob()
+j.addJob(5)
+j.reconfigJob(10)
+j.testCancelQueue()
+j.testCancelBuild()
+j.deleteJob()