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 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
|
###################################################################################
# LAVA QA tool
# Copyright (C) 2015 Collabora Ltd.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library 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
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 US
###################################################################################
from __future__ import print_function
import re
import yaml
import json
import os
import os.path
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
import jinja2.exceptions
import jinja2
import requests
try:
from xmlrpc.client import Fault
except ImportError:
from xmlrpclib import Fault
from jinja2 import Environment, FileSystemLoader, \
StrictUndefined, DebugUndefined
from lqa_api.exit_codes import APPLICATION_ERROR
from lqa_api.job import Job
from lqa_api.waitqueue import WaitQueue
from lqa_api.outputlog import OutputLog, OutputLogError
from lqa_tool.settings import lqa_logger
from lqa_tool.utils import merge_profiles
from lqa_tool.commands import Command
from lqa_tool.exceptions import ProfileNotFound
from lqa_tool.utils import print_add_msg, print_wait_msg, \
print_remove_msg, print_timeout_msg
class Profiles(object):
"""Initialize profiles for command objects.
:param cmd: The command object to setup
:param default_config_file: The default configuration file path"""
def __init__(self, cmd, default_config_file):
# Set yaml configuration
try:
with open(default_config_file) as conf_data:
env = jinja2.Environment(loader = jinja2.FileSystemLoader('.'))
t = env.get_template(default_config_file)
self.config = yaml.safe_load(t.render())
except EnvironmentError as e:
lqa_logger.error(e)
exit(APPLICATION_ERROR)
except yaml.scanner.ScannerError as e:
lqa_logger.error(e)
exit(APPLICATION_ERROR)
class SubmitCmd(Command):
def __init__(self, args):
Command.__init__(self, args)
self.waitq = WaitQueue()
def run(self):
"""Submit job id
It follows the priority for profiles processing:
1) Any profile specified by '-p'
2) All profiles from 'all-profiles'
3) Or run without any profile....
"""
if self.args.profile or self.args.all_profiles:
# A profile file is required for using profiles
if not self.args.profile_file:
raise ProfileNotFound
# Get the profiles from the <profiles>.yaml file
self.profiles = Profiles(self, self.args.profile_file)
# Fetch the main profile and merge later with any sub profile
main_profile = self.profiles.config.get('main-profile', {})
if self.args.profile:
# If -p/--profile specified.
for profile_name in self.args.profile:
# Filter profiles from the profiles file matching -p.
profiles = [
profile
for profile in self.profiles.config.get('profiles', [])
if profile['name'] == profile_name ]
# Exit with failure if no profile in the filtered list.
if not profiles:
lqa_logger.error(
"error: profile {} not found in profile file {}"
.format(profile_name, self.args.profile_file))
exit(APPLICATION_ERROR)
# Run with found profiles.
# There can exist profiles with the same name.
for profile in profiles:
self._run(merge_profiles(main_profile, profile))
else:
# Otherwise is --all-profiles.
for profile in self.profiles.config.get('profiles', []):
self._run(merge_profiles(main_profile, profile))
else:
self._run()
# Wait for jobs if --wait option enabled.
if self.waitq.has_jobs():
try:
self.waitq.wait(self.args.wait_timeout, print_wait_msg,
print_remove_msg, print_timeout_msg)
# Exit from here if the --wait option is passed so the
# exit code from the queue is used instead.
exit(self.waitq.exit_code)
except ValueError as e:
lqa_logger.error("wait timeout: {}".format(e))
exit(APPLICATION_ERROR)
def _mangle_json(self, data, variables, job_file):
# Deserialize rendered template into a json object.
# This is required to apply the 'key fields' replacement.
try:
json_obj = json.loads(data)
except ValueError as e:
lqa_logger.error("json file '{}': {}".format(job_file, e))
return None
# Replace key field variables.
r_json_obj = _replaceKeyFields(json_obj, variables)
# Set priority.
# This option overrides the profile values to the 'priority' field.
if self.args.priority:
r_json_obj['priority'] = self.args.priority
# After all variable substitutions, check if the image url exists if
# --check-image-url is true.
if self.args.check_image_url:
self._check_image_url(r_json_obj)
# Serialize json object to json string for submitting job.
return json.dumps(r_json_obj, indent=2)
def _run(self, profile={}):
variables = profile.get('variables', {})
if self.args.template_vars:
# The variables translates to a hash that we can easily use
# for mapping the fields -> values in the json template
for item in self.args.template_vars:
k, v = item.split(':', 1)
variables[k] = v
# Get the job files from either the command line if available,
# or from the 'templates' profile variable otherwise.
job_files = []
template_dirs = []
if self.args.submit_job:
for f in self.args.submit_job:
job_files.append(os.path.basename(f))
template_dirs.append(os.path.dirname(f))
else:
job_files = profile.get('templates', [])
template_dirs.append(profile.get('template-dir', os.getcwd()))
# Choose the 'undefined' variable strategy
uv = (self.args.debug_vars and DebugUndefined) or StrictUndefined
# Create a template environment.
# Use list/set to remove any duplicate.
env = Environment(loader=FileSystemLoader(list(set(template_dirs))),
undefined=uv)
# Process job files
for job_file in job_files:
try:
# Get template from environment and render it.
data = env.get_template(job_file).render(variables)
except TypeError as e:
lqa_logger.error("type error in {}: {}".format(job_file, e))
continue
except jinja2.exceptions.TemplateNotFound as e:
lqa_logger.error("template not found: {}".format(e))
continue
except jinja2.exceptions.UndefinedError as e:
lqa_logger.error("template variable not defined in {}: {}"
.format(job_file, e))
continue
# V1 files we replace key-value pairs not jus templated values
if job_file.endswith(".json"):
data = self._mangle_json(data, variables, job_file)
if data == None:
continue
elif not job_file.endswith(".yaml"):
lqa_logger.error("Job file not recognize as v1 or v2: {}"
.format(job_file))
if self.args.verbose:
print(data)
if not self.args.dry_run:
try:
job_id = self.server.submit_job(data)
# Queue jobs ids to wait for them if 'wait' option enabled.
if self.args.wait_timeout:
self.waitq.addjob(Job(job_id, self.server), print_add_msg)
except Fault as e:
lqa_logger.error("Submitting job {}: {}".format(job_file, e))
continue
lqa_logger.info("Submitted job {} with id {}" \
.format(job_file, job_id))
# Show live output if '--live' option passed
if self.args.live:
if len(job_files) > 1:
lqa_logger.info("lqa submit: --live option is not valid "
"with more than 1 file (skipping)")
elif self.args.wait_timeout:
lqa_logger.info("lqa submit: --live option can't be used "
"together with --wait option (skipping)")
else:
# Everything is valid to use live option, then go for it!
try:
OutputLog(job_id, self.server, is_live=True,
logger=lqa_logger).run()
except OutputLogError as e:
lqa_logger.error("lqa submit: error: {}".format(e))
exit(APPLICATION_ERROR)
def _check_image_url(self, json_job_obj):
"""This method checks for the existence of the image field url path
for all the 'deploy_image' actions (if available)"""
for action in json_job_obj['actions']:
if action['command'] == 'deploy_image':
image = action['parameters'].get('image', None)
if image:
# Test for local url path
url = urlparse(image)
if url.scheme == 'file':
if os.path.exists(url.path):
return
else:
url_code = requests.head(image)
if url_code.status_code == requests.codes.ok:
return
lqa_logger.error(
"lqa submit error: image url does not exist: {}"
.format(image))
exit(APPLICATION_ERROR)
def _replaceKeyFields(json_obj, variables):
"""Replace key fields
This method will iterate over a JSON object and will assign values to
those keys (json fields) with the same name of a (profile) variable.
:param json_obj: The json object to be processed
:param variables: Dictionary containing variable and values.
:returns: A new json object containing the replaced values for the
json fields with the same name of the specified variables.
"""
def _replaceKey(variable, value, json_obj):
if type(json_obj) == list:
for i, json_obj_v in enumerate(json_obj):
json_obj[i] = _replaceKey(variable, value, json_obj_v)
elif type(json_obj) == dict:
for k in json_obj:
# Replace key value if the 'key' == 'variable'
if k == variable:
json_obj[k] = value
# Otherwise continue processing the json fields.
else:
json_obj[k] = _replaceKey(variable, value, json_obj[k])
return json_obj
j = json_obj
for variable in variables:
j = _replaceKey(variable, variables[variable], j)
return j
|