iact3/report/generate_reports.py (286 lines of code) (raw):

import asyncio import datetime import json import xml.dom.minidom as minidom import logging import os import textwrap import time from pathlib import Path import aiofiles import tabulate import yattag from iact3.stack import Stacker, Stack LOG = logging.getLogger(__name__) class ReportBuilder: """ This class generates the test report. """ def __init__(self, stacks: Stacker, output_file: Path): self._stacks = stacks self._output_file = output_file self._report_json_name = f'{self._stacks.project_name}-result.json' async def generate_report(self): doc = yattag.Doc() tag = doc.tag text = doc.text dirname = os.path.abspath(os.path.dirname(__file__)) details = [] test_result = 'Success' async with aiofiles.open(f'{dirname}/html.css', 'r') as f: output_css = await f.read() with tag("html"): with tag("head"): doc.stag("meta", charset="utf-8") doc.stag("meta", name="viewport", content="width=device-width") with tag("style", type="text/css"): text(output_css) with tag("title"): text("Iact3 Report") with tag("body"): tested_on = time.strftime("%A - %b,%d,%Y @ %H:%M:%S") with tag("table", "class=header-table-fill"): with tag("tbody"): with tag("th", "colspan=2"): with tag("tr"): with tag("td"): text("Tested on: ") text(tested_on) doc.stag("p") with tag("table", "class=table-fill"): with tag("tbody"): with tag("thread"): with tag("tr"): with tag("th", "class=text-center", "width=25%"): text("Test Name") with tag("th", "class=text-left", "width=10%"): text("Tested Region") with tag("th", "class=text-left", "width=30%"): text("Stack Name") with tag("th", "class=text-left", "width=20%"): text("Tested Results") with tag("th", "class=text-left", "width=15%"): text("Test Logs") for stack in self._stacks.stacks: with tag("tr", "class= test-footer"): with tag("td", "colspan=5"): text("") LOG.info(f'Start producing test reports for {stack.test_name} in {stack.region}...') test_name = stack.test_name status = stack.status stack_name = stack.name region = stack.region stack_id = stack.id css = "class=test-green" if status == 'CREATE_COMPLETE' else 'class=test-red' with tag("tr"): with tag("td", "class=test-info"): with tag("h3"): text(test_name) with tag("td", "class=text-left"): text(region) with tag("td", "class=text-left"): ref_url = f"https://ros.console.aliyun.com/{region}/stacks/{stack_id}" with tag("a", href=ref_url): text(stack_name) with tag("td", css): text(str(status)) with tag("td", "class=text-left"): clog = f"{stack_name}-{region}.txt" with tag("a", href=clog): text("View Logs ") success = stack.launch_succeeded if not success: test_result = 'Failed' details.append({ 'TestName': test_name, 'TestedRegion': region, 'StackName': stack_name, 'StackId': stack.id, 'TestResult': status, 'TestLog': clog, 'Result': 'Success' if success else 'Failed' }) doc.stag("p") html_output = yattag.indent( doc.getvalue(), indentation=" ", newline="\r\n", indent_text=True ) with open(str(self._output_file / 'index.html'), 'w', encoding='utf-8') as _f: _f.write(html_output) json_result = { 'Result': test_result, 'Details': details } file_name = self._report_json_name with open(str(self._output_file / file_name), 'w', encoding='utf-8') as f: json.dump(json_result, f, ensure_ascii=False) LOG.info(f'The test report has been completed, you can view it in {self._output_file} directory.') return html_output async def get_events(self, stack: Stack): stack_events = await stack.events(refresh=True) events = [] for event in stack_events: event_details = { "TimeStamp": str(event.timestamp), "ResourceStatus": event.status, "ResourceType": event.type, "LogicalResourceId": event.logical_id, } if event.status_reason: event_details["ResourceStatusReason"] = event.status_reason else: event_details["ResourceStatusReason"] = "" events.append(event_details) return events async def get_resources(self, stack: Stack): stack_resources = await stack.resources(refresh=True) resources = [] for resource in stack_resources: resource_details = { "TimeStamp": str(resource.last_updated_timestamp), "ResourceStatus": resource.status, "ResourceType": resource.type, "LogicalResourceId": resource.logical_id, "PhysicalResourceId": resource.physical_id } if resource.status_reason: resource_details["ResourceStatusReason"] = resource.status_reason else: resource_details["ResourceStatusReason"] = "" resources.append(resource_details) return resources async def create_logs(self, log_format:str): if log_format: log_formats = log_format.split(',') else: log_formats = [] tasks = [] file_names = [] for stack in self._stacks.stacks: file_name = f'{stack.name}-{stack.region}' task = asyncio.create_task(self.write_logs(stack, self._output_file / file_name, log_formats)) tasks.append(task) file_names.append(file_name + ".txt") if "json" in log_formats: file_names.append(file_name + ".json") if "xml" in log_formats: file_names.append(file_name + ".xml") await asyncio.gather(*tasks) file_names.append(self._report_json_name) return file_names async def add_attr_minidom(self, doc: minidom.Document,father_node: minidom.Element, label, value): child = doc.createElement(label) father_node.appendChild(child) if isinstance(value, dict): for k, v in value.items(): await self.add_attr_minidom(doc, child, k, v) elif isinstance(value, list): for list_item in value: await self.add_attr_minidom(doc, child, "item", list_item) else: child_content = doc.createTextNode(str(value)) child.appendChild(child_content) async def write_logs(self, stack: Stack, log_path: Path, log_formats: list): stack_name = stack.name region = stack.region stack_id = stack.id or '' parameters = stack.parameters events = await self.get_events(stack) if stack.id else [] resources = await self.get_resources(stack) if stack.id else [] if stack.launch_succeeded: tested_result = 'Success' reason = 'Stack launch was successful' else: tested_result = 'Failed' reason = f'{stack.status}, {stack.status_reason}' test_time = datetime.datetime.now().strftime("%A, %d. %B %Y %I:%M%p") if "json" in log_formats: with open(str(log_path)+'.json', 'w', encoding='utf-8') as log_output: json_output = {} json_output["Region"] = region json_output["StackName"] = stack_name json_output["StackId"] = stack_id json_output["Parameters"] = parameters json_output["TestedResult"] = tested_result json_output["ResultReason"] = str(reason) json_output["Events"] = events json_output["Resources"] = resources json_output["TestTime"] = test_time json.dump(json_output, log_output, ensure_ascii=False, indent=4) log_output.close() if "xml" in log_formats: with open(str(log_path)+'.xml', 'w', encoding='utf-8') as log_output: xml_doc = minidom.Document() root = xml_doc.createElement(f'{stack.name}-{stack.region}') xml_doc.appendChild(root) await self.add_attr_minidom(xml_doc, root, "Region", region) await self.add_attr_minidom(xml_doc, root, "StackName", stack_name) await self.add_attr_minidom(xml_doc, root, "StackId", stack_id) await self.add_attr_minidom(xml_doc, root, "Parameters", parameters) await self.add_attr_minidom(xml_doc, root, "TestedResult", tested_result) await self.add_attr_minidom(xml_doc, root, "ResultReason", reason) await self.add_attr_minidom(xml_doc, root, "Events", events) await self.add_attr_minidom(xml_doc, root, "Resources", resources) await self.add_attr_minidom(xml_doc, root, "TestTime", test_time) log_output.write(xml_doc.toprettyxml(indent="\t")) async with aiofiles.open(str(log_path)+'.txt', "a", encoding="utf-8") as log_output: await log_output.write( "------------------------------------------------------------------" "-----------\n" ) await log_output.write("Region: " + region + "\n") await log_output.write("StackName: " + stack_name + "\n") await log_output.write("StackId: " + stack_id + "\n") await log_output.write( "******************************************************************" "***********\n" ) if parameters: parameters = [ dict( ParameterKey=k, ParameterValue=v if v is not None else '' ) for k, v in parameters.items() ] await log_output.write(tabulate.tabulate(parameters, headers="keys")) await log_output.write( "\n******************************************************************" "***********\n" ) await log_output.write(f"TestedResult: {tested_result} \n") await log_output.write("ResultReason: \n") await log_output.write(textwrap.fill(str(reason), 85) + "\n") await log_output.write( "******************************************************************" "***********\n" ) await log_output.write( "******************************************************************" "***********\n" ) await log_output.write("Events: \n") await log_output.writelines(tabulate.tabulate(events, headers="keys")) await log_output.write( "\n****************************************************************" "*************\n" ) await log_output.write( "******************************************************************" "***********\n" ) await log_output.write("Resources: \n") await log_output.write(tabulate.tabulate(resources, headers="keys")) await log_output.write( "\n****************************************************************" "*************\n" ) await log_output.write( "------------------------------------------------------------------" "-----------\n" ) await log_output.write( "Tested on: " + test_time + "\n" ) await log_output.write( "------------------------------------------------------------------" "-----------\n\n" ) await log_output.close()