1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
# -*- coding: utf-8 -*-
# Copyright (C) 2015-2018 Linaro Limited
#
# Author: Neil Williams <neil.williams@linaro.org>
# Remi Duraffort <remi.duraffort@linaro.org>
#
# This file is part of LAVA.
#
# LAVA is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License version 3
# as published by the Free Software Foundation
#
# LAVA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with LAVA. If not, see <http://www.gnu.org/licenses/>.
import os
import yaml
import logging
from django.db import DataError
from django.utils.translation import ungettext_lazy
from django.core.exceptions import PermissionDenied
from linaro_django_xmlrpc.models import AuthToken
def help_max_length(max_length):
return ungettext_lazy( # pylint: disable=no-member
u"Maximum length: {0} character", u"Maximum length: {0} characters", max_length
).format(max_length)
class StreamEcho: # pylint: disable=too-few-public-methods
def write(self, value): # pylint: disable=no-self-use,
return value
def description_filename(job):
filename = os.path.join(job.output_dir, "description.yaml")
if not os.path.exists(filename):
return None
return filename
class V2Loader(yaml.Loader):
def remove_pipeline_module(self, suffix, node):
if "lava_dispatcher.pipeline" in suffix:
suffix = suffix.replace("lava_dispatcher.pipeline", "lava_dispatcher")
return self.construct_python_object(suffix, node)
def remove_pipeline_module_name(self, suffix, node):
# Fix old dumps when "pipeline" was a module
if "lava_dispatcher.pipeline" in suffix:
suffix = suffix.replace("lava_dispatcher.pipeline", "lava_dispatcher")
# Fix dumps when dispatcher exceptions where not in lava_common.
exceptions = [
"ConfigurationError",
"InfrastructureError",
"JobCanceled",
"JobError",
"LAVABug",
"MultinodeProtocolTimeoutError",
"TestError",
]
for exc in exceptions:
if "lava_dispatcher.action.%s" % exc in suffix:
suffix = suffix.replace(
"lava_dispatcher.action.%s" % exc, "lava_common.exceptions.%s" % exc
)
return self.construct_python_name(suffix, node)
def remove_pipeline_module_new(self, suffix, node):
if "lava_dispatcher.pipeline" in suffix:
suffix = suffix.replace("lava_dispatcher.pipeline", "lava_dispatcher")
return self.construct_python_object_new(suffix, node)
V2Loader.add_multi_constructor(
u"tag:yaml.org,2002:python/name:", V2Loader.remove_pipeline_module_name
)
V2Loader.add_multi_constructor(
u"tag:yaml.org,2002:python/object:", V2Loader.remove_pipeline_module
)
V2Loader.add_multi_constructor(
u"tag:yaml.org,2002:python/object/new:", V2Loader.remove_pipeline_module_new
)
def description_data(job):
logger = logging.getLogger("lava_results_app")
filename = description_filename(job)
if not filename:
return {}
data = None
try:
data = yaml.load(open(filename, "r"), Loader=V2Loader)
except yaml.YAMLError as exc:
logger.error("Unable to parse description for %s", job.id)
logger.exception(exc)
except OSError as exc:
logger.error("Unable to open description for %s", job.id)
logger.exception(exc)
# This should be a dictionary, None is not acceptable
return data if data else {}
# FIXME: relocate these two functions into dbutils to avoid needing django settings here.
# other functions in utils can be run outside django. Remove import of AuthToken.
def anonymous_token(request, job):
querydict = request.GET
user = querydict.get("user", default=None)
token = querydict.get("token", default=None)
# safe to call with (None, None) - returns None
auth_user = AuthToken.get_user_for_secret(username=user, secret=token)
if not user and not job.is_public:
raise PermissionDenied()
if not auth_user:
raise PermissionDenied()
return auth_user
def check_request_auth(request, job):
if job.is_public:
return
if not request.user.is_authenticated:
# handle anonymous access
auth_user = anonymous_token(request, job)
if not auth_user or not job.can_view(auth_user):
raise PermissionDenied()
elif not job.can_view(request.user):
raise PermissionDenied()
def get_testcases_with_limit(testsuite, limit=None, offset=None):
logger = logging.getLogger("lava_results_app")
if limit:
try:
if not offset:
testcases = list(testsuite.testcase_set.all().order_by("id")[:limit])
else:
testcases = list(
testsuite.testcase_set.all().order_by("id")[offset:][:limit]
)
except ValueError as e:
logger.warning("Offset and limit must be integers: %s", str(e))
return []
except DataError as e:
logger.warning("Offset must be positive integer: %s", str(e))
return []
else:
testcases = list(testsuite.testcase_set.all().order_by("id"))
return testcases
def testcase_export_fields():
"""
Keep this list in sync with the keys in export_testcase
:return: list of fields used in export_testcase
"""
return [
"job",
"suite",
"result",
"measurement",
"unit",
"duration",
"timeout",
"logged",
"level",
"metadata",
"url",
"name",
"id",
"log_start_line",
"log_end_line",
]
def export_testcase(testcase, with_buglinks=False):
"""
Returns string versions of selected elements of a TestCase
Unicode causes issues with CSV and can complicate YAML parsing
with non-python parsers.
:param testcase: list of TestCase objects
:return: Dictionary containing relevant information formatted for export
"""
metadata = dict(testcase.action_metadata) if testcase.action_metadata else {}
extra_source = []
extra_data = metadata.get("extra")
if isinstance(extra_data, str) and os.path.exists(extra_data):
with open(metadata["extra"], "r") as extra_file:
# TODO: this can fail!
items = yaml.load(extra_file, Loader=yaml.CLoader)
# hide the !!python OrderedDict prefix from the output.
for key, value in items.items():
extra_source.append({key: value})
metadata["extra"] = extra_source
casedict = {
"name": str(testcase.name),
"job": str(testcase.suite.job_id),
"suite": str(testcase.suite.name),
"result": str(testcase.result_code),
"measurement": str(testcase.measurement),
"unit": str(testcase.units),
"level": metadata.get("level", ""),
"url": str(testcase.get_absolute_url()),
"id": str(testcase.id),
"logged": str(testcase.logged),
"log_start_line": str(testcase.start_log_line)
if testcase.start_log_line
else "",
"log_end_line": str(testcase.end_log_line) if testcase.end_log_line else "",
"metadata": metadata,
}
if with_buglinks:
casedict["buglinks"] = [
str(url) for url in testcase.buglinks.values_list("url", flat=True)
]
return casedict
|