summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2015-02-16 17:47:07 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-02-20 12:58:19 +0000
commitc368d83bd6b34c2420c3d1d7269d8dc2edba1ce9 (patch)
treed6a905444fe2ea0f0313bc4b848430108ac17388
parenta574f293fe16612df446d3b7fef71adcab4773e9 (diff)
downloadpoky-c368d83bd6b34c2420c3d1d7269d8dc2edba1ce9.tar.gz
bitbake: toaster: bitbake cooker log saving and downloading
This patch brings in cooker log saving and proper download links. * toasterui will now write the cooker log file if running in managed mode * the BuildRequest has a new state, REQ_ARCHIVE, indicating that the build is completed, and the artifacts are ready to be grabbed * the runbuild test execution commands will gather needed artifacts, and save them to a storage directory selected during Toaster setup. * the build dashboard, project builds and all builds pages have permanent links for the cooker log [YOCTO #7220] [YOCTO #7206] (Bitbake rev: fad80e36c9da663b000cdf2cb3c75440c6431d84) Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/bb/ui/buildinfohelper.py3
-rw-r--r--bitbake/lib/bb/ui/toasterui.py29
-rw-r--r--bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py17
-rw-r--r--bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py29
-rw-r--r--bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py138
-rw-r--r--bitbake/lib/toaster/bldcontrol/models.py2
-rw-r--r--bitbake/lib/toaster/toastergui/templates/build.html9
-rw-r--r--bitbake/lib/toaster/toastergui/templates/builddashboard.html11
-rw-r--r--bitbake/lib/toaster/toastergui/templates/managed_builds.html9
-rw-r--r--bitbake/lib/toaster/toastergui/templates/projectbuilds.html14
-rw-r--r--bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html17
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py94
-rw-r--r--bitbake/lib/toaster/toastermain/urls.py6
13 files changed, 335 insertions, 43 deletions
diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 491fd1566d..1096ccf4de 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -1114,7 +1114,8 @@ class BuildInfoHelper(object):
1114 be.save() 1114 be.save()
1115 br = BuildRequest.objects.get(pk = br_id) 1115 br = BuildRequest.objects.get(pk = br_id)
1116 if errorcode == 0: 1116 if errorcode == 0:
1117 br.state = BuildRequest.REQ_COMPLETED 1117 # request archival of the project artifacts
1118 br.state = BuildRequest.REQ_ARCHIVE
1118 else: 1119 else:
1119 br.state = BuildRequest.REQ_FAILED 1120 br.state = BuildRequest.REQ_FAILED
1120 br.save() 1121 br.save()
diff --git a/bitbake/lib/bb/ui/toasterui.py b/bitbake/lib/bb/ui/toasterui.py
index a85ad5a06a..df9f362284 100644
--- a/bitbake/lib/bb/ui/toasterui.py
+++ b/bitbake/lib/bb/ui/toasterui.py
@@ -58,7 +58,12 @@ def _log_settings_from_server(server):
58 if error: 58 if error:
59 logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error) 59 logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
60 raise BaseException(error) 60 raise BaseException(error)
61 return includelogs, loglines 61 consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
62 if error:
63 logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
64 raise BaseException(error)
65 return includelogs, loglines, consolelogfile
66
62 67
63def main(server, eventHandler, params ): 68def main(server, eventHandler, params ):
64 69
@@ -71,7 +76,7 @@ def main(server, eventHandler, params ):
71 console.setFormatter(format) 76 console.setFormatter(format)
72 logger.addHandler(console) 77 logger.addHandler(console)
73 78
74 includelogs, loglines = _log_settings_from_server(server) 79 includelogs, loglines, consolelogfile = _log_settings_from_server(server)
75 80
76 # verify and warn 81 # verify and warn
77 build_history_enabled = True 82 build_history_enabled = True
@@ -96,6 +101,16 @@ def main(server, eventHandler, params ):
96 101
97 buildinfohelper = BuildInfoHelper(server, build_history_enabled) 102 buildinfohelper = BuildInfoHelper(server, build_history_enabled)
98 103
104 if buildinfohelper.brbe is not None and consolelogfile:
105 # if we are under managed mode we have no other UI and we need to write our own file
106 bb.utils.mkdirhier(os.path.dirname(consolelogfile))
107 conlogformat = bb.msg.BBLogFormatter(format_str)
108 consolelog = logging.FileHandler(consolelogfile)
109 bb.msg.addDefaultlogFilter(consolelog)
110 consolelog.setFormatter(conlogformat)
111 logger.addHandler(consolelog)
112
113
99 while True: 114 while True:
100 try: 115 try:
101 event = eventHandler.waitEvent(0.25) 116 event = eventHandler.waitEvent(0.25)
@@ -115,8 +130,12 @@ def main(server, eventHandler, params ):
115 130
116 if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)): 131 if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
117 buildinfohelper.update_and_store_task(event) 132 buildinfohelper.update_and_store_task(event)
133 logger.warn("Logfile for task %s" % event.logfile)
118 continue 134 continue
119 135
136 if isinstance(event, bb.build.TaskBase):
137 logger.info(event._message)
138
120 if isinstance(event, bb.event.LogExecTTY): 139 if isinstance(event, bb.event.LogExecTTY):
121 logger.warn(event.msg) 140 logger.warn(event.msg)
122 continue 141 continue
@@ -162,7 +181,12 @@ def main(server, eventHandler, params ):
162 if isinstance(event, bb.event.CacheLoadCompleted): 181 if isinstance(event, bb.event.CacheLoadCompleted):
163 continue 182 continue
164 if isinstance(event, bb.event.MultipleProviders): 183 if isinstance(event, bb.event.MultipleProviders):
184 logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
185 event._item,
186 ", ".join(event._candidates))
187 logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
165 continue 188 continue
189
166 if isinstance(event, bb.event.NoProvider): 190 if isinstance(event, bb.event.NoProvider):
167 return_value = 1 191 return_value = 1
168 errors = errors + 1 192 errors = errors + 1
@@ -229,6 +253,7 @@ def main(server, eventHandler, params ):
229 buildinfohelper.store_log_event(event) 253 buildinfohelper.store_log_event(event)
230 errors += 1 254 errors += 1
231 errorcode = 1 255 errorcode = 1
256 logger.error("Command execution failed: %s", event.error)
232 257
233 buildinfohelper.update_build_information(event, errors, warnings, taskfailures) 258 buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
234 buildinfohelper.close(errorcode) 259 buildinfohelper.close(errorcode)
diff --git a/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py b/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
index 5d80bc7155..1ff5c92833 100644
--- a/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
+++ b/bitbake/lib/toaster/bldcontrol/management/commands/checksettings.py
@@ -62,6 +62,23 @@ class Command(NoArgsCommand):
62 62
63 63
64 def handle(self, **options): 64 def handle(self, **options):
65 # verify that we have a settings for downloading artifacts
66 while ToasterSetting.objects.filter(name="ARTIFACTS_STORAGE_DIR").count() == 0:
67 guessedpath = os.getcwd() + "/toaster_build_artifacts/"
68 print("Toaster needs to know in which directory it can download build log files and other artifacts.\n Toaster suggests \"%s\"." % guessedpath)
69 artifacts_storage_dir = raw_input(" Press Enter to select \"%s\" or type the full path to a different directory: " % guessedpath)
70 if len(artifacts_storage_dir) == 0:
71 artifacts_storage_dir = guessedpath
72 if len(artifacts_storage_dir) > 0 and artifacts_storage_dir.startswith("/"):
73 try:
74 os.makedirs(artifacts_storage_dir)
75 except OSError as ose:
76 if "File exists" in str(ose):
77 pass
78 else:
79 raise ose
80 ToasterSetting.objects.create(name="ARTIFACTS_STORAGE_DIR", value=artifacts_storage_dir)
81
65 self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) 82 self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__)))))))
66 # refuse to start if we have no build environments 83 # refuse to start if we have no build environments
67 while BuildEnvironment.objects.count() == 0: 84 while BuildEnvironment.objects.count() == 0:
diff --git a/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py b/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
index 3de582cc86..808318f14f 100644
--- a/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
+++ b/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
@@ -1,6 +1,6 @@
1from django.core.management.base import NoArgsCommand, CommandError 1from django.core.management.base import NoArgsCommand, CommandError
2from django.db import transaction 2from django.db import transaction
3from orm.models import Build 3from orm.models import Build, ToasterSetting
4from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException, BuildSetupException 4from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException, BuildSetupException
5from bldcontrol.models import BuildRequest, BuildEnvironment, BRError, BRVariable 5from bldcontrol.models import BuildRequest, BuildEnvironment, BRError, BRVariable
6import os 6import os
@@ -93,7 +93,33 @@ class Command(NoArgsCommand):
93 bec.be.lock = BuildEnvironment.LOCK_FREE 93 bec.be.lock = BuildEnvironment.LOCK_FREE
94 bec.be.save() 94 bec.be.save()
95 95
96 def archive(self):
97 ''' archives data from the builds '''
98 artifact_storage_dir = ToasterSetting.objects.get(name="ARTIFACTS_STORAGE_DIR").value
99 for br in BuildRequest.objects.filter(state = BuildRequest.REQ_ARCHIVE):
100 # save cooker log
101 if br.build == None:
102 br.state = BuildRequest.REQ_FAILED
103 br.save()
104 continue
105 build_artifact_storage_dir = os.path.join(artifact_storage_dir, "%d" % br.build.pk)
106 try:
107 os.makedirs(build_artifact_storage_dir)
108 except OSError as ose:
109 if "File exists" in str(ose):
110 pass
111 else:
112 raise ose
113
114 file_name = os.path.join(build_artifact_storage_dir, "cooker_log.txt")
115 try:
116 with open(file_name, "w") as f:
117 f.write(br.environment.get_artifact(br.build.cooker_log_path).read())
118 except IOError:
119 os.unlink(file_name)
96 120
121 br.state = BuildRequest.REQ_COMPLETED
122 br.save()
97 123
98 def cleanup(self): 124 def cleanup(self):
99 from django.utils import timezone 125 from django.utils import timezone
@@ -104,4 +130,5 @@ class Command(NoArgsCommand):
104 130
105 def handle_noargs(self, **options): 131 def handle_noargs(self, **options):
106 self.cleanup() 132 self.cleanup()
133 self.archive()
107 self.schedule() 134 self.schedule()
diff --git a/bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py b/bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py
new file mode 100644
index 0000000000..f5469607f3
--- /dev/null
+++ b/bitbake/lib/toaster/bldcontrol/migrations/0008_brarchive.py
@@ -0,0 +1,138 @@
1# -*- coding: utf-8 -*-
2from south.utils import datetime_utils as datetime
3from south.db import db
4from south.v2 import DataMigration
5from django.db import models
6
7class Migration(DataMigration):
8 # ids that cannot be imported from BuildRequest
9
10 def forwards(self, orm):
11 REQ_COMPLETED = 3
12 REQ_ARCHIVE = 6
13 "Write your forwards methods here."
14 # Note: Don't use "from appname.models import ModelName".
15 # Use orm.ModelName to refer to models in this application,
16 # and orm['appname.ModelName'] for models in other applications.
17 orm.BuildRequest.objects.filter(state=REQ_COMPLETED).update(state=REQ_ARCHIVE)
18
19 def backwards(self, orm):
20 REQ_COMPLETED = 3
21 REQ_ARCHIVE = 6
22 "Write your backwards methods here."
23 orm.BuildRequest.objects.filter(state=REQ_ARCHIVE).update(state=REQ_COMPLETED)
24
25 models = {
26 u'bldcontrol.brbitbake': {
27 'Meta': {'object_name': 'BRBitbake'},
28 'commit': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
29 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
30 'giturl': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
31 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
32 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']", 'unique': 'True'})
33 },
34 u'bldcontrol.brerror': {
35 'Meta': {'object_name': 'BRError'},
36 'errmsg': ('django.db.models.fields.TextField', [], {}),
37 'errtype': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
38 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
39 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"}),
40 'traceback': ('django.db.models.fields.TextField', [], {})
41 },
42 u'bldcontrol.brlayer': {
43 'Meta': {'object_name': 'BRLayer'},
44 'commit': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
45 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
46 'giturl': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
47 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
48 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
49 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"})
50 },
51 u'bldcontrol.brtarget': {
52 'Meta': {'object_name': 'BRTarget'},
53 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
54 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"}),
55 'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
56 'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
57 },
58 u'bldcontrol.brvariable': {
59 'Meta': {'object_name': 'BRVariable'},
60 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
62 'req': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildRequest']"}),
63 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
64 },
65 u'bldcontrol.buildenvironment': {
66 'Meta': {'object_name': 'BuildEnvironment'},
67 'address': ('django.db.models.fields.CharField', [], {'max_length': '254'}),
68 'bbaddress': ('django.db.models.fields.CharField', [], {'max_length': '254', 'blank': 'True'}),
69 'bbport': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
70 'bbstate': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
71 'bbtoken': ('django.db.models.fields.CharField', [], {'max_length': '126', 'blank': 'True'}),
72 'betype': ('django.db.models.fields.IntegerField', [], {}),
73 'builddir': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
74 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
75 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76 'lock': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
77 'sourcedir': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
78 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
79 },
80 u'bldcontrol.buildrequest': {
81 'Meta': {'object_name': 'BuildRequest'},
82 'build': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Build']", 'unique': 'True', 'null': 'True'}),
83 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
84 'environment': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['bldcontrol.BuildEnvironment']", 'null': 'True'}),
85 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
87 'state': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
88 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
89 },
90 u'orm.bitbakeversion': {
91 'Meta': {'object_name': 'BitbakeVersion'},
92 'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
93 'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
94 'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
95 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
97 },
98 u'orm.build': {
99 'Meta': {'object_name': 'Build'},
100 'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
101 'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
102 'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
103 'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
104 'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
105 'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
106 'errors_no': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
107 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108 'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
109 'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
110 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']", 'null': 'True'}),
111 'started_on': ('django.db.models.fields.DateTimeField', [], {}),
112 'timespent': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
113 'warnings_no': ('django.db.models.fields.IntegerField', [], {'default': '0'})
114 },
115 u'orm.project': {
116 'Meta': {'object_name': 'Project'},
117 'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
118 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
119 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
121 'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"}),
122 'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
123 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
124 'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
125 },
126 u'orm.release': {
127 'Meta': {'object_name': 'Release'},
128 'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
129 'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
130 'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
131 'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
132 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
133 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
134 }
135 }
136
137 complete_apps = ['bldcontrol']
138 symmetrical = True
diff --git a/bitbake/lib/toaster/bldcontrol/models.py b/bitbake/lib/toaster/bldcontrol/models.py
index dc4afca2f7..25d94cd3fe 100644
--- a/bitbake/lib/toaster/bldcontrol/models.py
+++ b/bitbake/lib/toaster/bldcontrol/models.py
@@ -94,6 +94,7 @@ class BuildRequest(models.Model):
94 REQ_COMPLETED = 3 94 REQ_COMPLETED = 3
95 REQ_FAILED = 4 95 REQ_FAILED = 4
96 REQ_DELETED = 5 96 REQ_DELETED = 5
97 REQ_ARCHIVE = 6
97 98
98 REQUEST_STATE = ( 99 REQUEST_STATE = (
99 (REQ_CREATED, "created"), 100 (REQ_CREATED, "created"),
@@ -102,6 +103,7 @@ class BuildRequest(models.Model):
102 (REQ_COMPLETED, "completed"), 103 (REQ_COMPLETED, "completed"),
103 (REQ_FAILED, "failed"), 104 (REQ_FAILED, "failed"),
104 (REQ_DELETED, "deleted"), 105 (REQ_DELETED, "deleted"),
106 (REQ_ARCHIVE, "archive"),
105 ) 107 )
106 108
107 search_allowed_fields = ("brtarget__target",) 109 search_allowed_fields = ("brtarget__target",)
diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html
index e71e38feb9..684ec65884 100644
--- a/bitbake/lib/toaster/toastergui/templates/build.html
+++ b/bitbake/lib/toaster/toastergui/templates/build.html
@@ -40,7 +40,9 @@
40 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> 40 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
41 {% for build in objects %} 41 {% for build in objects %}
42 <tr class="data"> 42 <tr class="data">
43 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> 43 <td class="outcome">
44 <a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a> &nbsp;
45 </td>
44 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 46 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
45 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 47 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
46 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 48 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
@@ -61,11 +63,6 @@
61 <td class="errors_no"> 63 <td class="errors_no">
62 {% if build.errors_no %} 64 {% if build.errors_no %}
63 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> 65 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
64 {% if MANAGED and build.project %}
65 <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
66 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
67 </a>
68 {% endif %}
69 {%endif%} 66 {%endif%}
70 </td> 67 </td>
71 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td> 68 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
diff --git a/bitbake/lib/toaster/toastergui/templates/builddashboard.html b/bitbake/lib/toaster/toastergui/templates/builddashboard.html
index 2458cdb6d1..c0898e291d 100644
--- a/bitbake/lib/toaster/toastergui/templates/builddashboard.html
+++ b/bitbake/lib/toaster/toastergui/templates/builddashboard.html
@@ -36,7 +36,11 @@
36{% endif %} 36{% endif %}
37 <span > <i class="icon-warning-sign yellow"></i><strong><a href="#warnings" class="warning show-warnings"> {{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a></strong></span> 37 <span > <i class="icon-warning-sign yellow"></i><strong><a href="#warnings" class="warning show-warnings"> {{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a></strong></span>
38{% endif %} 38{% endif %}
39 <span class="pull-right">Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a></span> 39 <span class="pull-right">Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a> &nbsp;
40 {% if MANAGED and build.project %}
41 <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%else%}btn-danger{%endif%} pull-right" href="{% url 'build_artifact' build.id "cookerlog" build.id %}">Download build log</a>
42 {% endif %}
43 </span>
40{%endif%} 44{%endif%}
41 </div> 45 </div>
42 {% if build.toaster_exceptions.count > 0 %} 46 {% if build.toaster_exceptions.count > 0 %}
@@ -54,10 +58,7 @@
54<div class="accordion span10 pull-right" id="errors"> 58<div class="accordion span10 pull-right" id="errors">
55 <div class="accordion-group"> 59 <div class="accordion-group">
56 <div class="accordion-heading"> 60 <div class="accordion-heading">
57 {% if MANAGED and build.project %} 61 <a class="accordion-toggle error toggle-errors">
58 <a class="btn btn-large pull-right" href="{% url 'build_artifact' build.id "cookerlog" build.id %}" style="margin:15px;">Download build log</a>
59 {% endif %}
60 <a class="accordion-toggle error toggle-errors">
61 <h2 id="error-toggle"> 62 <h2 id="error-toggle">
62 <i class="icon-minus-sign"></i> 63 <i class="icon-minus-sign"></i>
63 {{build.errors_no}} error{{build.errors_no|pluralize}} 64 {{build.errors_no}} error{{build.errors_no|pluralize}}
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
index 121ad07898..a4db55b967 100644
--- a/bitbake/lib/toaster/toastergui/templates/managed_builds.html
+++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
@@ -46,7 +46,14 @@
46 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> 46 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
47 {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #} 47 {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #}
48 <tr class="data"> 48 <tr class="data">
49 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> 49 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a>
50 {% if build.project %}
51 &nbsp; <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
52 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
53 </a>
54 {% endif %}
55
56 </td>
50 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 57 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
51 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 58 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
52 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 59 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
diff --git a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
index 02d166341f..afcf5191b5 100644
--- a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
+++ b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html
@@ -49,7 +49,14 @@
49 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> 49 <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
50 {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} 50 {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #}
51 <tr class="data"> 51 <tr class="data">
52 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td> 52 <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a>
53 {% if build.project %}
54 &nbsp; <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
55 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
56 </a>
57 {% endif %}
58 </td>
59
53 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 60 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
54 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 61 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
55 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 62 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
@@ -70,11 +77,6 @@
70 <td class="errors_no"> 77 <td class="errors_no">
71 {% if build.errors_no %} 78 {% if build.errors_no %}
72 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a> 79 <a class="errors_no error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
73 {% if MANAGED and build.project %}
74 <a href="{% url 'build_artifact' build.id "cookerlog" build.id %}">
75 <i class="icon-download-alt" title="" data-original-title="Download build log"></i>
76 </a>
77 {% endif %}
78 {%endif%} 80 {%endif%}
79 </td> 81 </td>
80 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td> 82 <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
diff --git a/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html b/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html
new file mode 100644
index 0000000000..c93d4257db
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/unavailable_artifact.html
@@ -0,0 +1,17 @@
1{% extends "base.html" %}
2{% load projecttags %}
3{% load humanize %}
4{% load static %}
5
6{% block pagecontent %}
7<div class="section">
8</div>
9<div class="row-fluid">
10
11 <div class="alert alert-info">
12 <p class="lead"> The artifact you are seeking to download is not available. We are sorry. You can <a href="javascript:window.history.back()">go back</a>
13 </p>
14 </div>
15</div>
16{% endblock %}
17
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index b5ff9b1d53..eb323ec81d 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2914,9 +2914,6 @@ if toastermain.settings.MANAGED:
2914 if artifact_type == "imagefile": 2914 if artifact_type == "imagefile":
2915 file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name 2915 file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name
2916 2916
2917 elif artifact_type == "cookerlog":
2918 file_name = b.cooker_log_path
2919
2920 elif artifact_type == "buildartifact": 2917 elif artifact_type == "buildartifact":
2921 file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name 2918 file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name
2922 2919
@@ -2935,26 +2932,87 @@ if toastermain.settings.MANAGED:
2935 2932
2936 2933
2937 def build_artifact(request, build_id, artifact_type, artifact_id): 2934 def build_artifact(request, build_id, artifact_type, artifact_id):
2938 b = Build.objects.get(pk=build_id) 2935 if artifact_type in ["cookerlog"]:
2939 if b.buildrequest is None or b.buildrequest.environment is None: 2936 # these artifacts are saved after building, so they are on the server itself
2940 raise Exception("Artifact not available for download (missing build request or build environment)") 2937 def _mimetype_for_artifact(path):
2938 try:
2939 import magic
2940
2941 # fair warning: this is a mess; there are multiple competing and incompatible
2942 # magic modules floating around, so we try some of the most common combinations
2943
2944 try: # we try ubuntu's python-magic 5.4
2945 m = magic.open(magic.MAGIC_MIME_TYPE)
2946 m.load()
2947 return m.file(path)
2948 except AttributeError:
2949 pass
2950
2951 try: # we try python-magic 0.4.6
2952 m = magic.Magic(magic.MAGIC_MIME)
2953 return m.from_file(path)
2954 except AttributeError:
2955 pass
2956
2957 try: # we try pip filemagic 1.6
2958 m = magic.Magic(flags=magic.MAGIC_MIME_TYPE)
2959 return m.id_filename(path)
2960 except AttributeError:
2961 pass
2962
2963 return "binary/octet-stream"
2964 except ImportError:
2965 return "binary/octet-stream"
2966 try:
2967 # match code with runbuilds.Command.archive()
2968 build_artifact_storage_dir = os.path.join(ToasterSetting.objects.get(name="ARTIFACTS_STORAGE_DIR").value, "%d" % int(build_id))
2969 file_name = os.path.join(build_artifact_storage_dir, "cooker_log.txt")
2970
2971 fsock = open(file_name, "r")
2972 content_type=_mimetype_for_artifact(file_name)
2941 2973
2942 file_name = _file_name_for_artifact(b, artifact_type, artifact_id) 2974 response = HttpResponse(fsock, content_type = content_type)
2943 fsock = None 2975
2944 content_type='application/force-download' 2976 response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_name)
2977 return response
2978 except IOError:
2979 context = {
2980 'build' : Build.objects.get(pk = build_id),
2981 }
2982 return render(request, "unavailable_artifact.html", context)
2945 2983
2946 if file_name is None:
2947 raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
2948 else: 2984 else:
2949 content_type = b.buildrequest.environment.get_artifact_type(file_name) 2985 # retrieve the artifact directly from the build environment
2950 fsock = b.buildrequest.environment.get_artifact(file_name) 2986 return _get_be_artifact(request, build_id, artifact_type, artifact_id)
2951 file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
2952 2987
2953 response = HttpResponse(fsock, content_type = content_type)
2954 2988
2955 # returns a file from the environment 2989 def _get_be_artifact(request, build_id, artifact_type, artifact_id):
2956 response['Content-Disposition'] = 'attachment; filename=' + file_name 2990 try:
2957 return response 2991 b = Build.objects.get(pk=build_id)
2992 if b.buildrequest is None or b.buildrequest.environment is None:
2993 raise Exception("Artifact not available for download (missing build request or build environment)")
2994
2995 file_name = _file_name_for_artifact(b, artifact_type, artifact_id)
2996 fsock = None
2997 content_type='application/force-download'
2998
2999 if file_name is None:
3000 raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
3001 else:
3002 content_type = b.buildrequest.environment.get_artifact_type(file_name)
3003 fsock = b.buildrequest.environment.get_artifact(file_name)
3004 file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
3005
3006 response = HttpResponse(fsock, content_type = content_type)
3007
3008 # returns a file from the environment
3009 response['Content-Disposition'] = 'attachment; filename=' + file_name
3010 return response
3011 except IOError:
3012 context = {
3013 'build' : Build.objects.get(pk = build_id),
3014 }
3015 return render(request, "unavailable_artifact.html", context)
2958 3016
2959 3017
2960 3018
diff --git a/bitbake/lib/toaster/toastermain/urls.py b/bitbake/lib/toaster/toastermain/urls.py
index f66f11dcde..395c4e8c34 100644
--- a/bitbake/lib/toaster/toastermain/urls.py
+++ b/bitbake/lib/toaster/toastermain/urls.py
@@ -50,12 +50,12 @@ import toastermain.settings
50 50
51if toastermain.settings.FRESH_ENABLED: 51if toastermain.settings.FRESH_ENABLED:
52 urlpatterns.insert(1, url(r'', include('fresh.urls'))) 52 urlpatterns.insert(1, url(r'', include('fresh.urls')))
53 logger.info("Enabled django-fresh extension") 53 #logger.info("Enabled django-fresh extension")
54 54
55if toastermain.settings.DEBUG_PANEL_ENABLED: 55if toastermain.settings.DEBUG_PANEL_ENABLED:
56 import debug_toolbar 56 import debug_toolbar
57 urlpatterns.insert(1, url(r'', include(debug_toolbar.urls))) 57 urlpatterns.insert(1, url(r'', include(debug_toolbar.urls)))
58 logger.info("Enabled django_toolbar extension") 58 #logger.info("Enabled django_toolbar extension")
59 59
60 60
61if toastermain.settings.MANAGED: 61if toastermain.settings.MANAGED:
@@ -86,4 +86,4 @@ for t in os.walk(os.path.dirname(currentdir)):
86 logger.warn("Module \'%s\' has a regexp conflict, was not added to the urlpatterns" % modulename) 86 logger.warn("Module \'%s\' has a regexp conflict, was not added to the urlpatterns" % modulename)
87 87
88from pprint import pformat 88from pprint import pformat
89logger.debug("urlpatterns list %s", pformat(urlpatterns)) 89#logger.debug("urlpatterns list %s", pformat(urlpatterns))