From df5c8d6471bf2484db61c7f180c9758fad4182e1 Mon Sep 17 00:00:00 2001 From: Marlon Rodriguez Garcia Date: Mon, 11 Dec 2023 11:47:05 -0500 Subject: bitbake: toaster: Added new feature to import eventlogs from command line into toaster using replay functionality Added a new button on the base template to access a new template. Added a model register the information on the builds and generate access links Added a form to include the option to load specific files Added jquery and ajax functions to block screen and redirect to build page when import eventlogs is trigger Added a new button on landing page linked to import build page, and set min-height of buttons in landing page for uniformity Removed test assertion to check command line build in content, because new button contains text Updated toaster_eventreplay to use library Fix test in test_layerdetails_page Rebased from master This feature uses the value from the variable BB_DEFAULT_EVENTLOG to read the files created by bitbake Exclude listing of files that don't contain the allvariables definitions used to replay builds This part of the feature should be revisited. Over a long period of time, the BB_DEFAULT_EVENTLOG will exponentially increase the size of the log file and cause bottlenecks when importing. (Bitbake rev: ab96cafe03d8bab33c1de09602cc62bd6974f157) Signed-off-by: Marlon Rodriguez Garcia Signed-off-by: Richard Purdie --- bitbake/lib/toaster/toastergui/views.py | 173 +++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 2 deletions(-) (limited to 'bitbake/lib/toaster/toastergui/views.py') diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 735d304ad8..3b5b9f5bd9 100644 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -6,24 +6,36 @@ # SPDX-License-Identifier: GPL-2.0-only # +import ast import re +import subprocess +import sys + +import bb.cooker +from bb.ui import toasterui +from bb.ui import eventreplay from django.db.models import F, Q, Sum from django.db import IntegrityError -from django.shortcuts import render, redirect, get_object_or_404 +from django.shortcuts import render, redirect, get_object_or_404, HttpResponseRedirect from django.utils.http import urlencode from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe from orm.models import LogMessage, Variable, Package_Dependency, Package from orm.models import Task_Dependency, Package_File from orm.models import Target_Installed_Package, Target_File from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File -from orm.models import BitbakeVersion, CustomImageRecipe +from orm.models import BitbakeVersion, CustomImageRecipe, EventLogsImports from django.urls import reverse, resolve +from django.contrib import messages + from django.core.exceptions import ObjectDoesNotExist +from django.core.files.storage import FileSystemStorage +from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.http import HttpResponseNotFound, JsonResponse from django.utils import timezone +from django.views.generic import TemplateView from datetime import timedelta, datetime from toastergui.templatetags.projecttags import json as jsonfilter from decimal import Decimal @@ -32,6 +44,10 @@ import os from os.path import dirname import mimetypes +from toastergui.forms import LoadFileForm + +from collections import namedtuple + import logging from toastermain.logs import log_view_mixin @@ -41,6 +57,7 @@ logger = logging.getLogger("toaster") # Project creation and managed build enable project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER')) is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC')) +import_page = False class MimeTypeFinder(object): # setting this to False enables additional non-standard mimetypes @@ -1940,3 +1957,155 @@ if True: except (ObjectDoesNotExist, IOError): return toaster_render(request, "unavailable_artifact.html") + +class CommandLineBuilds(TemplateView): + model = EventLogsImports + template_name = 'command_line_builds.html' + + def get_context_data(self, **kwargs): + context = super(CommandLineBuilds, self).get_context_data(**kwargs) + #get value from BB_DEFAULT_EVENTLOG defined in bitbake.conf + eventlog = subprocess.check_output(['bitbake-getvar', 'BB_DEFAULT_EVENTLOG', '--value']) + if eventlog: + logs_dir = os.path.dirname(eventlog.decode().strip('\n')) + files = os.listdir(logs_dir) + imported_files = EventLogsImports.objects.all() + files_list = [] + + # Filter files that end with ".json" + event_files = [] + for file in files: + if file.endswith(".json"): + # because BB_DEFAULT_EVENTLOG is a directory, we need to check if the file is a valid eventlog + with open("{}/{}".format(logs_dir, file)) as efile: + content = efile.read() + if 'allvariables' in content: + event_files.append(file) + + #build dict for template using db data + for event_file in event_files: + if imported_files.filter(name=event_file): + files_list.append({ + 'name': event_file, + 'imported': True, + 'build_id': imported_files.filter(name=event_file)[0].build_id, + 'size': os.path.getsize("{}/{}".format(logs_dir, event_file)) + }) + else: + files_list.append({ + 'name': event_file, + 'imported': False, + 'build_id': None, + 'size': os.path.getsize("{}/{}".format(logs_dir, event_file)) + }) + context['import_all'] = True + + context['files'] = files_list + context['dir'] = logs_dir + else: + context['files'] = [] + context['dir'] = '' + + # enable session variable + if not self.request.session.get('file'): + self.request.session['file'] = "" + + context['form'] = LoadFileForm() + context['project_enable'] = project_enable + return context + + def post(self, request, **kwargs): + logs_dir = request.POST.get('dir') + all_files = request.POST.get('all') + + imported_files = EventLogsImports.objects.all() + try: + if all_files == 'true': + # use of session variable to deactivate icon for builds in progress + request.session['all_builds'] = True + request.session.modified = True + request.session.save() + + files = ast.literal_eval(request.POST.get('file')) + for file in files: + if imported_files.filter(name=file.get('name')).exists(): + imported_files.filter(name=file.get('name'))[0].imported = True + else: + with open("{}/{}".format(logs_dir, file.get('name'))) as eventfile: + # load variables from the first line + variables = None + while line := eventfile.readline().strip(): + try: + variables = json.loads(line)['allvariables'] + break + except (KeyError, json.JSONDecodeError): + continue + if not variables: + raise Exception("File content missing build variables") + eventfile.seek(0) + params = namedtuple('ConfigParams', ['observe_only'])(True) + player = eventreplay.EventPlayer(eventfile, variables) + + toasterui.main(player, player, params) + event_log_import = EventLogsImports.objects.create(name=file.get('name'), imported=True) + event_log_import.build_id = Build.objects.last().id + event_log_import.save() + else: + if self.request.FILES.get('eventlog_file'): + file = self.request.FILES['eventlog_file'] + else: + file = request.POST.get('file') + # use of session variable to deactivate icon for build in progress + request.session['file'] = file + request.session['all_builds'] = False + request.session.modified = True + request.session.save() + + if imported_files.filter(name=file).exists(): + imported_files.filter(name=file)[0].imported = True + else: + if isinstance(file, InMemoryUploadedFile) or isinstance(file, TemporaryUploadedFile): + variables = None + while line := file.readline().strip(): + try: + variables = json.loads(line)['allvariables'] + break + except (KeyError, json.JSONDecodeError): + continue + if not variables: + raise Exception("File content missing build variables") + file.seek(0) + params = namedtuple('ConfigParams', ['observe_only'])(True) + player = eventreplay.EventPlayer(file, variables) + if not os.path.exists('{}/{}'.format(logs_dir, file.name)): + fs = FileSystemStorage(location=logs_dir) + fs.save(file.name, file) + toasterui.main(player, player, params) + else: + with open("{}/{}".format(logs_dir, file)) as eventfile: + # load variables from the first line + variables = None + while line := eventfile.readline().strip(): + try: + variables = json.loads(line)['allvariables'] + break + except (KeyError, json.JSONDecodeError): + continue + if not variables: + raise Exception("File content missing build variables") + eventfile.seek(0) + params = namedtuple('ConfigParams', ['observe_only'])(True) + player = eventreplay.EventPlayer(eventfile, variables) + toasterui.main(player, player, params) + event_log_import = EventLogsImports.objects.create(name=file, imported=True) + event_log_import.build_id = Build.objects.last().id + event_log_import.save() + request.session['file'] = "" + except Exception: + messages.add_message( + self.request, + messages.ERROR, + "The file content is not in the correct format. Update file content or upload a different file." + ) + return HttpResponseRedirect("/toastergui/cmdline/") + return HttpResponseRedirect('/toastergui/builds/') -- cgit v1.2.3-54-g00ecf