api.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. #
  2. # BitBake Toaster Implementation
  3. #
  4. # Copyright (C) 2016 Intel Corporation
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License version 2 as
  8. # published by the Free Software Foundation.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along
  16. # with this program; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. # Temporary home for the UI's misc API
  19. import re
  20. from orm.models import Project, ProjectTarget, Build, Layer_Version
  21. from orm.models import LayerVersionDependency, LayerSource, ProjectLayer
  22. from bldcontrol.models import BuildRequest
  23. from bldcontrol import bbcontroller
  24. from django.http import HttpResponse, JsonResponse
  25. from django.views.generic import View
  26. from django.core.urlresolvers import reverse
  27. from django.core import serializers
  28. from django.utils import timezone
  29. from django.template.defaultfilters import date
  30. from toastergui.templatetags.projecttags import json, sectohms, get_tasks
  31. def error_response(error):
  32. return JsonResponse({"error": error})
  33. class XhrBuildRequest(View):
  34. def get(self, request, *args, **kwargs):
  35. return HttpResponse()
  36. def post(self, request, *args, **kwargs):
  37. """
  38. Build control
  39. Entry point: /xhr_buildrequest/<project_id>
  40. Method: POST
  41. Args:
  42. id: id of build to change
  43. buildCancel = build_request_id ...
  44. buildDelete = id ...
  45. targets = recipe_name ...
  46. Returns:
  47. {"error": "ok"}
  48. or
  49. {"error": <error message>}
  50. """
  51. project = Project.objects.get(pk=kwargs['pid'])
  52. if 'buildCancel' in request.POST:
  53. for i in request.POST['buildCancel'].strip().split(" "):
  54. try:
  55. br = BuildRequest.objects.get(project=project, pk=i)
  56. try:
  57. bbctrl = bbcontroller.BitbakeController(br.environment)
  58. bbctrl.forceShutDown()
  59. except:
  60. # We catch a bunch of exceptions here because
  61. # this is where the server has not had time to start up
  62. # and the build request or build is in transit between
  63. # processes.
  64. # We can safely just set the build as cancelled
  65. # already as it never got started
  66. build = br.build
  67. build.outcome = Build.CANCELLED
  68. build.save()
  69. # We now hand over to the buildinfohelper to update the
  70. # build state once we've finished cancelling
  71. br.state = BuildRequest.REQ_CANCELLING
  72. br.save()
  73. except BuildRequest.DoesNotExist:
  74. return error_response('No such build id %s' % i)
  75. return error_response('ok')
  76. if 'buildDelete' in request.POST:
  77. for i in request.POST['buildDelete'].strip().split(" "):
  78. try:
  79. BuildRequest.objects.select_for_update().get(
  80. project=project,
  81. pk=i,
  82. state__lte=BuildRequest.REQ_DELETED).delete()
  83. except BuildRequest.DoesNotExist:
  84. pass
  85. return error_response("ok")
  86. if 'targets' in request.POST:
  87. ProjectTarget.objects.filter(project=project).delete()
  88. s = str(request.POST['targets'])
  89. for t in re.sub(r'[;%|"]', '', s).split(" "):
  90. if ":" in t:
  91. target, task = t.split(":")
  92. else:
  93. target = t
  94. task = ""
  95. ProjectTarget.objects.create(project=project,
  96. target=target,
  97. task=task)
  98. project.schedule_build()
  99. return error_response('ok')
  100. response = HttpResponse()
  101. response.status_code = 500
  102. return response
  103. class XhrLayer(View):
  104. """ Get and Update Layer information """
  105. def post(self, request, *args, **kwargs):
  106. """
  107. Update a layer
  108. Entry point: /xhr_layer/<layerversion_id>
  109. Method: POST
  110. Args:
  111. vcs_url, dirpath, commit, up_branch, summary, description
  112. add_dep = append a layerversion_id as a dependency
  113. rm_dep = remove a layerversion_id as a depedency
  114. Returns:
  115. {"error": "ok"}
  116. or
  117. {"error": <error message>}
  118. """
  119. try:
  120. # We currently only allow Imported layers to be edited
  121. layer_version = Layer_Version.objects.get(
  122. id=kwargs['layerversion_id'],
  123. project=kwargs['pid'],
  124. layer_source=LayerSource.TYPE_IMPORTED)
  125. except Layer_Version.DoesNotExist:
  126. return error_response("Cannot find imported layer to update")
  127. if "vcs_url" in request.POST:
  128. layer_version.layer.vcs_url = request.POST["vcs_url"]
  129. if "dirpath" in request.POST:
  130. layer_version.dirpath = request.POST["dirpath"]
  131. if "commit" in request.POST:
  132. layer_version.commit = request.POST["commit"]
  133. layer_version.branch = request.POST["commit"]
  134. if "summary" in request.POST:
  135. layer_version.layer.summary = request.POST["summary"]
  136. if "description" in request.POST:
  137. layer_version.layer.description = request.POST["description"]
  138. if "add_dep" in request.POST:
  139. lvd = LayerVersionDependency(
  140. layer_version=layer_version,
  141. depends_on_id=request.POST["add_dep"])
  142. lvd.save()
  143. if "rm_dep" in request.POST:
  144. rm_dep = LayerVersionDependency.objects.get(
  145. layer_version=layer_version,
  146. depends_on_id=request.POST["rm_dep"])
  147. rm_dep.delete()
  148. try:
  149. layer_version.layer.save()
  150. layer_version.save()
  151. except Exception as e:
  152. return error_response("Could not update layer version entry: %s"
  153. % e)
  154. return JsonResponse({"error": "ok"})
  155. def delete(self, request, *args, **kwargs):
  156. try:
  157. # We currently only allow Imported layers to be deleted
  158. layer_version = Layer_Version.objects.get(
  159. id=kwargs['layerversion_id'],
  160. project=kwargs['pid'],
  161. layer_source=LayerSource.TYPE_IMPORTED)
  162. except Layer_Version.DoesNotExist:
  163. return error_response("Cannot find imported layer to delete")
  164. try:
  165. ProjectLayer.objects.get(project=kwargs['pid'],
  166. layercommit=layer_version).delete()
  167. except ProjectLayer.DoesNotExist:
  168. pass
  169. layer_version.layer.delete()
  170. layer_version.delete()
  171. return JsonResponse({
  172. "error": "ok",
  173. "redirect": reverse('project', args=(kwargs['pid'],))
  174. })
  175. class MostRecentBuildsView(View):
  176. def _was_yesterday_or_earlier(self, completed_on):
  177. now = timezone.now()
  178. delta = now - completed_on
  179. if delta.days >= 1:
  180. return True
  181. return False
  182. def get(self, request, *args, **kwargs):
  183. """
  184. Returns a list of builds in JSON format.
  185. """
  186. mrb_type = 'all'
  187. project = None
  188. project_id = request.GET.get('project_id', None)
  189. if project_id:
  190. try:
  191. mrb_type = 'project'
  192. project = Project.objects.get(pk=project_id)
  193. except:
  194. # if project lookup fails, assume no project
  195. pass
  196. recent_build_objs = Build.get_recent(project)
  197. recent_builds = []
  198. # for timezone conversion
  199. tz = timezone.get_current_timezone()
  200. for build_obj in recent_build_objs:
  201. dashboard_url = reverse('builddashboard', args=(build_obj.pk,))
  202. buildtime_url = reverse('buildtime', args=(build_obj.pk,))
  203. rebuild_url = \
  204. reverse('xhr_buildrequest', args=(build_obj.project.pk,))
  205. cancel_url = \
  206. reverse('xhr_buildrequest', args=(build_obj.project.pk,))
  207. build = {}
  208. build['id'] = build_obj.pk
  209. build['dashboard_url'] = dashboard_url
  210. tasks_complete_percentage = 0
  211. if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED):
  212. tasks_complete_percentage = 100
  213. elif build_obj.outcome == Build.IN_PROGRESS:
  214. tasks_complete_percentage = build_obj.completeper()
  215. build['tasks_complete_percentage'] = tasks_complete_percentage
  216. build['state'] = build_obj.get_state()
  217. build['errors'] = build_obj.errors.count()
  218. build['dashboard_errors_url'] = dashboard_url + '#errors'
  219. build['warnings'] = build_obj.warnings.count()
  220. build['dashboard_warnings_url'] = dashboard_url + '#warnings'
  221. build['buildtime'] = sectohms(build_obj.timespent_seconds)
  222. build['buildtime_url'] = buildtime_url
  223. build['rebuild_url'] = rebuild_url
  224. build['cancel_url'] = cancel_url
  225. build['is_default_project_build'] = build_obj.project.is_default
  226. build['build_targets_json'] = \
  227. json(get_tasks(build_obj.target_set.all()))
  228. # convert completed_on time to user's timezone
  229. completed_on = timezone.localtime(build_obj.completed_on)
  230. completed_on_template = '%H:%M'
  231. if self._was_yesterday_or_earlier(completed_on):
  232. completed_on_template = '%d/%m/%Y ' + completed_on_template
  233. build['completed_on'] = completed_on.strftime(completed_on_template)
  234. targets = []
  235. target_objs = build_obj.get_sorted_target_list()
  236. for target_obj in target_objs:
  237. if target_obj.task:
  238. targets.append(target_obj.target + ':' + target_obj.task)
  239. else:
  240. targets.append(target_obj.target)
  241. build['targets'] = ' '.join(targets)
  242. # abbreviated form of the full target list
  243. abbreviated_targets = ''
  244. num_targets = len(targets)
  245. if num_targets > 0:
  246. abbreviated_targets = targets[0]
  247. if num_targets > 1:
  248. abbreviated_targets += (' +%s' % (num_targets - 1))
  249. build['targets_abbreviated'] = abbreviated_targets
  250. recent_builds.append(build)
  251. return JsonResponse(recent_builds, safe=False)