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
1=== modified file 'android_build/frontend/api.py'
2--- android_build/frontend/api.py 2012-01-13 16:17:34 +0000
3+++ android_build/frontend/api.py 2012-01-24 14:42:25 +0000
4@@ -255,7 +255,19 @@
5 return resp
6 return HttpResponse('foo')
7
8-def get_lava_job_info(request):
9+def _match_host(allowed_hosts, url):
10+ for host in allowed_hosts:
11+ if re.search("^" + host, url):
12+ return True
13+ return False
14+
15+def _match_file(allowed_files, url):
16+ for file in allowed_files:
17+ if re.search(file + "$", url):
18+ return True
19+ return False
20+
21+def proxy_remote_file(request):
22 """Give Web UI a proxy to fetch lava-job-info for it to parse.
23
24 This is required because the android files are now protected by the need
25@@ -269,8 +281,13 @@
26 for thing in request.GET.getlist("url"):
27 url = thing
28
29- if not (re.search("/lava-job-info$", url) and
30- re.search("^https?://snapshots.linaro.org/", url)):
31+ allowed_hosts = ["https?://snapshots.linaro.org/",
32+ "https?://android-build.linaro.org"]
33+
34+ allowed_files = ['lava-job-info', 'manifest.txt']
35+
36+ if not (_match_host(allowed_hosts, url) and
37+ _match_file(allowed_files, url)):
38 raise Http404
39
40 try:
41@@ -289,3 +306,38 @@
42 return HttpResponse(message)
43 else:
44 return HttpResponse(response.read())
45+
46+def test_remote_dir_exists(request):
47+ """Give Web UI the ability to test where a build is stored.
48+
49+ Checks if a directory exists. If it does, return OK (200), else, raise
50+ a 404.
51+ """
52+
53+ url = ""
54+ for thing in request.GET.getlist("url"):
55+ url = thing
56+
57+ allowed_hosts = ["https?://snapshots.linaro.org/",
58+ "https?://android-build.linaro.org"]
59+
60+ if not (_match_host(allowed_hosts, url) and
61+ re.search('/$', url)):
62+ raise Http404
63+
64+ try:
65+ urllib2.urlopen(url)
66+ except urllib2.HTTPError, e:
67+ message = "Server error: %d" % e.code
68+ resp = HttpResponse(message)
69+ resp.status_code = e.code
70+ return resp
71+ except urllib2.URLError, e:
72+ message = "We failed to reach a server."
73+
74+ if isinstance(e.reason, str):
75+ message += " Reason %s" % e.reason
76+
77+ return HttpResponse(message)
78+ else:
79+ return HttpResponse("Found")
80
81=== modified file 'android_build/urls.py'
82--- android_build/urls.py 2012-01-09 15:56:08 +0000
83+++ android_build/urls.py 2012-01-24 14:42:25 +0000
84@@ -27,7 +27,8 @@
85 (r'^api/is-daily$', 'android_build.frontend.api.is_daily'),
86 (r'^api/new$', 'android_build.frontend.api.new'),
87 (r'^api/set-daily$', 'android_build.frontend.api.set_daily'),
88- (r'^api/get-lava-job-info$', 'android_build.frontend.api.get_lava_job_info'),
89+ (r'^api/proxy-remote-file$', 'android_build.frontend.api.proxy_remote_file'),
90+ (r'^api/test-dir-exists$', 'android_build.frontend.api.test_remote_dir_exists'),
91
92 (r'^openid/', include('django_openid_auth.urls')),
93 (r'^logout$', 'django.contrib.auth.views.logout'),
94
95=== modified file 'static/buildDetails.js'
96--- static/buildDetails.js 2012-01-13 17:01:54 +0000
97+++ static/buildDetails.js 2012-01-24 14:42:25 +0000
98@@ -8,6 +8,11 @@
99 'history:change',
100 function (e) {
101 if (e.src === Y.HistoryHash.SRC_HASH) {
102+ // URL changed
103+ if (typeof getDownloadURL.downloadUrl != "undefined")
104+ {
105+ delete getDownloadURL.downloadUrl;
106+ }
107 displayJobByNumber(e.changed.build.newVal);
108 }
109 }
110@@ -120,6 +125,56 @@
111 );
112 }
113
114+ function getProxyURL ()
115+ {
116+ return '/api/proxy-remote-file?url=';
117+ }
118+
119+ function getDownloadURL(results)
120+ {
121+ /* Work out where to download from. The assumption is that all
122+ downloads will end up on snapshots.linaro.org soon, but we want to
123+ detect if they haven't moved. If we can't find a build on
124+ snapshots and we can find it locally, point the download location
125+ to the local build. If we can't find it locally either, keep pointing
126+ at snapshots.
127+ */
128+
129+ if (typeof getDownloadURL.downloadUrl != "undefined")
130+ {
131+ return getDownloadURL.downloadUrl; // Return cached result
132+ }
133+
134+ var downloadUrl = 'http://snapshots.linaro.org/android/' + rawBuildName + '/' + results.number.toString() + '/';
135+
136+ var test_snapshots = Y.io(
137+ '/api/test-dir-exists?url=' + downloadUrl,
138+ {
139+ sync: true
140+ });
141+
142+ if (test_snapshots.status != 200) // snapshots.linaro.org doesn't host this build
143+ {
144+ // Test to see if the local server (probably android-build.linaro.org) does.
145+ var testUrl = 'http://android-build.linaro.org/builds/' + rawBuildName + '/' + results.number.toString() + '/';
146+
147+ var test_local = Y.io(
148+ '/api/test-dir-exists?url=' + testUrl,
149+ {
150+ sync: true
151+ });
152+
153+ if (test_local.status == 200)
154+ {
155+ // Found a build host - update download link.
156+ downloadUrl = testUrl;
157+ }
158+ }
159+
160+ getDownloadURL.downloadUrl = downloadUrl; // Save result to speed up future calls
161+ return downloadUrl;
162+ }
163+
164 var element_mapping = {
165 status: function (results) {
166 return results.result || "RUNNING";
167@@ -157,11 +212,10 @@
168 },
169 testresults: function (results) {
170
171- var new_style_url = '/api/get-lava-job-info?url=http://snapshots.linaro.org/android/'
172- + rawBuildName + '/' + results.number.toString() + '/lava-job-info';
173+ var downloadUrl = getDownloadURL(results);
174
175 Y.io(
176- new_style_url,
177+ getProxyURL() + downloadUrl + '/lava-job-info',
178 {
179 on: { success: receivedLavaJobInfo,
180 failure: function(id, resp) {
181@@ -172,14 +226,54 @@
182
183 return 'Loading...';
184 },
185+
186 output: function (results) {
187- var downloadAreaUrl = 'http://snapshots.linaro.org/android/' + rawBuildName + '/' + results.number.toString() + '/';
188- Y.one('#all-output a').setAttribute(
189- 'href', downloadAreaUrl);
190+ /* Generate a download list */
191+
192+ /* In case getDownloadURL fails for some reason, we always have a link
193+ to where new builds should be.
194+ */
195+ var downloadUrl = 'http://snapshots.linaro.org/android/' + rawBuildName + '/' + results.number.toString() + '/';
196+
197+ Y.one('#all-output a').setAttribute('href', downloadUrl);
198 Y.one('#all-output a').removeClass('hidden');
199 Y.one('#all-output span').addClass('hidden');
200- if (results.artifacts.length) {
201- var downloadUrl = 'http://' + window.location.hostname + '/builds/' + rawBuildName + '/' + results.number.toString() + '/';
202+
203+ downloadUrl = getDownloadURL(results);
204+
205+ /* Decision made about downloadUrl. Now use it. We test to see if we
206+ can find manifest.txt, which is a list of all files available for
207+ download. If we can't find it we try using results.artifacts.
208+ If that fails, we display a message saying that we can't provide
209+ a downloads list.
210+ */
211+ var io_result = Y.io(
212+ getProxyURL() + downloadUrl + '/MANIFEST',
213+ {
214+ sync: true
215+ });
216+
217+ if (io_result.status == 200)
218+ {
219+ var manifest = io_result.responseText.split("\n");
220+
221+ var listNode = Y.Node.create('<ul/>');
222+
223+ for (var i = 0; i < manifest.length; i++)
224+ {
225+ if (!manifest[i].match(/\S/))
226+ {
227+ continue;
228+ }
229+ var itemNode = Y.Node.create('<li><a/></li>');
230+ itemNode.one('a').setContent(manifest[i]);
231+ itemNode.one('a').setAttribute('href', downloadUrl + manifest[i]);
232+ listNode.appendChild(itemNode);
233+ }
234+ return listNode;
235+ }
236+ else if (results.artifacts.length)
237+ {
238 var listNode = Y.Node.create('<ul/>');
239 for (var i = 0; i < results.artifacts.length; i++) {
240 var artifact = results.artifacts[i];
241@@ -190,10 +284,10 @@
242 listNode.appendChild(itemNode);
243 }
244 return listNode;
245- } else {
246-// Y.one('#all-output a').addClass('hidden');
247-// Y.one('#all-output span').removeClass('hidden');
248- return Y.Node.create("<p>Build artifacts not known</p>");
249+ }
250+ else
251+ {
252+ return Y.Node.create('<p>Unable to find downloads list.</p>')
253 }
254 }
255 };

Subscribers

People subscribed via source and target branches