~vila/ubuntu-test-cases/retry-apt-get-update

« back to all changes in this revision

Viewing changes to scripts/dashboard.py

  • Committer: Leo Arias
  • Date: 2014-11-10 19:28:56 UTC
  • mfrom: (345 touch)
  • mto: This revision was merged to the branch mainline in revision 352.
  • Revision ID: leo.arias@canonical.com-20141110192856-rgpksx9n9j0b39yl
Merged with the touch branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
import argparse
 
4
import datetime
 
5
import json
 
6
import logging
 
7
import os
 
8
 
 
9
import yaml
 
10
 
 
11
from httplib import ACCEPTED, HTTPConnection, HTTPException, OK, CREATED
 
12
from urllib import urlencode
 
13
from urlparse import urlparse
 
14
 
 
15
log = logging.getLogger()
 
16
 
 
17
 
 
18
class API(object):
 
19
    def __init__(self, host=None, port=None, user=None, key=None, prefix=None):
 
20
        if not host:
 
21
            host = os.environ.get('DASHBOARD_HOST', None)
 
22
        if not port:
 
23
            port = int(os.environ.get('DASHBOARD_PORT', '80'))
 
24
        if not user:
 
25
            user = os.environ.get('DASHBOARD_USER', None)
 
26
        if not key:
 
27
            key = os.environ.get('DASHBOARD_KEY', None)
 
28
        if not prefix:
 
29
            prefix = os.environ.get('DASHBOARD_PREFIX', None)
 
30
 
 
31
        self.host = host
 
32
        self.port = port
 
33
        self.resource_base = prefix
 
34
 
 
35
        self._headers = None
 
36
        if user and key:
 
37
            self._headers = {
 
38
                'Content-Type': 'application/json',
 
39
                'Authorization': 'ApiKey %s:%s' % (user, key)
 
40
            }
 
41
            # mod_wsgi will strip the Authorization header, but tastypie
 
42
            # allows it as GET param also. More details for fixing apache:
 
43
            #  http://django-tastypie.rtfd.org/en/latest/authentication.html
 
44
            self._auth_param = '?username=%s&api_key=%s' % (user, key)
 
45
 
 
46
    def _connect(self):
 
47
        if self.host:
 
48
            return HTTPConnection(self.host, self.port)
 
49
        return None
 
50
 
 
51
    def _http_get(self, resource):
 
52
        con = self._connect()
 
53
        if not con:
 
54
            # we just mock this for the case where the caller wants to
 
55
            # use our API transparently enabled/disabled
 
56
            return {}
 
57
 
 
58
        if self.resource_base:
 
59
            resource = self.resource_base + resource
 
60
 
 
61
        logging.debug('doing get on: %s', resource)
 
62
        headers = {'Content-Type': 'application/json'}
 
63
        con.request('GET', resource, headers=headers)
 
64
        resp = con.getresponse()
 
65
        if resp.status != OK:
 
66
            msg = ''
 
67
            try:
 
68
                msg = resp.read().decode()
 
69
            except:
 
70
                pass
 
71
            fmt = '%d error getting resource(%s): %s'
 
72
            raise HTTPException(fmt % (resp.status, resource, msg))
 
73
        data = json.loads(resp.read().decode())
 
74
        if len(data['objects']) == 0:
 
75
            raise HTTPException('resource not found: %s' % resource)
 
76
        assert len(data['objects']) == 1
 
77
        return data['objects'][0]['resource_uri']
 
78
 
 
79
    def _http_post(self, resource, params):
 
80
        con = self._connect()
 
81
        if not con or not self._headers:
 
82
            return None
 
83
 
 
84
        if self.resource_base:
 
85
            resource = self.resource_base + resource
 
86
        resource += self._auth_param
 
87
 
 
88
        params = json.dumps(params)
 
89
        log.debug('posting (%s): %s', resource, params)
 
90
        con.request('POST', resource, params, self._headers)
 
91
        resp = con.getresponse()
 
92
        if resp.status != CREATED:
 
93
            msg = ''
 
94
            try:
 
95
                msg = str(resp.getheaders())
 
96
                msg += resp.read().decode()
 
97
            except:
 
98
                pass
 
99
            raise HTTPException(
 
100
                '%d creating resource(%s): %s' % (resp.status, resource, msg))
 
101
        uri = resp.getheader('Location')
 
102
        return urlparse(uri).path
 
103
 
 
104
    def _http_patch(self, resource, params):
 
105
        con = self._connect()
 
106
        if not con or not self._headers:
 
107
            return None
 
108
 
 
109
        resource += self._auth_param
 
110
 
 
111
        con.request('PATCH', resource, json.dumps(params), self._headers)
 
112
        resp = con.getresponse()
 
113
        if resp.status != ACCEPTED:
 
114
            msg = ''
 
115
            try:
 
116
                msg = resp.getheaders()
 
117
            except:
 
118
                pass
 
119
            raise HTTPException(
 
120
                '%d patching resource(%s): %s' % (resp.status, resource, msg))
 
121
        return resource
 
122
 
 
123
    @staticmethod
 
124
    def _uri_to_pk(resource_uri):
 
125
        if resource_uri:
 
126
            return resource_uri.split('/')[-2]
 
127
        return None  # we are mocked
 
128
 
 
129
    def job_get(self, name):
 
130
        resource = '/smokeng/api/v1/job/?' + urlencode({'name': name})
 
131
        return self._http_get(resource)
 
132
 
 
133
    def job_add(self, name):
 
134
        resource = '/smokeng/api/v1/job/'
 
135
        params = {
 
136
            'name': name,
 
137
            'url': 'http://jenkins.qa.ubuntu.com/job/' + name + '/'
 
138
        }
 
139
        return self._http_post(resource, params)
 
140
 
 
141
    def build_add(self, job_name, job_number):
 
142
        try:
 
143
            logging.debug('trying to find job: %s', job_name)
 
144
            job = self.job_get(job_name)
 
145
        except HTTPException:
 
146
            job = self.job_add(job_name)
 
147
        logging.info('job is: %s', job)
 
148
 
 
149
        resource = '/smokeng/api/v1/build/'
 
150
        params = {
 
151
            'build_number': job_number,
 
152
            'job': job,
 
153
            'ran_at': datetime.datetime.now().isoformat(),
 
154
            'build_description': 'inprogress',
 
155
        }
 
156
        return self._http_post(resource, params)
 
157
 
 
158
    def _image_get(self, build_number, release, variant, arch, flavor):
 
159
        resource = '/smokeng/api/v1/image/?'
 
160
        resource += urlencode({
 
161
            'build_number': build_number,
 
162
            'release': release,
 
163
            'flavor': flavor,
 
164
            'variant': variant,
 
165
            'arch': arch,
 
166
        })
 
167
        return self._http_get(resource)
 
168
 
 
169
    def image_add(self, build_number, release, variant, arch, flavor):
 
170
        try:
 
171
            img = self._image_get(build_number, release, variant, arch, flavor)
 
172
            return img
 
173
        except HTTPException:
 
174
            # image doesn't exist so go continue and create
 
175
            pass
 
176
 
 
177
        resource = '/smokeng/api/v1/image/'
 
178
        params = {
 
179
            'build_number': build_number,
 
180
            'release': release,
 
181
            'flavor': flavor,
 
182
            'variant': variant,
 
183
            'arch': arch,
 
184
        }
 
185
        try:
 
186
            return self._http_post(resource, params)
 
187
        except HTTPException:
 
188
            # race situation. Both callers saw _image_get fail and tried to
 
189
            # create. Only one of them can succeed, so the failed call should
 
190
            # now safely be able to get the image created by the other
 
191
            img = self._image_get(build_number, release, variant, arch, flavor)
 
192
            return img
 
193
 
 
194
    def result_get(self, image, test):
 
195
        # deal with getting resource uri's as parameters instead of id's
 
196
        image = API._uri_to_pk(image)
 
197
 
 
198
        resource = '/smokeng/api/v1/result/?'
 
199
        resource += urlencode({
 
200
            'image': image,
 
201
            'name': test,
 
202
        })
 
203
        return self._http_get(resource)
 
204
 
 
205
    def _result_status(self, image, build, test, status, results=None):
 
206
        create = False
 
207
        params = {
 
208
            'ran_at': datetime.datetime.now().isoformat(),
 
209
            'status': status,
 
210
            'jenkins_build': build,
 
211
        }
 
212
        if results:
 
213
            params['results'] = results
 
214
 
 
215
        try:
 
216
            resource = self.result_get(image, test)
 
217
        except HTTPException:
 
218
            create = True
 
219
            resource = '/smokeng/api/v1/result/'
 
220
            params['image'] = image
 
221
            params['name'] = test
 
222
 
 
223
        if create:
 
224
            return self._http_post(resource, params)
 
225
        else:
 
226
            return self._http_patch(resource, params)
 
227
 
 
228
    def result_queue(self, image, build, test):
 
229
        return self._result_status(image, build, test, 0)
 
230
 
 
231
    def result_running(self, image, build, test):
 
232
        return self._result_status(image, build, test, 1)
 
233
 
 
234
    def result_syncing(self, image, build, test, results):
 
235
        return self._result_status(image, build, test, 2, results)
 
236
 
 
237
 
 
238
def _result_running(api, args):
 
239
    return api.result_running(args.image, args.build, args.test)
 
240
 
 
241
 
 
242
def _result_syncing(api, args):
 
243
    results = {}
 
244
    with open(args.results) as f:
 
245
        results = yaml.safe_load(f.read())
 
246
    return api.result_syncing(args.image, args.build, args.test, results)
 
247
 
 
248
 
 
249
def _set_args(parser, names, func):
 
250
    for n in names:
 
251
        parser.add_argument(n, required=True)
 
252
    parser.set_defaults(func=func)
 
253
 
 
254
 
 
255
def _get_parser():
 
256
    parser = argparse.ArgumentParser(
 
257
        description='Interact with the CI dashboard API')
 
258
 
 
259
    sub = parser.add_subparsers(title='Commands', metavar='')
 
260
 
 
261
    args = ['--image', '--build', '--test']
 
262
    p = sub.add_parser('result-running', help='Set a SmokeResult "Running".')
 
263
    _set_args(p, args, _result_running)
 
264
 
 
265
    p = sub.add_parser('result-syncing', help='Set a SmokeResult "Syncing".')
 
266
    _set_args(p, args, _result_syncing)
 
267
    p.add_argument('--results', required=True, help='UTAH yaml file')
 
268
 
 
269
    return parser
 
270
 
 
271
 
 
272
def _assert_env():
 
273
    required = [
 
274
        'DASHBOARD_HOST', 'DASHBOARD_PORT', 'DASHBOARD_USER', 'DASHBOARD_KEY']
 
275
    missing = []
 
276
    for r in required:
 
277
        if r not in os.environ:
 
278
            missing.append(r)
 
279
    if len(missing):
 
280
        print('Missing the following environment variables:')
 
281
        for x in missing:
 
282
            print('  %s' % x)
 
283
        exit(1)
 
284
 
 
285
 
 
286
def _main(args):
 
287
    _assert_env()
 
288
 
 
289
    api = API()
 
290
    try:
 
291
        val = args.func(api, args)
 
292
        if val:
 
293
            if '/?' in val:
 
294
                log.debug('stripping api-key from response')
 
295
                val = val.split('/?')[0] + '/'
 
296
            print(val)
 
297
    except HTTPException as e:
 
298
        print('ERROR: %s' % e)
 
299
        exit(1)
 
300
 
 
301
    exit(0)
 
302
 
 
303
if __name__ == '__main__':
 
304
    args = _get_parser().parse_args()
 
305
    exit(_main(args))