1*2d57dc69SVladimir Kotal# 2*2d57dc69SVladimir Kotal# CDDL HEADER START 3*2d57dc69SVladimir Kotal# 4*2d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the 5*2d57dc69SVladimir Kotal# Common Development and Distribution License (the "License"). 6*2d57dc69SVladimir Kotal# You may not use this file except in compliance with the License. 7*2d57dc69SVladimir Kotal# 8*2d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific 9*2d57dc69SVladimir Kotal# language governing permissions and limitations under the License. 10*2d57dc69SVladimir Kotal# 11*2d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each 12*2d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt. 13*2d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the 14*2d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying 15*2d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner] 16*2d57dc69SVladimir Kotal# 17*2d57dc69SVladimir Kotal# CDDL HEADER END 18*2d57dc69SVladimir Kotal# 19*2d57dc69SVladimir Kotal 20*2d57dc69SVladimir Kotal# 21*2d57dc69SVladimir Kotal# Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. 22*2d57dc69SVladimir Kotal# 23*2d57dc69SVladimir Kotal 24*2d57dc69SVladimir Kotalimport logging 25*2d57dc69SVladimir Kotal 26*2d57dc69SVladimir Kotalfrom requests.exceptions import HTTPError 27*2d57dc69SVladimir Kotal 28*2d57dc69SVladimir Kotalfrom .command import Command 29*2d57dc69SVladimir Kotalfrom .utils import is_web_uri 30*2d57dc69SVladimir Kotalfrom .exitvals import ( 31*2d57dc69SVladimir Kotal CONTINUE_EXITVAL, 32*2d57dc69SVladimir Kotal SUCCESS_EXITVAL, 33*2d57dc69SVladimir Kotal FAILURE_EXITVAL 34*2d57dc69SVladimir Kotal) 35*2d57dc69SVladimir Kotalfrom .restful import call_rest_api 36*2d57dc69SVladimir Kotalfrom .patterns import PROJECT_SUBST, COMMAND_PROPERTY 37*2d57dc69SVladimir Kotalimport re 38*2d57dc69SVladimir Kotal 39*2d57dc69SVladimir Kotal 40*2d57dc69SVladimir Kotalclass CommandSequenceBase: 41*2d57dc69SVladimir Kotal """ 42*2d57dc69SVladimir Kotal Wrap the run of a set of Command instances. 43*2d57dc69SVladimir Kotal 44*2d57dc69SVladimir Kotal This class intentionally does not contain any logging 45*2d57dc69SVladimir Kotal so that it can be passed through Pool.map(). 46*2d57dc69SVladimir Kotal """ 47*2d57dc69SVladimir Kotal 48*2d57dc69SVladimir Kotal def __init__(self, name, commands, loglevel=logging.INFO, cleanup=None, 49*2d57dc69SVladimir Kotal driveon=False): 50*2d57dc69SVladimir Kotal self.name = name 51*2d57dc69SVladimir Kotal self.commands = commands 52*2d57dc69SVladimir Kotal self.failed = False 53*2d57dc69SVladimir Kotal self.retcodes = {} 54*2d57dc69SVladimir Kotal self.outputs = {} 55*2d57dc69SVladimir Kotal if cleanup and not isinstance(cleanup, list): 56*2d57dc69SVladimir Kotal raise Exception("cleanup is not a list of commands") 57*2d57dc69SVladimir Kotal 58*2d57dc69SVladimir Kotal self.cleanup = cleanup 59*2d57dc69SVladimir Kotal self.loglevel = loglevel 60*2d57dc69SVladimir Kotal self.driveon = driveon 61*2d57dc69SVladimir Kotal 62*2d57dc69SVladimir Kotal def __str__(self): 63*2d57dc69SVladimir Kotal return str(self.name) 64*2d57dc69SVladimir Kotal 65*2d57dc69SVladimir Kotal def get_cmd_output(self, cmd, indent=""): 66*2d57dc69SVladimir Kotal str = "" 67*2d57dc69SVladimir Kotal for line in self.outputs.get(cmd, []): 68*2d57dc69SVladimir Kotal str += '{}{}'.format(indent, line) 69*2d57dc69SVladimir Kotal 70*2d57dc69SVladimir Kotal return str 71*2d57dc69SVladimir Kotal 72*2d57dc69SVladimir Kotal def fill(self, retcodes, outputs, failed): 73*2d57dc69SVladimir Kotal self.retcodes = retcodes 74*2d57dc69SVladimir Kotal self.outputs = outputs 75*2d57dc69SVladimir Kotal self.failed = failed 76*2d57dc69SVladimir Kotal 77*2d57dc69SVladimir Kotal 78*2d57dc69SVladimir Kotalclass CommandSequence(CommandSequenceBase): 79*2d57dc69SVladimir Kotal 80*2d57dc69SVladimir Kotal re_program = re.compile('ERROR[:]*\\s+') 81*2d57dc69SVladimir Kotal 82*2d57dc69SVladimir Kotal def __init__(self, base): 83*2d57dc69SVladimir Kotal super().__init__(base.name, base.commands, loglevel=base.loglevel, 84*2d57dc69SVladimir Kotal cleanup=base.cleanup, driveon=base.driveon) 85*2d57dc69SVladimir Kotal 86*2d57dc69SVladimir Kotal self.logger = logging.getLogger(__name__) 87*2d57dc69SVladimir Kotal self.logger.setLevel(base.loglevel) 88*2d57dc69SVladimir Kotal 89*2d57dc69SVladimir Kotal def run_command(self, cmd): 90*2d57dc69SVladimir Kotal """ 91*2d57dc69SVladimir Kotal Execute a command and return its return code. 92*2d57dc69SVladimir Kotal """ 93*2d57dc69SVladimir Kotal cmd.execute() 94*2d57dc69SVladimir Kotal self.retcodes[str(cmd)] = cmd.getretcode() 95*2d57dc69SVladimir Kotal self.outputs[str(cmd)] = cmd.getoutput() 96*2d57dc69SVladimir Kotal 97*2d57dc69SVladimir Kotal return cmd.getretcode() 98*2d57dc69SVladimir Kotal 99*2d57dc69SVladimir Kotal def run(self): 100*2d57dc69SVladimir Kotal """ 101*2d57dc69SVladimir Kotal Run the sequence of commands and capture their output and return code. 102*2d57dc69SVladimir Kotal First command that returns code other than 0 terminates the sequence. 103*2d57dc69SVladimir Kotal If the command has return code 2, the sequence will be terminated 104*2d57dc69SVladimir Kotal however it will not be treated as error (unless the 'driveon' parameter 105*2d57dc69SVladimir Kotal is True). 106*2d57dc69SVladimir Kotal 107*2d57dc69SVladimir Kotal If a command contains PROJECT_SUBST pattern, it will be replaced 108*2d57dc69SVladimir Kotal by project name, otherwise project name will be appended to the 109*2d57dc69SVladimir Kotal argument list of the command. 110*2d57dc69SVladimir Kotal 111*2d57dc69SVladimir Kotal Any command entry that is a URI, will be used to submit RESTful API 112*2d57dc69SVladimir Kotal request. 113*2d57dc69SVladimir Kotal """ 114*2d57dc69SVladimir Kotal 115*2d57dc69SVladimir Kotal for command in self.commands: 116*2d57dc69SVladimir Kotal if is_web_uri(command.get(COMMAND_PROPERTY)[0]): 117*2d57dc69SVladimir Kotal try: 118*2d57dc69SVladimir Kotal call_rest_api(command, PROJECT_SUBST, self.name) 119*2d57dc69SVladimir Kotal except HTTPError as e: 120*2d57dc69SVladimir Kotal self.logger.error("RESTful command {} failed: {}". 121*2d57dc69SVladimir Kotal format(command, e)) 122*2d57dc69SVladimir Kotal self.failed = True 123*2d57dc69SVladimir Kotal self.retcodes[str(command)] = FAILURE_EXITVAL 124*2d57dc69SVladimir Kotal 125*2d57dc69SVladimir Kotal break 126*2d57dc69SVladimir Kotal else: 127*2d57dc69SVladimir Kotal command_args = command.get(COMMAND_PROPERTY) 128*2d57dc69SVladimir Kotal command = Command(command_args, 129*2d57dc69SVladimir Kotal env_vars=command.get("env"), 130*2d57dc69SVladimir Kotal resource_limits=command.get("limits"), 131*2d57dc69SVladimir Kotal args_subst={PROJECT_SUBST: self.name}, 132*2d57dc69SVladimir Kotal args_append=[self.name], excl_subst=True) 133*2d57dc69SVladimir Kotal retcode = self.run_command(command) 134*2d57dc69SVladimir Kotal 135*2d57dc69SVladimir Kotal # If a command exits with non-zero return code, 136*2d57dc69SVladimir Kotal # terminate the sequence of commands. 137*2d57dc69SVladimir Kotal if retcode != SUCCESS_EXITVAL: 138*2d57dc69SVladimir Kotal if retcode == CONTINUE_EXITVAL: 139*2d57dc69SVladimir Kotal if not self.driveon: 140*2d57dc69SVladimir Kotal self.logger.debug("command '{}' for project {} " 141*2d57dc69SVladimir Kotal "requested break". 142*2d57dc69SVladimir Kotal format(command, self.name)) 143*2d57dc69SVladimir Kotal self.run_cleanup() 144*2d57dc69SVladimir Kotal else: 145*2d57dc69SVladimir Kotal self.logger.debug("command '{}' for project {} " 146*2d57dc69SVladimir Kotal "requested break however " 147*2d57dc69SVladimir Kotal "the 'driveon' option is set " 148*2d57dc69SVladimir Kotal "so driving on.". 149*2d57dc69SVladimir Kotal format(command, self.name)) 150*2d57dc69SVladimir Kotal continue 151*2d57dc69SVladimir Kotal else: 152*2d57dc69SVladimir Kotal self.logger.error("command '{}' for project {} failed " 153*2d57dc69SVladimir Kotal "with code {}, breaking". 154*2d57dc69SVladimir Kotal format(command, self.name, retcode)) 155*2d57dc69SVladimir Kotal self.failed = True 156*2d57dc69SVladimir Kotal self.run_cleanup() 157*2d57dc69SVladimir Kotal 158*2d57dc69SVladimir Kotal break 159*2d57dc69SVladimir Kotal 160*2d57dc69SVladimir Kotal def run_cleanup(self): 161*2d57dc69SVladimir Kotal """ 162*2d57dc69SVladimir Kotal Call cleanup sequence in case the command sequence failed 163*2d57dc69SVladimir Kotal or termination was requested. 164*2d57dc69SVladimir Kotal """ 165*2d57dc69SVladimir Kotal if self.cleanup is None: 166*2d57dc69SVladimir Kotal return 167*2d57dc69SVladimir Kotal 168*2d57dc69SVladimir Kotal for cleanup_cmd in self.cleanup: 169*2d57dc69SVladimir Kotal if is_web_uri(cleanup_cmd.get(COMMAND_PROPERTY)[0]): 170*2d57dc69SVladimir Kotal try: 171*2d57dc69SVladimir Kotal call_rest_api(cleanup_cmd, PROJECT_SUBST, self.name) 172*2d57dc69SVladimir Kotal except HTTPError as e: 173*2d57dc69SVladimir Kotal self.logger.error("RESTful command {} failed: {}". 174*2d57dc69SVladimir Kotal format(cleanup_cmd, e)) 175*2d57dc69SVladimir Kotal else: 176*2d57dc69SVladimir Kotal command_args = cleanup_cmd.get(COMMAND_PROPERTY) 177*2d57dc69SVladimir Kotal self.logger.debug("Running cleanup command '{}'". 178*2d57dc69SVladimir Kotal format(command_args)) 179*2d57dc69SVladimir Kotal cmd = Command(command_args, 180*2d57dc69SVladimir Kotal args_subst={PROJECT_SUBST: self.name}, 181*2d57dc69SVladimir Kotal args_append=[self.name], excl_subst=True) 182*2d57dc69SVladimir Kotal cmd.execute() 183*2d57dc69SVladimir Kotal if cmd.getretcode() != SUCCESS_EXITVAL: 184*2d57dc69SVladimir Kotal self.logger.error("cleanup command '{}' failed with " 185*2d57dc69SVladimir Kotal "code {}". 186*2d57dc69SVladimir Kotal format(cmd.cmd, cmd.getretcode())) 187*2d57dc69SVladimir Kotal self.logger.info('output: {}'.format(cmd.getoutputstr())) 188*2d57dc69SVladimir Kotal 189*2d57dc69SVladimir Kotal def check(self, ignore_errors): 190*2d57dc69SVladimir Kotal """ 191*2d57dc69SVladimir Kotal Check the output of the commands and perform logging. 192*2d57dc69SVladimir Kotal 193*2d57dc69SVladimir Kotal Return 0 on success, 1 if error was detected. 194*2d57dc69SVladimir Kotal """ 195*2d57dc69SVladimir Kotal 196*2d57dc69SVladimir Kotal ret = SUCCESS_EXITVAL 197*2d57dc69SVladimir Kotal self.logger.debug("Output for project '{}':".format(self.name)) 198*2d57dc69SVladimir Kotal for cmd in self.outputs.keys(): 199*2d57dc69SVladimir Kotal if self.outputs[cmd] and len(self.outputs[cmd]) > 0: 200*2d57dc69SVladimir Kotal self.logger.debug("'{}': {}". 201*2d57dc69SVladimir Kotal format(cmd, self.outputs[cmd])) 202*2d57dc69SVladimir Kotal 203*2d57dc69SVladimir Kotal if self.name in ignore_errors: 204*2d57dc69SVladimir Kotal self.logger.debug("errors of project '{}' ignored". 205*2d57dc69SVladimir Kotal format(self.name)) 206*2d57dc69SVladimir Kotal return 207*2d57dc69SVladimir Kotal 208*2d57dc69SVladimir Kotal self.logger.debug("retcodes = {}".format(self.retcodes)) 209*2d57dc69SVladimir Kotal if any(rv != SUCCESS_EXITVAL and rv != CONTINUE_EXITVAL 210*2d57dc69SVladimir Kotal for rv in self.retcodes.values()): 211*2d57dc69SVladimir Kotal ret = 1 212*2d57dc69SVladimir Kotal self.logger.error("processing of project '{}' failed". 213*2d57dc69SVladimir Kotal format(self)) 214*2d57dc69SVladimir Kotal indent = " " 215*2d57dc69SVladimir Kotal self.logger.error("{}failed commands:".format(indent)) 216*2d57dc69SVladimir Kotal failed_cmds = {k: v for k, v in 217*2d57dc69SVladimir Kotal self.retcodes.items() if v != SUCCESS_EXITVAL} 218*2d57dc69SVladimir Kotal indent = " " 219*2d57dc69SVladimir Kotal for cmd in failed_cmds.keys(): 220*2d57dc69SVladimir Kotal self.logger.error("{}'{}': {}". 221*2d57dc69SVladimir Kotal format(indent, cmd, failed_cmds[cmd])) 222*2d57dc69SVladimir Kotal out = self.get_cmd_output(cmd, 223*2d57dc69SVladimir Kotal indent=indent + " ") 224*2d57dc69SVladimir Kotal if out: 225*2d57dc69SVladimir Kotal self.logger.error(out) 226*2d57dc69SVladimir Kotal self.logger.error("") 227*2d57dc69SVladimir Kotal 228*2d57dc69SVladimir Kotal errored_cmds = {k: v for k, v in self.outputs.items() 229*2d57dc69SVladimir Kotal if self.re_program.match(str(v))} 230*2d57dc69SVladimir Kotal if len(errored_cmds) > 0: 231*2d57dc69SVladimir Kotal ret = 1 232*2d57dc69SVladimir Kotal self.logger.error("Command output in project '{}'" 233*2d57dc69SVladimir Kotal " contains errors:".format(self.name)) 234*2d57dc69SVladimir Kotal indent = " " 235*2d57dc69SVladimir Kotal for cmd in errored_cmds.keys(): 236*2d57dc69SVladimir Kotal self.logger.error("{}{}".format(indent, cmd)) 237*2d57dc69SVladimir Kotal out = self.get_cmd_output(cmd, 238*2d57dc69SVladimir Kotal indent=indent + " ") 239*2d57dc69SVladimir Kotal if out: 240*2d57dc69SVladimir Kotal self.logger.error(out) 241*2d57dc69SVladimir Kotal self.logger.error("") 242*2d57dc69SVladimir Kotal 243*2d57dc69SVladimir Kotal return ret 244