9
from httplib import ACCEPTED, HTTPConnection, HTTPException, OK, CREATED
10
from urllib import urlencode
11
from urlparse import urlparse
13
log = logging.getLogger()
17
def __init__(self, host=None, port=None, user=None, key=None, prefix=None):
19
host = os.environ.get('DASHBOARD_HOST', None)
21
port = int(os.environ.get('DASHBOARD_PORT', '80'))
23
user = os.environ.get('DASHBOARD_USER', None)
25
key = os.environ.get('DASHBOARD_KEY', None)
27
prefix = os.environ.get('DASHBOARD_PREFIX', None)
31
self.resource_base = prefix
36
'Content-Type': 'application/json',
37
'Authorization': 'ApiKey %s:%s' % (user, key)
39
# mod_wsgi will strip the Authorization header, but tastypie
40
# allows it as GET param also. More details for fixing apache:
41
# http://django-tastypie.rtfd.org/en/latest/authentication.html
42
self._auth_param = '?username=%s&api_key=%s' % (user, key)
46
return HTTPConnection(self.host, self.port)
49
def _http_get(self, resource):
52
# we just mock this for the case where the caller wants to
53
# use our API transparently enabled/disabled
56
if self.resource_base:
57
resource = self.resource_base + resource
59
logging.debug('doing get on: %s', resource)
60
headers = {'Content-Type': 'application/json'}
61
con.request('GET', resource, headers=headers)
62
resp = con.getresponse()
66
msg = resp.read().decode()
69
fmt = '%d error getting resource(%s): %s'
70
raise HTTPException(fmt % (resp.status, resource, msg))
71
data = json.loads(resp.read().decode())
72
if len(data['objects']) == 0:
73
raise HTTPException('resource not found: %s' % resource)
74
assert len(data['objects']) == 1
75
return data['objects'][0]['resource_uri']
77
def _http_post(self, resource, params):
79
if not con or not self._headers:
82
if self.resource_base:
83
resource = self.resource_base + resource
84
resource += self._auth_param
86
params = json.dumps(params)
87
log.debug('posting (%s): %s', resource, params)
88
con.request('POST', resource, params, self._headers)
89
resp = con.getresponse()
90
if resp.status != CREATED:
93
msg = str(resp.getheaders())
94
msg += resp.read().decode()
98
'%d creating resource(%s): %s' % (resp.status, resource, msg))
99
uri = resp.getheader('Location')
100
return urlparse(uri).path
102
def _http_patch(self, resource, params):
103
con = self._connect()
104
if not con or not self._headers:
107
resource += self._auth_param
109
con.request('PATCH', resource, json.dumps(params), self._headers)
110
resp = con.getresponse()
111
if resp.status != ACCEPTED:
114
msg = resp.getheaders()
118
'%d patching resource(%s): %s' % (resp.status, resource, msg))
122
def _uri_to_pk(resource_uri):
124
return resource_uri.split('/')[-2]
125
return None # we are mocked
127
def job_get(self, name):
128
resource = '/smokeng/api/v1/job/?' + urlencode({'name': name})
129
return self._http_get(resource)
131
def job_add(self, name):
132
resource = '/smokeng/api/v1/job/'
135
'url': 'http://jenkins.qa.ubuntu.com/job/' + name + '/'
137
return self._http_post(resource, params)
139
def build_add(self, job_name, job_number):
141
logging.debug('trying to find job: %s', job_name)
142
job = self.job_get(job_name)
143
except HTTPException:
144
job = self.job_add(job_name)
145
logging.info('job is: %s', job)
147
resource = '/smokeng/api/v1/build/'
149
'build_number': job_number,
151
'ran_at': datetime.datetime.now().isoformat(),
152
'build_description': 'inprogress',
154
return self._http_post(resource, params)
156
def _image_get(self, build_number, release, variant, arch, flavor):
157
resource = '/smokeng/api/v1/image/?'
158
resource += urlencode({
159
'build_number': build_number,
165
return self._http_get(resource)
167
def image_add(self, build_number, release, variant, arch, flavor):
169
img = self._image_get(build_number, release, variant, arch, flavor)
171
except HTTPException:
172
# image doesn't exist so go continue and create
175
resource = '/smokeng/api/v1/image/'
177
'build_number': build_number,
183
return self._http_post(resource, params)
185
def result_get(self, image, test):
186
# deal with getting resource uri's as parameters instead of id's
187
image = API._uri_to_pk(image)
189
resource = '/smokeng/api/v1/result/?'
190
resource += urlencode({
194
return self._http_get(resource)
196
def _result_status(self, image, build, test, status,
197
passes=0, fails=0, errors=0):
200
'ran_at': datetime.datetime.now().isoformat(),
202
'total_count': passes + fails + errors,
203
'pass_count': passes,
204
'error_count': errors,
206
'jenkins_build': build,
210
resource = self.result_get(image, test)
211
except HTTPException:
213
resource = '/smokeng/api/v1/result/'
214
params['image'] = image
215
params['name'] = test
218
return self._http_post(resource, params)
220
return self._http_patch(resource, params)
222
def result_queue(self, image, build, test):
223
return self._result_status(image, build, test, 0)
225
def result_running(self, image, build, test):
226
return self._result_status(image, build, test, 1)
228
def result_syncing(self, image, build, test, passes, fails, errors):
229
return self._result_status(
230
image, build, test, 2, passes, fails, errors)
233
def _result_running(api, args):
234
return api.result_running(args.image, args.build, args.test)
237
def _result_syncing(api, args):
238
passes = args.tests - args.fails - args.errors
239
return api.result_syncing(args.image, args.build, args.test,
240
passes, args.fails, args.errors)
243
def _set_args(parser, names, func):
245
parser.add_argument(n, required=True)
246
parser.set_defaults(func=func)
250
parser = argparse.ArgumentParser(
251
description='Interact with the CI dashboard API')
253
sub = parser.add_subparsers(title='Commands', metavar='')
255
args = ['--image', '--build', '--test']
256
p = sub.add_parser('result-running', help='Set a SmokeResult "Running".')
257
_set_args(p, args, _result_running)
259
p = sub.add_parser('result-syncing', help='Set a SmokeResult "Syncing".')
260
_set_args(p, args, _result_syncing)
261
p.add_argument('--tests', type=int, default=0)
262
p.add_argument('--fails', type=int, default=0)
263
p.add_argument('--errors', type=int, default=0)
270
'DASHBOARD_HOST', 'DASHBOARD_PORT', 'DASHBOARD_USER', 'DASHBOARD_KEY']
273
if r not in os.environ:
276
print('Missing the following environment variables:')
287
val = args.func(api, args)
290
except HTTPException as e:
291
print('ERROR: %s' % e)
296
if __name__ == '__main__':
297
args = _get_parser().parse_args()