Merge lp:~milo/linaro-ci-dashboard/android-build-model-refactor into lp:linaro-ci-dashboard

Proposed by Milo Casagrande
Status: Merged
Approved by: Stevan Radaković
Approved revision: 31
Merged at revision: 19
Proposed branch: lp:~milo/linaro-ci-dashboard/android-build-model-refactor
Merge into: lp:linaro-ci-dashboard
Diff against target: 710 lines (+372/-60)
23 files modified
Makefile (+10/-7)
README (+1/-1)
dashboard/frontend/android_build/migrations/0001_initial.py (+92/-0)
dashboard/frontend/android_build/models/__init__.py (+1/-0)
dashboard/frontend/android_build/models/android_loop.py (+127/-0)
dashboard/frontend/android_build/urls.py (+34/-0)
dashboard/frontend/integration_loop/forms/integration_loop_form.py (+1/-1)
dashboard/frontend/integration_loop/migrations/0001_initial.py (+50/-0)
dashboard/frontend/integration_loop/models/__init__.py (+1/-0)
dashboard/frontend/integration_loop/models/integration_loop.py (+1/-2)
dashboard/frontend/integration_loop/urls.py (+34/-0)
dashboard/frontend/integration_loop/views/integration_loop_create_view.py (+3/-3)
dashboard/frontend/integration_loop/views/integration_loop_detail_view.py (+1/-1)
dashboard/frontend/integration_loop/views/integration_loop_update_view.py (+3/-3)
dashboard/frontend/migrations/0001_initial.py (+0/-19)
dashboard/frontend/models/__init__.py (+0/-1)
dashboard/frontend/models/loop.py (+0/-1)
dashboard/frontend/tests/test_views.py (+6/-6)
dashboard/frontend/urls.py (+1/-12)
dashboard/frontend/views/index_view.py (+1/-1)
dashboard/frontend/views/loop_build_view.py (+0/-2)
dashboard/settings.py (+3/-0)
dashboard/urls.py (+2/-0)
To merge this branch: bzr merge lp:~milo/linaro-ci-dashboard/android-build-model-refactor
Reviewer Review Type Date Requested Status
Stevan Radaković code, modeling Approve
Georgy Redkozubov Approve
Linaro Infrastructure Pending
Review via email: mp+119580@code.launchpad.net

Description of the change

This is the refactoring of the android build model, as an "inside app", under the "frontend" one.
If this is the agreed approach, I will continue and refactor the rest (mostly the integration_loop one, keeping the loop model under the frontend).

To post a comment you must log in.
Revision history for this message
Georgy Redkozubov (gesha) wrote :

The starting steps look good.

review: Approve
Revision history for this message
Stevan Radaković (stevanr) wrote :

Thanks for refactoring things Milo.

One thing still bothers me a lot and that's the jenkins config.
In my opinion, this should not be separated from the jenkins server model, because at some point (even though the jenkins server is internally considered singleton for now), we might have more then one jenkins server connected with our app and then we will not know which config refers to which server. And it just makes sense that the jenkins server configuration is part of the jenkins server itself, n'est-ce pas? :)

So if you agree with this there are two possible solutions that I see (with me routing for the first one):
1. Remove the config model and add the two fields (config_template and config) to the jenkins server model.
2. Keep the config model, but add one-to-one relation between config model and jenkins server model. This will make the relation between the Loop and the JenkinsConfig models obsolete so it should be removed.

review: Needs Fixing (modeling)
Revision history for this message
Georgy Redkozubov (gesha) wrote :

> Thanks for refactoring things Milo.
>
> One thing still bothers me a lot and that's the jenkins config.
> In my opinion, this should not be separated from the jenkins server model,
> because at some point (even though the jenkins server is internally considered
> singleton for now), we might have more then one jenkins server connected with
> our app and then we will not know which config refers to which server. And it
> just makes sense that the jenkins server configuration is part of the jenkins
> server itself, n'est-ce pas? :)

Good point, but the it's not bound to the jenkins server, ok, better to say it's a job config for a particular loop which configuration is based on the template and filled with the data before sending it to jenkins job.
There were 3 options:
1) Generate all needed job config fields on the fly from DB when creating/updating job.
2) Add fields to Loop model and do it once when creating/updating job.
3) Add new model and link it to Loop and do it once when creating/updating job.
Second approach wonn't work for us, in case we need to add new configuration fields we have to update all loops.
First and third approaches are similar.
We decided to go third way. We can have common configuration fields like 'command', 'config' generated in one place and specific to particular loop type generate in that loop class.
May be I'm making things complicate and there is easier way? :)

>
> So if you agree with this there are two possible solutions that I see (with me
> routing for the first one):
> 1. Remove the config model and add the two fields (config_template and config)
> to the jenkins server model.
> 2. Keep the config model, but add one-to-one relation between config model and
> jenkins server model. This will make the relation between the Loop and the
> JenkinsConfig models obsolete so it should be removed.

Revision history for this message
Stevan Radaković (stevanr) wrote :

> > Thanks for refactoring things Milo.
> >
> > One thing still bothers me a lot and that's the jenkins config.
> > In my opinion, this should not be separated from the jenkins server model,
> > because at some point (even though the jenkins server is internally
> considered
> > singleton for now), we might have more then one jenkins server connected
> with
> > our app and then we will not know which config refers to which server. And
> it
> > just makes sense that the jenkins server configuration is part of the
> jenkins
> > server itself, n'est-ce pas? :)
>
> Good point, but the it's not bound to the jenkins server, ok, better to say
> it's a job config for a particular loop which configuration is based on the
> template and filled with the data before sending it to jenkins job.
> There were 3 options:
> 1) Generate all needed job config fields on the fly from DB when
> creating/updating job.
> 2) Add fields to Loop model and do it once when creating/updating job.
> 3) Add new model and link it to Loop and do it once when creating/updating
> job.
> Second approach wonn't work for us, in case we need to add new configuration
> fields we have to update all loops.
> First and third approaches are similar.
> We decided to go third way. We can have common configuration fields like
> 'command', 'config' generated in one place and specific to particular loop
> type generate in that loop class.
> May be I'm making things complicate and there is easier way? :)
>

Ok sorry I didn't know that it's representing a job config since it's called the JenkinsConfig and it's part of the jenkinsserver APP, so I realized it's representing general Jenkins server configuration.

With that thing cleared up, I would like us to remember general guidelines for the CI Loops development presented by Danilo couple of weeks ago. The point was that we have as small amount of dependency on jenkins in our frontend app as possible.

In my opinion this new dependency is redundant since we must update the jenkins job each time the loop is created or updated anyway, so why keep it in the DB when we must do it on-the-fly anyway.
If we rly want to keep the job config anyway, I'd suggest linking it with loop the other way around, by putting loop_id as foreign key in JenkinsConfig (which should be renamed anyway if we decide to keep it) and adding unique constraint, so it's one-on-one relationship. Of course, all the updates to the jenkins config table should then be from the jenkinsserver up, and in no case from the frontend.

I hope this clears up the things a bit..

> >
> > So if you agree with this there are two possible solutions that I see (with
> me
> > routing for the first one):
> > 1. Remove the config model and add the two fields (config_template and
> config)
> > to the jenkins server model.
> > 2. Keep the config model, but add one-to-one relation between config model
> and
> > jenkins server model. This will make the relation between the Loop and the
> > JenkinsConfig models obsolete so it should be removed.

Revision history for this message
Milo Casagrande (milo) wrote :

On Wed, Aug 15, 2012 at 12:50 PM, Stevan Radaković
<email address hidden> wrote:
>
> Ok sorry I didn't know that it's representing a job config since it's called the JenkinsConfig and it's part of the jenkinsserver APP, so I realized it's representing general Jenkins server configuration.

Our bad on choosing that name I guess... but as gesha explained, is a
parameter necessary for the the Android Build job, and is called
CONFIG inside Jenkins.

> With that thing cleared up, I would like us to remember general guidelines for the CI Loops development presented by Danilo couple of weeks ago. The point was that we have as small amount of dependency on jenkins in our frontend app as possible.

Yep, that is right.

> In my opinion this new dependency is redundant since we must update the jenkins job each time the loop is created or updated anyway, so why keep it in the DB when we must do it on-the-fly anyway.

From what I saw from the Android builds, they are updated seldomly.
Once a loop is created and doesn't explode, it is left like that.
The on-the-fly option was considered, but since jobs are not updated
that much, it made more sense to store that parameter somewhere and
retrieve it. I think it is more of a tradeoff between taking up a
little bit more space, than starting to create base64 encoded strings
for all the builds on the fly.

> If we rly want to keep the job config anyway, I'd suggest linking it with loop the other way around, by putting loop_id as foreign key in JenkinsConfig (which should be renamed anyway if we decide to keep it) and adding unique constraint, so it's one-on-one relationship. Of course, all the updates to the jenkins config table should then be from the jenkinsserver up, and in no case from the frontend.

Name will be changed, for sure.
Good point about reverting the direction of the relationship. For the
constraint I was considering it, but didn't add it since I wanted to
see if we needed other fields in there that could have been unique
together too.

Another thing, since this is only related to Android, could be to
extend the android_loop model and store the base64 encoded "CONFIG" in
there.

--
Milo Casagrande
Infrastructure Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs

27. By Milo Casagrande

Removed JenkinsConfig.

Revision history for this message
Milo Casagrande (milo) wrote :

With revno 27 I removed the JenkinsConfig class as discussed during the meeting.

28. By Milo Casagrande

Removed jenkins_config from migrations.

29. By Milo Casagrande

integration_loop as subapps inside frontend.

30. By Milo Casagrande

Fixed Makefile, fixed imports in tests.

31. By Milo Casagrande

Fixed problems with tests (remember to clear up .pyc files).

Revision history for this message
Milo Casagrande (milo) wrote :

With revno 31 there is the final refactoring of integration_loop as a subapp, and tests are now fixed (had problems with .pyc files around in the projects dir).

Revision history for this message
Stevan Radaković (stevanr) wrote :

I thinks we will still need to talk about the layout of subapps in the frontend app, but this is good enough for landing, we can easily refactor later.
Approve +1.

review: Approve (code, modeling)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2012-07-19 08:00:53 +0000
3+++ Makefile 2012-08-17 07:43:18 +0000
4@@ -1,17 +1,21 @@
5 # Makefile for linaro-ci-dashboard
6
7 help:
8- @echo "Please use \`make <target>' where <target> is one of"
9- @echo " migration to update the database schema with the" \
10+ @echo "Please use \`make <target>' where <target> is one of:"
11+ @echo " create-migration to update the database schema with the" \
12 "latest model changes"
13- @echo " runserver to run the django web server"
14- @echo " test to run the unit tests"
15+ @echo " runserver to run the django web server"
16+ @echo " test to run the unit tests"
17
18 create-migration: syncdb
19 python dashboard/manage.py schemamigration frontend --auto || \
20 echo "No changes to frontend models."
21- python dashboard/manage.py schemamigration jenkins --auto || \
22+ python dashboard/manage.py schemamigration jenkinsserver --auto || \
23 echo "No changes to jenkins models."
24+ python dashboard/manage.py schemamigration android_build --auto || \
25+ echo "No changes to android_build models."
26+ python dashboard/manage.py schemamigration integration_loop --auto || \
27+ echo "No changes to integration_loop models."
28 python dashboard/manage.py migrate
29
30 migrate: syncdb
31@@ -25,7 +29,6 @@
32
33 test:
34 python dashboard/manage.py jenkins start
35- python dashboard/manage.py test frontend \
36- jenkinsserver
37+ python dashboard/manage.py test frontend jenkinsserver
38
39 .PHONY: help migration test
40
41=== modified file 'README'
42--- README 2012-07-24 21:34:26 +0000
43+++ README 2012-08-17 07:43:18 +0000
44@@ -46,7 +46,7 @@
45 Whenever any of the models are changed, it is strongly advised to run
46 South' schemamigration command:
47
48- $ make migration
49+ $ make migrate
50
51
52 Tests
53
54=== added directory 'dashboard/frontend/android_build'
55=== added file 'dashboard/frontend/android_build/__init__.py'
56=== added directory 'dashboard/frontend/android_build/migrations'
57=== added file 'dashboard/frontend/android_build/migrations/0001_initial.py'
58--- dashboard/frontend/android_build/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
59+++ dashboard/frontend/android_build/migrations/0001_initial.py 2012-08-17 07:43:18 +0000
60@@ -0,0 +1,92 @@
61+# encoding: utf-8
62+import datetime
63+from south.db import db
64+from south.v2 import SchemaMigration
65+from django.db import models
66+
67+class Migration(SchemaMigration):
68+
69+ def forwards(self, orm):
70+
71+ # Adding model 'AndroidLoop'
72+ db.create_table('android_build_androidloop', (
73+ ('loop_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['frontend.Loop'], unique=True, primary_key=True)),
74+ ('build_type', self.gf('django.db.models.fields.CharField')(max_length=100)),
75+ ('manifest_repo', self.gf('django.db.models.fields.CharField')(default='git://android.git.kernel.org/platform/manifest.git', max_length=255)),
76+ ('manifest_branch', self.gf('django.db.models.fields.CharField')(default='master', max_length=50)),
77+ ('repo_quiet', self.gf('django.db.models.fields.BooleanField')(default=False)),
78+ ('manifest_filename', self.gf('django.db.models.fields.CharField')(default='default.xml', max_length=255)),
79+ ('make_targets', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
80+ ('make_jobs', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
81+ ('ramdisk_size', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
82+ ('external_tarball', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
83+ ('build_fs_image', self.gf('django.db.models.fields.BooleanField')(default=False)),
84+ ('fs_image_size', self.gf('django.db.models.fields.CharField')(default='2G', max_length=20, blank=True)),
85+ ('user_defined_values', self.gf('django.db.models.fields.TextField')(blank=True)),
86+ ('toolchain_url', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
87+ ('target_product', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True)),
88+ ('target_tools_prefix', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
89+ ('binutils_version', self.gf('django.db.models.fields.CharField')(default='2.20.1', max_length=25)),
90+ ('binutils_url', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
91+ ('gcc_version', self.gf('django.db.models.fields.CharField')(default='4.4.0', max_length=25)),
92+ ('gcc_url', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
93+ ('sysroot_ndk_url', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
94+ ('lava_submit', self.gf('django.db.models.fields.BooleanField')(default=False)),
95+ ('lava_test_plan', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
96+ ('lava_submit_fatal', self.gf('django.db.models.fields.BooleanField')(default=True)),
97+ ('lava_android_binaries', self.gf('django.db.models.fields.BooleanField')(default=True)),
98+ ))
99+ db.send_create_signal('android_build', ['AndroidLoop'])
100+
101+
102+ def backwards(self, orm):
103+
104+ # Deleting model 'AndroidLoop'
105+ db.delete_table('android_build_androidloop')
106+
107+
108+ models = {
109+ 'android_build.androidloop': {
110+ 'Meta': {'object_name': 'AndroidLoop', '_ormbases': ['frontend.Loop']},
111+ 'binutils_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
112+ 'binutils_version': ('django.db.models.fields.CharField', [], {'default': "'2.20.1'", 'max_length': '25'}),
113+ 'build_fs_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
114+ 'build_type': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
115+ 'external_tarball': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
116+ 'fs_image_size': ('django.db.models.fields.CharField', [], {'default': "'2G'", 'max_length': '20', 'blank': 'True'}),
117+ 'gcc_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
118+ 'gcc_version': ('django.db.models.fields.CharField', [], {'default': "'4.4.0'", 'max_length': '25'}),
119+ 'lava_android_binaries': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
120+ 'lava_submit': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
121+ 'lava_submit_fatal': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
122+ 'lava_test_plan': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
123+ 'loop_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['frontend.Loop']", 'unique': 'True', 'primary_key': 'True'}),
124+ 'make_jobs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
125+ 'make_targets': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
126+ 'manifest_branch': ('django.db.models.fields.CharField', [], {'default': "'master'", 'max_length': '50'}),
127+ 'manifest_filename': ('django.db.models.fields.CharField', [], {'default': "'default.xml'", 'max_length': '255'}),
128+ 'manifest_repo': ('django.db.models.fields.CharField', [], {'default': "'git://android.git.kernel.org/platform/manifest.git'", 'max_length': '255'}),
129+ 'ramdisk_size': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
130+ 'repo_quiet': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
131+ 'sysroot_ndk_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
132+ 'target_product': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
133+ 'target_tools_prefix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
134+ 'toolchain_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
135+ 'user_defined_values': ('django.db.models.fields.TextField', [], {'blank': 'True'})
136+ },
137+ 'frontend.loop': {
138+ 'Meta': {'object_name': 'Loop'},
139+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
141+ 'server': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkinsserver.JenkinsServer']"})
142+ },
143+ 'jenkinsserver.jenkinsserver': {
144+ 'Meta': {'object_name': 'JenkinsServer'},
145+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
146+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
147+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
148+ 'username': ('django.db.models.fields.CharField', [], {'max_length': '100'})
149+ }
150+ }
151+
152+ complete_apps = ['android_build']
153
154=== added file 'dashboard/frontend/android_build/migrations/__init__.py'
155=== added directory 'dashboard/frontend/android_build/models'
156=== added file 'dashboard/frontend/android_build/models/__init__.py'
157--- dashboard/frontend/android_build/models/__init__.py 1970-01-01 00:00:00 +0000
158+++ dashboard/frontend/android_build/models/__init__.py 2012-08-17 07:43:18 +0000
159@@ -0,0 +1,1 @@
160+from frontend.android_build.models.android_loop import AndroidLoop
161
162=== added file 'dashboard/frontend/android_build/models/android_loop.py'
163--- dashboard/frontend/android_build/models/android_loop.py 1970-01-01 00:00:00 +0000
164+++ dashboard/frontend/android_build/models/android_loop.py 2012-08-17 07:43:18 +0000
165@@ -0,0 +1,127 @@
166+# Copyright (C) 2012 Linaro
167+#
168+# This file is part of linaro-ci-dashboard.
169+#
170+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
171+# it under the terms of the GNU Affero General Public License as published by
172+# the Free Software Foundation, either version 3 of the License, or
173+# (at your option) any later version.
174+#
175+# linaro-ci-dashboard is distributed in the hope that it will be useful,
176+# but WITHOUT ANY WARRANTY; without even the implied warranty of
177+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
178+# GNU Affero General Public License for more details.
179+#
180+# You should have received a copy of the GNU Affero General Public License
181+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
182+
183+from django.db import models
184+from frontend.models.loop import Loop
185+from jenkinsserver.models.jenkins_server import JenkinsServer
186+
187+
188+# The internal names for the different build type, and a user visible name
189+BUILD_ANDROID = (r'build-android', 'Android')
190+BUILD_ANDROID_RESTRICTED = (r'build-android-restricted', 'Android Restricted')
191+BUILD_ANDROID_TOOLCHAIN = (r'build-android-toolchain', 'Android Toolchain')
192+BUILD_ANDROID_TOOLCHAIN_LINARO = (r'build-android-toolchain-linaro',
193+ 'Android Toolchain Linaro')
194+# All the supported build types.
195+ANDROID_BUILD_TYPES = (
196+ BUILD_ANDROID,
197+ BUILD_ANDROID_RESTRICTED,
198+ BUILD_ANDROID_TOOLCHAIN,
199+ BUILD_ANDROID_TOOLCHAIN_LINARO,
200+ )
201+
202+# Inetrnal name of the board, and a user visible name
203+BEAGLEBOARD = (r'beagleboard', 'Beagleboard')
204+PANDABOARD = (r'panda', 'Pandaboard')
205+SNOWBALL = (r'snowball', 'Snowball')
206+# The supported boards for the build.
207+TARGET_BOARDS = (BEAGLEBOARD, PANDABOARD, SNOWBALL)
208+
209+
210+class AndroidLoop(Loop):
211+ """Defines the base class for an Android integration loop."""
212+ class Meta:
213+ app_label = 'android_build'
214+
215+ # The default file name for the manifest file.
216+ DEFAULT_MANIFEST_FILE = 'default.xml'
217+ DEFAULT_REPO = 'git://android.git.kernel.org/platform/manifest.git'
218+ DEFAULT_BRANCH = 'master'
219+ DEFAULT_BINUTILS = '2.20.1'
220+ DEFAULT_GCC = '4.4.0'
221+ DEFAULT_IMAGE_SIZE = '2G'
222+
223+ # BUILD_TYPE
224+ build_type = models.CharField(max_length=100, choices=ANDROID_BUILD_TYPES)
225+ # MANIFEST_REPO
226+ manifest_repo = models.CharField(max_length=255, default=DEFAULT_REPO)
227+ # MANIFEST_BRANCH
228+ manifest_branch = models.CharField(max_length=50, default=DEFAULT_BRANCH)
229+ # REPO_QUIET
230+ repo_quiet = models.BooleanField(default=False)
231+ # MANIFEST_FILENAME
232+ manifest_filename = models.CharField(max_length=255,
233+ default=DEFAULT_MANIFEST_FILE)
234+ # MAKE_TARGETS
235+ make_targets = models.CharField(max_length=255, blank=True)
236+ # MAKE_JOBS
237+ make_jobs = models.PositiveIntegerField(default=0)
238+ # RAMDISK_SIZE
239+ ramdisk_size = models.PositiveIntegerField(default=0)
240+ # EXTERNAL_TARBALL
241+ external_tarball = models.CharField(max_length=255, blank=True)
242+ # BUILD_FS_IMAGE
243+ build_fs_image = models.BooleanField(default=False,
244+ verbose_name='Build filesystem image')
245+ # FS_IMAGE_SIZE
246+ fs_image_size = models.CharField(max_length=20,
247+ blank=True,
248+ verbose_name='Filesystem image size',
249+ default=DEFAULT_IMAGE_SIZE)
250+ # Users might want to define their own values.
251+ user_defined_values = models.TextField(blank=True)
252+
253+ # Data needed for build of type build-android.
254+ toolchain_url = models.CharField(max_length=255, blank=True)
255+ target_product = models.CharField(max_length=50, choices=TARGET_BOARDS,
256+ blank=True)
257+ target_tools_prefix = models.CharField(max_length=255, blank=True)
258+
259+ # Data needed for build of type build-android-toolchain.
260+ binutils_version = models.CharField(max_length=25,
261+ default=DEFAULT_BINUTILS)
262+ binutils_url = models.CharField(max_length=255, blank=True)
263+
264+ gcc_version = models.CharField(max_length=25, default=DEFAULT_GCC)
265+ gcc_url = models.CharField(max_length=255, blank=True)
266+
267+ # TODO need to check the default value for this one.
268+ sysroot_ndk_url = models.CharField(max_length=255, blank=True)
269+
270+ # Data needed for LAVA integration.
271+ lava_submit = models.BooleanField(default=False)
272+ # TODO need to know the values here
273+ lava_test_plan = models.CharField(max_length=255, blank=True)
274+ lava_submit_fatal = models.BooleanField(default=True, blank=True)
275+ lava_android_binaries = models.BooleanField(default=True, blank=True)
276+
277+ def save(self, *args, **kwargs):
278+ self.server = JenkinsServer.objects.get(id=1)
279+ super(AndroidLoop, self).save(*args, **kwargs)
280+ # We do not want a failure in remote server to affect the
281+ # CI Loop flow.
282+ try:
283+ self.server.create_or_update_integration_loop(self)
284+ except:
285+ # TODO: Log error.
286+ pass
287+
288+ def schedule_build(self):
289+ # Every time before the build is scheduled, update the
290+ # loop config on the remote server.
291+ self.server.create_or_update_integration_loop(self)
292+ super(AndroidLoop, self).schedule_build()
293
294=== added directory 'dashboard/frontend/android_build/templates'
295=== added file 'dashboard/frontend/android_build/templates/__init__.py'
296=== added directory 'dashboard/frontend/android_build/tests'
297=== added file 'dashboard/frontend/android_build/tests/__init__.py'
298=== added file 'dashboard/frontend/android_build/urls.py'
299--- dashboard/frontend/android_build/urls.py 1970-01-01 00:00:00 +0000
300+++ dashboard/frontend/android_build/urls.py 2012-08-17 07:43:18 +0000
301@@ -0,0 +1,34 @@
302+# Copyright (C) 2012 Linaro
303+#
304+# This file is part of linaro-ci-dashboard.
305+#
306+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
307+# it under the terms of the GNU Affero General Public License as published by
308+# the Free Software Foundation, either version 3 of the License, or
309+# (at your option) any later version.
310+#
311+# linaro-ci-dashboard is distributed in the hope that it will be useful,
312+# but WITHOUT ANY WARRANTY; without even the implied warranty of
313+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
314+# GNU Affero General Public License for more details.
315+#
316+# You should have received a copy of the GNU Affero General Public License
317+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
318+
319+from django.conf.urls.defaults import patterns, include, url
320+from frontend.integration_loop.views.integration_loop_create_view \
321+ import IntegrationLoopCreateView
322+from frontend.integration_loop.views.integration_loop_update_view \
323+ import IntegrationLoopUpdateView
324+from frontend.integration_loop.views.integration_loop_detail_view \
325+ import IntegrationLoopDetailView
326+
327+
328+urlpatterns = patterns('',
329+ url(r'^android/create/$',
330+ IntegrationLoopCreateView.as_view(), name='AndroidLoopCreate'),
331+ url(r'^android/detail/(?P<slug>[\w|\W]+)/$',
332+ IntegrationLoopDetailView.as_view(), name='AndroidLoopDetail'),
333+ url(r'^android/update/(?P<slug>[\w|\W]+)/$',
334+ IntegrationLoopUpdateView.as_view(), name='AndroidLoopUpdate'),
335+)
336
337=== added directory 'dashboard/frontend/android_build/views'
338=== added file 'dashboard/frontend/android_build/views/__init__.py'
339=== added directory 'dashboard/frontend/integration_loop'
340=== added file 'dashboard/frontend/integration_loop/__init__.py'
341=== added directory 'dashboard/frontend/integration_loop/forms'
342=== added file 'dashboard/frontend/integration_loop/forms/__init__.py'
343=== renamed file 'dashboard/frontend/forms/integration_loop_form.py' => 'dashboard/frontend/integration_loop/forms/integration_loop_form.py'
344--- dashboard/frontend/forms/integration_loop_form.py 2012-07-24 15:15:35 +0000
345+++ dashboard/frontend/integration_loop/forms/integration_loop_form.py 2012-08-17 07:43:18 +0000
346@@ -17,7 +17,7 @@
347 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
348
349 from django.forms import ModelForm
350-from frontend.models.integration_loop import IntegrationLoop
351+from frontend.integration_loop.models.integration_loop import IntegrationLoop
352
353
354 class IntegrationLoopForm(ModelForm):
355
356=== added directory 'dashboard/frontend/integration_loop/migrations'
357=== added file 'dashboard/frontend/integration_loop/migrations/0001_initial.py'
358--- dashboard/frontend/integration_loop/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
359+++ dashboard/frontend/integration_loop/migrations/0001_initial.py 2012-08-17 07:43:18 +0000
360@@ -0,0 +1,50 @@
361+# encoding: utf-8
362+import datetime
363+from south.db import db
364+from south.v2 import SchemaMigration
365+from django.db import models
366+
367+class Migration(SchemaMigration):
368+
369+ def forwards(self, orm):
370+
371+ # Adding model 'IntegrationLoop'
372+ db.create_table('integration_loop_integrationloop', (
373+ ('loop_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['frontend.Loop'], unique=True, primary_key=True)),
374+ ('branch', self.gf('django.db.models.fields.CharField')(max_length=200)),
375+ ('precommand', self.gf('django.db.models.fields.CharField')(max_length=200)),
376+ ('command', self.gf('django.db.models.fields.CharField')(max_length=200)),
377+ ))
378+ db.send_create_signal('integration_loop', ['IntegrationLoop'])
379+
380+
381+ def backwards(self, orm):
382+
383+ # Deleting model 'IntegrationLoop'
384+ db.delete_table('integration_loop_integrationloop')
385+
386+
387+ models = {
388+ 'frontend.loop': {
389+ 'Meta': {'object_name': 'Loop'},
390+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
391+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
392+ 'server': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['jenkinsserver.JenkinsServer']"})
393+ },
394+ 'integration_loop.integrationloop': {
395+ 'Meta': {'object_name': 'IntegrationLoop', '_ormbases': ['frontend.Loop']},
396+ 'branch': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
397+ 'command': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
398+ 'loop_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['frontend.Loop']", 'unique': 'True', 'primary_key': 'True'}),
399+ 'precommand': ('django.db.models.fields.CharField', [], {'max_length': '200'})
400+ },
401+ 'jenkinsserver.jenkinsserver': {
402+ 'Meta': {'object_name': 'JenkinsServer'},
403+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
404+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
405+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
406+ 'username': ('django.db.models.fields.CharField', [], {'max_length': '100'})
407+ }
408+ }
409+
410+ complete_apps = ['integration_loop']
411
412=== added file 'dashboard/frontend/integration_loop/migrations/__init__.py'
413=== added directory 'dashboard/frontend/integration_loop/models'
414=== added file 'dashboard/frontend/integration_loop/models/__init__.py'
415--- dashboard/frontend/integration_loop/models/__init__.py 1970-01-01 00:00:00 +0000
416+++ dashboard/frontend/integration_loop/models/__init__.py 2012-08-17 07:43:18 +0000
417@@ -0,0 +1,1 @@
418+from frontend.integration_loop.models.integration_loop import IntegrationLoop
419\ No newline at end of file
420
421=== renamed file 'dashboard/frontend/models/integration_loop.py' => 'dashboard/frontend/integration_loop/models/integration_loop.py'
422--- dashboard/frontend/models/integration_loop.py 2012-07-26 13:25:06 +0000
423+++ dashboard/frontend/integration_loop/models/integration_loop.py 2012-08-17 07:43:18 +0000
424@@ -16,7 +16,6 @@
425 # You should have received a copy of the GNU Affero General Public License
426 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
427
428-from django.contrib.auth.models import User
429 from django.db import models
430 from frontend.models.loop import Loop
431 from jenkinsserver.models.jenkins_server import JenkinsServer
432@@ -24,7 +23,7 @@
433
434 class IntegrationLoop(Loop):
435 class Meta:
436- app_label = 'frontend'
437+ app_label = 'integration_loop'
438
439 branch = models.CharField(max_length=200)
440 precommand = models.CharField(max_length=200)
441
442=== added directory 'dashboard/frontend/integration_loop/templates'
443=== added file 'dashboard/frontend/integration_loop/templates/__init__.py'
444=== renamed file 'dashboard/frontend/templates/integration_loop_create.html' => 'dashboard/frontend/integration_loop/templates/integration_loop_create.html'
445=== renamed file 'dashboard/frontend/templates/integration_loop_detail.html' => 'dashboard/frontend/integration_loop/templates/integration_loop_detail.html'
446=== renamed file 'dashboard/frontend/templates/integration_loop_update.html' => 'dashboard/frontend/integration_loop/templates/integration_loop_update.html'
447=== added file 'dashboard/frontend/integration_loop/urls.py'
448--- dashboard/frontend/integration_loop/urls.py 1970-01-01 00:00:00 +0000
449+++ dashboard/frontend/integration_loop/urls.py 2012-08-17 07:43:18 +0000
450@@ -0,0 +1,34 @@
451+# Copyright (C) 2012 Linaro
452+#
453+# This file is part of linaro-ci-dashboard.
454+#
455+# linaro-ci-dashboard is free software: you can redistribute it and/or modify
456+# it under the terms of the GNU Affero General Public License as published by
457+# the Free Software Foundation, either version 3 of the License, or
458+# (at your option) any later version.
459+#
460+# linaro-ci-dashboard is distributed in the hope that it will be useful,
461+# but WITHOUT ANY WARRANTY; without even the implied warranty of
462+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
463+# GNU Affero General Public License for more details.
464+#
465+# You should have received a copy of the GNU Affero General Public License
466+# along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
467+
468+from django.conf.urls.defaults import patterns, include, url
469+from frontend.integration_loop.views.integration_loop_create_view\
470+ import IntegrationLoopCreateView
471+from frontend.integration_loop.views.integration_loop_update_view\
472+ import IntegrationLoopUpdateView
473+from frontend.integration_loop.views.integration_loop_detail_view\
474+ import IntegrationLoopDetailView
475+
476+
477+urlpatterns = patterns('',
478+ url(r'^integration_loop/create/$',
479+ IntegrationLoopCreateView.as_view(), name='IntegrationLoopCreate'),
480+ url(r'^integration_loop/detail/(?P<slug>[\w|\W]+)/$',
481+ IntegrationLoopDetailView.as_view(), name='IntegrationLoopDetail'),
482+ url(r'^integration_loop/update/(?P<slug>[\w|\W]+)/$',
483+ IntegrationLoopUpdateView.as_view(), name='IntegrationLoopUpdate'),
484+)
485
486=== added directory 'dashboard/frontend/integration_loop/views'
487=== added file 'dashboard/frontend/integration_loop/views/__init__.py'
488=== renamed file 'dashboard/frontend/views/integration_loop_create_view.py' => 'dashboard/frontend/integration_loop/views/integration_loop_create_view.py'
489--- dashboard/frontend/views/integration_loop_create_view.py 2012-07-26 13:25:06 +0000
490+++ dashboard/frontend/integration_loop/views/integration_loop_create_view.py 2012-08-17 07:43:18 +0000
491@@ -18,11 +18,11 @@
492
493 from django.contrib.auth.decorators import login_required
494 from django.core.urlresolvers import reverse
495-from django.http import HttpResponseRedirect
496 from django.views.generic.edit import CreateView
497 from django.utils.decorators import method_decorator
498-from frontend.forms.integration_loop_form import IntegrationLoopForm
499-from frontend.models.integration_loop import IntegrationLoop
500+from frontend.integration_loop.forms.integration_loop_form \
501+ import IntegrationLoopForm
502+from frontend.integration_loop.models.integration_loop import IntegrationLoop
503
504
505 class IntegrationLoopCreateView(CreateView):
506
507=== renamed file 'dashboard/frontend/views/integration_loop_detail_view.py' => 'dashboard/frontend/integration_loop/views/integration_loop_detail_view.py'
508--- dashboard/frontend/views/integration_loop_detail_view.py 2012-07-24 15:15:35 +0000
509+++ dashboard/frontend/integration_loop/views/integration_loop_detail_view.py 2012-08-17 07:43:18 +0000
510@@ -19,7 +19,7 @@
511 from django.contrib.auth.decorators import login_required
512 from django.views.generic.detail import DetailView
513 from django.utils.decorators import method_decorator
514-from frontend.models.integration_loop import IntegrationLoop
515+from frontend.integration_loop.models.integration_loop import IntegrationLoop
516
517
518 class IntegrationLoopDetailView(DetailView):
519
520=== renamed file 'dashboard/frontend/views/integration_loop_update_view.py' => 'dashboard/frontend/integration_loop/views/integration_loop_update_view.py'
521--- dashboard/frontend/views/integration_loop_update_view.py 2012-07-26 13:25:06 +0000
522+++ dashboard/frontend/integration_loop/views/integration_loop_update_view.py 2012-08-17 07:43:18 +0000
523@@ -18,11 +18,11 @@
524
525 from django.contrib.auth.decorators import login_required
526 from django.core.urlresolvers import reverse
527-from django.http import HttpResponseRedirect
528 from django.views.generic.edit import UpdateView
529 from django.utils.decorators import method_decorator
530-from frontend.forms.integration_loop_form import IntegrationLoopForm
531-from frontend.models.integration_loop import IntegrationLoop
532+from frontend.integration_loop.forms.integration_loop_form \
533+ import IntegrationLoopForm
534+from frontend.integration_loop.models.integration_loop import IntegrationLoop
535
536
537 class IntegrationLoopUpdateView(UpdateView):
538
539=== modified file 'dashboard/frontend/migrations/0001_initial.py'
540--- dashboard/frontend/migrations/0001_initial.py 2012-07-25 12:20:01 +0000
541+++ dashboard/frontend/migrations/0001_initial.py 2012-08-17 07:43:18 +0000
542@@ -16,15 +16,6 @@
543 ))
544 db.send_create_signal('frontend', ['Loop'])
545
546- # Adding model 'IntegrationLoop'
547- db.create_table('frontend_integrationloop', (
548- ('loop_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['frontend.Loop'], unique=True, primary_key=True)),
549- ('branch', self.gf('django.db.models.fields.CharField')(max_length=200)),
550- ('precommand', self.gf('django.db.models.fields.CharField')(max_length=200)),
551- ('command', self.gf('django.db.models.fields.CharField')(max_length=200)),
552- ))
553- db.send_create_signal('frontend', ['IntegrationLoop'])
554-
555 # Adding model 'LoopBuild'
556 db.create_table('frontend_loopbuild', (
557 ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
558@@ -42,21 +33,11 @@
559 # Deleting model 'Loop'
560 db.delete_table('frontend_loop')
561
562- # Deleting model 'IntegrationLoop'
563- db.delete_table('frontend_integrationloop')
564-
565 # Deleting model 'LoopBuild'
566 db.delete_table('frontend_loopbuild')
567
568
569 models = {
570- 'frontend.integrationloop': {
571- 'Meta': {'object_name': 'IntegrationLoop', '_ormbases': ['frontend.Loop']},
572- 'branch': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
573- 'command': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
574- 'loop_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['frontend.Loop']", 'unique': 'True', 'primary_key': 'True'}),
575- 'precommand': ('django.db.models.fields.CharField', [], {'max_length': '200'})
576- },
577 'frontend.loop': {
578 'Meta': {'object_name': 'Loop'},
579 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
580
581=== modified file 'dashboard/frontend/models/__init__.py'
582--- dashboard/frontend/models/__init__.py 2012-07-24 15:15:35 +0000
583+++ dashboard/frontend/models/__init__.py 2012-08-17 07:43:18 +0000
584@@ -1,3 +1,2 @@
585 from frontend.models.loop import Loop
586-from frontend.models.integration_loop import IntegrationLoop
587 from frontend.models.loop_build import LoopBuild
588
589=== modified file 'dashboard/frontend/models/loop.py'
590--- dashboard/frontend/models/loop.py 2012-07-26 13:25:06 +0000
591+++ dashboard/frontend/models/loop.py 2012-08-17 07:43:18 +0000
592@@ -16,7 +16,6 @@
593 # You should have received a copy of the GNU Affero General Public License
594 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
595
596-from django.contrib.auth.models import User
597 from django.db import models
598 from jenkinsserver.models.jenkins_server import JenkinsServer
599
600
601=== modified file 'dashboard/frontend/tests/test_views.py'
602--- dashboard/frontend/tests/test_views.py 2012-07-28 02:27:35 +0000
603+++ dashboard/frontend/tests/test_views.py 2012-08-17 07:43:18 +0000
604@@ -20,12 +20,12 @@
605
606 from frontend.views.home_page_view import HomePageView
607 from frontend.views.index_view import IndexView
608-from frontend.views.integration_loop_create_view \
609- import IntegrationLoopCreateView
610-from frontend.views.integration_loop_update_view \
611- import IntegrationLoopUpdateView
612-from frontend.views.integration_loop_detail_view \
613- import IntegrationLoopDetailView
614+from frontend.integration_loop.views.integration_loop_create_view \
615+ import IntegrationLoopCreateView
616+from frontend.integration_loop.views.integration_loop_update_view \
617+ import IntegrationLoopUpdateView
618+from frontend.integration_loop.views.integration_loop_detail_view \
619+ import IntegrationLoopDetailView
620
621
622 class HomePageViewTest(TestCase):
623
624=== modified file 'dashboard/frontend/urls.py'
625--- dashboard/frontend/urls.py 2012-07-24 15:29:46 +0000
626+++ dashboard/frontend/urls.py 2012-08-17 07:43:18 +0000
627@@ -19,24 +19,13 @@
628 from django.conf.urls.defaults import patterns, include, url
629 from frontend.views.home_page_view import HomePageView
630 from frontend.views.index_view import IndexView
631-from frontend.views.integration_loop_create_view \
632- import IntegrationLoopCreateView
633-from frontend.views.integration_loop_update_view \
634- import IntegrationLoopUpdateView
635-from frontend.views.integration_loop_detail_view \
636- import IntegrationLoopDetailView
637 from frontend.views.loop_build_view import LoopBuildView
638
639+
640 urlpatterns = patterns('',
641 url(r'^$', IndexView.as_view(), name='Index'),
642 url(r'^index/$', IndexView.as_view(), name='Index'),
643 url(r'^home/$', HomePageView.as_view(), name='Home'),
644- url(r'^integration_loop/create/$',
645- IntegrationLoopCreateView.as_view(), name='IntegrationLoopCreate'),
646- url(r'^integration_loop/detail/(?P<slug>[\w|\W]+)/$',
647- IntegrationLoopDetailView.as_view(), name='IntegrationLoopDetail'),
648- url(r'^integration_loop/update/(?P<slug>[\w|\W]+)/$',
649- IntegrationLoopUpdateView.as_view(), name='IntegrationLoopUpdate'),
650 url(r'^loop/build/(?P<slug>[\w|\W]+)/$',
651 LoopBuildView.as_view(), name='LoopBuild'),
652 )
653
654=== modified file 'dashboard/frontend/views/index_view.py'
655--- dashboard/frontend/views/index_view.py 2012-07-24 15:15:35 +0000
656+++ dashboard/frontend/views/index_view.py 2012-08-17 07:43:18 +0000
657@@ -19,7 +19,7 @@
658 from django.views.generic.base import TemplateView
659 from django.utils.decorators import method_decorator
660 from django.contrib.auth.decorators import login_required
661-from frontend.models.integration_loop import IntegrationLoop
662+from frontend.integration_loop.models.integration_loop import IntegrationLoop
663
664
665 class IndexView(TemplateView):
666
667=== modified file 'dashboard/frontend/views/loop_build_view.py'
668--- dashboard/frontend/views/loop_build_view.py 2012-07-20 11:00:36 +0000
669+++ dashboard/frontend/views/loop_build_view.py 2012-08-17 07:43:18 +0000
670@@ -17,8 +17,6 @@
671 # along with linaro-ci-dashboard. If not, see <http://www.gnu.org/licenses/>.
672
673 from django.contrib.auth.decorators import login_required
674-from django.core import serializers
675-from django.views.generic import View
676 from django.views.generic.detail import DetailView
677 from django.utils.decorators import method_decorator
678 from frontend.models.loop import Loop
679
680=== modified file 'dashboard/settings.py'
681--- dashboard/settings.py 2012-07-24 21:28:00 +0000
682+++ dashboard/settings.py 2012-08-17 07:43:18 +0000
683@@ -44,6 +44,7 @@
684 }
685
686 SOUTH_TESTS_MIGRATE = False
687+SKIP_SOUTH_TESTS = True
688
689 # Local time zone for this installation. Choices can be found here:
690 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
691@@ -139,6 +140,8 @@
692 'django_openid_auth',
693 'jenkinsserver',
694 'frontend',
695+ 'frontend.android_build',
696+ 'frontend.integration_loop',
697 'south',
698 # Uncomment the next line to enable the admin:
699 # 'django.contrib.admin',
700
701=== modified file 'dashboard/urls.py'
702--- dashboard/urls.py 2012-07-19 19:58:21 +0000
703+++ dashboard/urls.py 2012-08-17 07:43:18 +0000
704@@ -30,4 +30,6 @@
705 url(r'^js/(?P<path>.*)$', 'django.views.static.serve',
706 {'document_root': settings.JS_PATH}),
707 url(r'^', include('dashboard.frontend.urls')),
708+ url(r'^', include('dashboard.frontend.android_build.urls')),
709+ url(r'^', include('dashboard.frontend.integration_loop.urls')),
710 )

Subscribers

People subscribed via source and target branches