Merge lp:~dooferlad/linaro-android-frontend/ui_download_links_update into lp:linaro-android-frontend

Proposed by James Tunnicliffe
Status: Merged
Approved by: Paul Sokolovsky
Approved revision: 253
Merged at revision: 246
Proposed branch: lp:~dooferlad/linaro-android-frontend/ui_download_links_update
Merge into: lp:linaro-android-frontend
Diff against target: 255 lines (+163/-16)
3 files modified
android_build/frontend/api.py (+55/-3)
android_build/urls.py (+2/-1)
static/buildDetails.js (+106/-12)
To merge this branch: bzr merge lp:~dooferlad/linaro-android-frontend/ui_download_links_update
Reviewer Review Type Date Requested Status
Paul Sokolovsky Approve
Review via email: mp+88914@code.launchpad.net

Description of the change

UI will present the user a list of downloads. It will test to see if it can find a build on snapshots.linaro.org. If it finds it it will use that server and point the links there. If it fails to find a build on snapshots, it will look for a locally stored on android-build.linaro.org. If it finds a build there, it will point the download links to it. If it fails to find builds anywhere, it will point links to where it thinks the build should be on snapshots.

In preparation for artefact publishing going away this update also supports using a file called manifest.txt, which is just a list of files (one relative location per line). If it finds manifest.txt in the root of the download location, it will use that. It will fall back to the artefact list and if that fails, display a message saying it couldn't provide links to individual files.

I have tested this using a proxy that would replace bits of the live android-build.linaro.org site (https://code.launchpad.net/~dooferlad/junk/dev_proxy) so I could browse around several build pages to see if this worked. Unfortunately it looks like my ISP is suffering from a routing problem and I just lost connectivity to android-build.linaro.org and I didn't have a chance to test looking at a build that has its downloads on android-build.linaro.org. It should work, but at this point it would be nice to have some feedback on the change.

To post a comment you must log in.
251. By James Tunnicliffe

Fixed error - wasn't testing return code when examinining where a build was hosted.
Fixed test_remote_dir_exists description.
Allow test_remote_dir_exists to test android-build.linaro.org.

Revision history for this message
James Tunnicliffe (dooferlad) wrote :

A couple of fixes added and now tested in the following conditions:

1. Build exists on android-build.linaro.org only
   - File links point to android-build.linaro.org
2. Build exists on snapshots.linaro.org
   - File links point to snapshots.linaro.org
3. Build exists on shapshots.linaro.org and manifest.txt exists
   - File links point to snapshots.linaro.org and download list comes from
     manifest.txt

So it should be good to go.

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

Well, comments are:

1. Are you sure that making 3 synchronous AJAX requests doesn't hurt page download/interactive performance?

2. create_manifest.py script apparently doesn't belong here, but rather should be part of linaro-android-build-tools and be called from build-android script at the appropriate place.

3. Did you give extra thought what manifest.txt may contain? Just filenames? Maybe file size? What about MD5SUMS file we already create for subset of files (actually, one of the reason why is it created for subset and not all was because that would require faithful duplication of artifact filename patterns - both in Jenkins and in this script, same problem would exist for create_manifest.py too). (I don't have any specific suggestion regarding what manifest.txt should include, just wanted to make sure this is though-out).

4. Ah, last one, naming. Maybe "MANIFEST" is better, it's kind of common practice (that depends on contents format of course).

review: Needs Information
Revision history for this message
James Tunnicliffe (dooferlad) wrote :

Hi Paul, thanks for the comments.

> 1. Are you sure that making 3 synchronous AJAX requests doesn't hurt page download/interactive performance?

It hasn't caused any obvious performance problems for me, but if one
or more of the requests took a long time it could be a problem. Will
investigate how easy it is to move them to asynchronous requests.

> 2. create_manifest.py script apparently doesn't belong here, but rather should be part of linaro-android-build-tools and be called from build-android script at the appropriate place.

Ah, thanks. Will move it.

> 3. Did you give extra thought what manifest.txt may contain? Just filenames? Maybe file size? What about MD5SUMS file we already create for subset of files (actually, one of the reason why is it created for subset and not all was because  that would require faithful duplication of artifact filename patterns - both in Jenkins and in this script, same problem would exist for create_manifest.py too). (I don't have any specific suggestion regarding what manifest.txt should include, just wanted to make sure this is though-out).

The only thing the web UI will use is file names and that is the only
reason to add the file. If we extend its use later we could update the
UI to cope.

> 4. Ah, last one, naming. Maybe "MANIFEST" is better, it's kind of common practice (that depends on contents format of course).

Not a problem - changing a file name is easy enough.

--
James Tunnicliffe

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

On Mon, 23 Jan 2012 16:42:47 -0000
James Tunnicliffe <email address hidden> wrote:

> Hi Paul, thanks for the comments.
>
> > 1. Are you sure that making 3 synchronous AJAX requests doesn't
> > hurt page download/interactive performance?
>
> It hasn't caused any obvious performance problems for me, but if one
> or more of the requests took a long time it could be a problem. Will
> investigate how easy it is to move them to asynchronous requests.

Well, my idea of that would be done was to have chain of async result
handlers - i.e. result handler of 1st request would check if it got
what's needed and if so, display it, otherwise queue up another request
whose handler would do similarly, etc.

I don't pledge for sync processing to be changed immediately, just want
to make sure it is known as point of potential problem and we're ready
to resolve it.

>
> > 2. create_manifest.py script apparently doesn't belong here, but
> > rather should be part of linaro-android-build-tools and be called
> > from build-android script at the appropriate place.
>
> Ah, thanks. Will move it.
>
> > 3. Did you give extra thought what manifest.txt may contain? Just
> > filenames? Maybe file size? What about MD5SUMS file we already
> > create for subset of files (actually, one of the reason why is it
> > created for subset and not all was because  that would require
> > faithful duplication of artifact filename patterns - both in
> > Jenkins and in this script, same problem would exist for
> > create_manifest.py too). (I don't have any specific suggestion
> > regarding what manifest.txt should include, just wanted to make
> > sure this is though-out).
>
> The only thing the web UI will use is file names and that is the only
> reason to add the file. If we extend its use later we could update the
> UI to cope.

Makes sense.

>
> > 4. Ah, last one, naming. Maybe "MANIFEST" is better, it's kind of
> > common practice (that depends on contents format of course).
>
> Not a problem - changing a file name is easy enough.

Ok, so I assume there will be more commits for this merge request
before approving it.

Thanks,
Paul

Linaro.org | Open source software for ARM SoCs
Follow Linaro: http://www.facebook.com/pages/Linaro
http://twitter.com/#!/linaroorg - http://www.linaro.org/linaro-blog

252. By James Tunnicliffe

create_manifest.py moved to linaro-android-build-tools project.
Add android-build.linaro.org as a proxy location
Updated static/buildDetails.js so lava-job-info and MANIFEST files are both sourced from the location returned by getDownloadURL.

TODO: remove as many synchronous Y.io calls as possible. It should be possible to save the result of getDownloadURL and reuse it.

253. By James Tunnicliffe

getDownloadURL - cache result of last call to reduce remote requests. The cached value is deleted on URL change since the JS doesn't reload, so the same cached value would be returned for different builds of the same project without this step.

Revision history for this message
James Tunnicliffe (dooferlad) wrote :

I have just pushed a new version. This doesn't perform asynchronous
requests, but does change the name of manifest.txt to MANIFEST and fix
a couple of bugs.

James

Revision history for this message
Paul Sokolovsky (pfalcon) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'android_build/frontend/api.py'
--- android_build/frontend/api.py 2012-01-13 16:17:34 +0000
+++ android_build/frontend/api.py 2012-01-24 14:42:25 +0000
@@ -255,7 +255,19 @@
255 return resp255 return resp
256 return HttpResponse('foo')256 return HttpResponse('foo')
257257
258def get_lava_job_info(request):258def _match_host(allowed_hosts, url):
259 for host in allowed_hosts:
260 if re.search("^" + host, url):
261 return True
262 return False
263
264def _match_file(allowed_files, url):
265 for file in allowed_files:
266 if re.search(file + "$", url):
267 return True
268 return False
269
270def proxy_remote_file(request):
259 """Give Web UI a proxy to fetch lava-job-info for it to parse.271 """Give Web UI a proxy to fetch lava-job-info for it to parse.
260272
261 This is required because the android files are now protected by the need273 This is required because the android files are now protected by the need
@@ -269,8 +281,13 @@
269 for thing in request.GET.getlist("url"):281 for thing in request.GET.getlist("url"):
270 url = thing282 url = thing
271283
272 if not (re.search("/lava-job-info$", url) and284 allowed_hosts = ["https?://snapshots.linaro.org/",
273 re.search("^https?://snapshots.linaro.org/", url)):285 "https?://android-build.linaro.org"]
286
287 allowed_files = ['lava-job-info', 'manifest.txt']
288
289 if not (_match_host(allowed_hosts, url) and
290 _match_file(allowed_files, url)):
274 raise Http404291 raise Http404
275292
276 try:293 try:
@@ -289,3 +306,38 @@
289 return HttpResponse(message)306 return HttpResponse(message)
290 else:307 else:
291 return HttpResponse(response.read())308 return HttpResponse(response.read())
309
310def test_remote_dir_exists(request):
311 """Give Web UI the ability to test where a build is stored.
312
313 Checks if a directory exists. If it does, return OK (200), else, raise
314 a 404.
315 """
316
317 url = ""
318 for thing in request.GET.getlist("url"):
319 url = thing
320
321 allowed_hosts = ["https?://snapshots.linaro.org/",
322 "https?://android-build.linaro.org"]
323
324 if not (_match_host(allowed_hosts, url) and
325 re.search('/$', url)):
326 raise Http404
327
328 try:
329 urllib2.urlopen(url)
330 except urllib2.HTTPError, e:
331 message = "Server error: %d" % e.code
332 resp = HttpResponse(message)
333 resp.status_code = e.code
334 return resp
335 except urllib2.URLError, e:
336 message = "We failed to reach a server."
337
338 if isinstance(e.reason, str):
339 message += " Reason %s" % e.reason
340
341 return HttpResponse(message)
342 else:
343 return HttpResponse("Found")
292344
=== modified file 'android_build/urls.py'
--- android_build/urls.py 2012-01-09 15:56:08 +0000
+++ android_build/urls.py 2012-01-24 14:42:25 +0000
@@ -27,7 +27,8 @@
27 (r'^api/is-daily$', 'android_build.frontend.api.is_daily'),27 (r'^api/is-daily$', 'android_build.frontend.api.is_daily'),
28 (r'^api/new$', 'android_build.frontend.api.new'),28 (r'^api/new$', 'android_build.frontend.api.new'),
29 (r'^api/set-daily$', 'android_build.frontend.api.set_daily'),29 (r'^api/set-daily$', 'android_build.frontend.api.set_daily'),
30 (r'^api/get-lava-job-info$', 'android_build.frontend.api.get_lava_job_info'),30 (r'^api/proxy-remote-file$', 'android_build.frontend.api.proxy_remote_file'),
31 (r'^api/test-dir-exists$', 'android_build.frontend.api.test_remote_dir_exists'),
3132
32 (r'^openid/', include('django_openid_auth.urls')),33 (r'^openid/', include('django_openid_auth.urls')),
33 (r'^logout$', 'django.contrib.auth.views.logout'),34 (r'^logout$', 'django.contrib.auth.views.logout'),
3435
=== modified file 'static/buildDetails.js'
--- static/buildDetails.js 2012-01-13 17:01:54 +0000
+++ static/buildDetails.js 2012-01-24 14:42:25 +0000
@@ -8,6 +8,11 @@
8 'history:change',8 'history:change',
9 function (e) {9 function (e) {
10 if (e.src === Y.HistoryHash.SRC_HASH) {10 if (e.src === Y.HistoryHash.SRC_HASH) {
11 // URL changed
12 if (typeof getDownloadURL.downloadUrl != "undefined")
13 {
14 delete getDownloadURL.downloadUrl;
15 }
11 displayJobByNumber(e.changed.build.newVal);16 displayJobByNumber(e.changed.build.newVal);
12 }17 }
13 }18 }
@@ -120,6 +125,56 @@
120 );125 );
121 }126 }
122127
128 function getProxyURL ()
129 {
130 return '/api/proxy-remote-file?url=';
131 }
132
133 function getDownloadURL(results)
134 {
135 /* Work out where to download from. The assumption is that all
136 downloads will end up on snapshots.linaro.org soon, but we want to
137 detect if they haven't moved. If we can't find a build on
138 snapshots and we can find it locally, point the download location
139 to the local build. If we can't find it locally either, keep pointing
140 at snapshots.
141 */
142
143 if (typeof getDownloadURL.downloadUrl != "undefined")
144 {
145 return getDownloadURL.downloadUrl; // Return cached result
146 }
147
148 var downloadUrl = 'http://snapshots.linaro.org/android/' + rawBuildName + '/' + results.number.toString() + '/';
149
150 var test_snapshots = Y.io(
151 '/api/test-dir-exists?url=' + downloadUrl,
152 {
153 sync: true
154 });
155
156 if (test_snapshots.status != 200) // snapshots.linaro.org doesn't host this build
157 {
158 // Test to see if the local server (probably android-build.linaro.org) does.
159 var testUrl = 'http://android-build.linaro.org/builds/' + rawBuildName + '/' + results.number.toString() + '/';
160
161 var test_local = Y.io(
162 '/api/test-dir-exists?url=' + testUrl,
163 {
164 sync: true
165 });
166
167 if (test_local.status == 200)
168 {
169 // Found a build host - update download link.
170 downloadUrl = testUrl;
171 }
172 }
173
174 getDownloadURL.downloadUrl = downloadUrl; // Save result to speed up future calls
175 return downloadUrl;
176 }
177
123 var element_mapping = {178 var element_mapping = {
124 status: function (results) {179 status: function (results) {
125 return results.result || "RUNNING";180 return results.result || "RUNNING";
@@ -157,11 +212,10 @@
157 },212 },
158 testresults: function (results) {213 testresults: function (results) {
159214
160 var new_style_url = '/api/get-lava-job-info?url=http://snapshots.linaro.org/android/'215 var downloadUrl = getDownloadURL(results);
161 + rawBuildName + '/' + results.number.toString() + '/lava-job-info';
162216
163 Y.io(217 Y.io(
164 new_style_url,218 getProxyURL() + downloadUrl + '/lava-job-info',
165 {219 {
166 on: { success: receivedLavaJobInfo,220 on: { success: receivedLavaJobInfo,
167 failure: function(id, resp) {221 failure: function(id, resp) {
@@ -172,14 +226,54 @@
172226
173 return 'Loading...';227 return 'Loading...';
174 },228 },
229
175 output: function (results) {230 output: function (results) {
176 var downloadAreaUrl = 'http://snapshots.linaro.org/android/' + rawBuildName + '/' + results.number.toString() + '/';231 /* Generate a download list */
177 Y.one('#all-output a').setAttribute(232
178 'href', downloadAreaUrl);233 /* In case getDownloadURL fails for some reason, we always have a link
234 to where new builds should be.
235 */
236 var downloadUrl = 'http://snapshots.linaro.org/android/' + rawBuildName + '/' + results.number.toString() + '/';
237
238 Y.one('#all-output a').setAttribute('href', downloadUrl);
179 Y.one('#all-output a').removeClass('hidden');239 Y.one('#all-output a').removeClass('hidden');
180 Y.one('#all-output span').addClass('hidden');240 Y.one('#all-output span').addClass('hidden');
181 if (results.artifacts.length) {241
182 var downloadUrl = 'http://' + window.location.hostname + '/builds/' + rawBuildName + '/' + results.number.toString() + '/';242 downloadUrl = getDownloadURL(results);
243
244 /* Decision made about downloadUrl. Now use it. We test to see if we
245 can find manifest.txt, which is a list of all files available for
246 download. If we can't find it we try using results.artifacts.
247 If that fails, we display a message saying that we can't provide
248 a downloads list.
249 */
250 var io_result = Y.io(
251 getProxyURL() + downloadUrl + '/MANIFEST',
252 {
253 sync: true
254 });
255
256 if (io_result.status == 200)
257 {
258 var manifest = io_result.responseText.split("\n");
259
260 var listNode = Y.Node.create('<ul/>');
261
262 for (var i = 0; i < manifest.length; i++)
263 {
264 if (!manifest[i].match(/\S/))
265 {
266 continue;
267 }
268 var itemNode = Y.Node.create('<li><a/></li>');
269 itemNode.one('a').setContent(manifest[i]);
270 itemNode.one('a').setAttribute('href', downloadUrl + manifest[i]);
271 listNode.appendChild(itemNode);
272 }
273 return listNode;
274 }
275 else if (results.artifacts.length)
276 {
183 var listNode = Y.Node.create('<ul/>');277 var listNode = Y.Node.create('<ul/>');
184 for (var i = 0; i < results.artifacts.length; i++) {278 for (var i = 0; i < results.artifacts.length; i++) {
185 var artifact = results.artifacts[i];279 var artifact = results.artifacts[i];
@@ -190,10 +284,10 @@
190 listNode.appendChild(itemNode);284 listNode.appendChild(itemNode);
191 }285 }
192 return listNode;286 return listNode;
193 } else {287 }
194// Y.one('#all-output a').addClass('hidden');288 else
195// Y.one('#all-output span').removeClass('hidden');289 {
196 return Y.Node.create("<p>Build artifacts not known</p>");290 return Y.Node.create('<p>Unable to find downloads list.</p>')
197 }291 }
198 }292 }
199 };293 };

Subscribers

People subscribed via source and target branches