#/* # * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more # * contributor license agreements. See the NOTICE file distributed with # * this work for additional information regarding copyright ownership. # * The OpenAirInterface Software Alliance licenses this file to You under # * the OAI Public License, Version 1.1 (the "License"); you may not use this file # * except in compliance with the License. # * You may obtain a copy of the License at # * # * http://www.openairinterface.org/?page_id=698 # * # * Unless required by applicable law or agreed to in writing, software # * distributed under the License is distributed on an "AS IS" BASIS, # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # * See the License for the specific language governing permissions and # * limitations under the License. # *------------------------------------------------------------------------------- # * For more information about the OpenAirInterface (OAI) Software Alliance: # * contact@openairinterface.org # */ #--------------------------------------------------------------------- import os import re import sys import subprocess class HtmlReport(): def __init__(self): self.job_name = '' self.job_id = '' self.job_url = '' self.job_start_time = 'TEMPLATE_TIME' self.git_url = '' self.git_src_branch = '' self.git_src_commit = '' self.git_src_commit_msg = None self.git_pull_request = False self.git_target_branch = '' self.git_target_commit = '' self.nb_warnings = 0 self.warning_rows = '' def generate(self): cwd = os.getcwd() self.file = open(cwd + '/test_results_oai_smf.html', 'w') self.generateHeader() self.coding_formatting_log() self.analyze_sca_log() self.buildSummaryHeader() self.initialGitSetup() self.installLibsPackagesRow() self.buildCompileRows() self.copyToTargetImage() self.copyConfToolsToTargetImage() self.imageSizeRow() self.buildSummaryFooter() self.testSummaryHeader() self.testSummaryFooter() self.generateFooter() self.file.close() def generateHeader(self): # HTML Header self.file.write('\n') self.file.write('\n') self.file.write('\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' OAI Core Network Test Results for ' + self.job_name + ' job build #' + self.job_id + '\n') self.file.write('\n') self.file.write('
\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write('
\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' Job Summary -- Job: ' + self.job_name + ' -- Build-ID: ' + self.job_id + '\n') self.file.write('
\n') self.file.write('
\n') # Build Info Summary buildSummary = '' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' #date_formatted = re.sub('\..*', '', self.created) buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' if self.git_pull_request: buildSummary += ' \n' else: buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' if self.git_pull_request: buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' if (self.git_src_commit_msg is not None): buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' else: buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' if (self.git_src_commit_msg is not None): buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += ' \n' buildSummary += '
Build Start Time' + self.job_start_time + '
Build TriggerPull RequestPush Event
GIT Repository' + self.git_url + '
Source Branch' + self.git_src_branch + '
Source Commit ID' + self.git_src_commit + '
Source Commit Message' + self.git_src_commit_msg + '
Target Branch' + self.git_target_branch + '
Target Commit ID' + self.git_target_commit + '
Branch' + self.git_src_branch + '
Commit ID' + self.git_src_commit + '
Commit Message' + self.git_src_commit_msg + '
\n' buildSummary += '
\n' self.file.write(buildSummary) cwd = os.getcwd() if os.path.isfile(cwd + '/ds_tester_results_oai_cn5g.html'): newEpcReport = open(cwd + '/ds_tester_results_oai_cn5g_new.html', 'w') buildSummaryDone = True with open(cwd + '/ds_tester_results_oai_cn5g.html', 'r') as originalEpcReport: for line in originalEpcReport: result = re.search('DS Tester Summary', line) if (result is not None) and buildSummaryDone: newEpcReport.write(buildSummary) buildSummaryDone = False newEpcReport.write(line) originalEpcReport.close() newEpcReport.close() os.rename(cwd + '/ds_tester_results_oai_cn5g_new.html', cwd + '/ds_tester_results_oai_cn5g.html') if os.path.isfile(cwd + '/deploy_results_oai_cn5g.html'): newEpcReport = open(cwd + '/deploy_results_oai_cn5g_new.html', 'w') buildSummaryDone = True with open(cwd + '/deploy_results_oai_cn5g.html', 'r') as originalEpcReport: for line in originalEpcReport: result = re.search('Deployment Summary', line) if (result is not None) and buildSummaryDone: newEpcReport.write(buildSummary) buildSummaryDone = False newEpcReport.write(line) originalEpcReport.close() newEpcReport.close() os.rename(cwd + '/deploy_results_oai_cn5g_new.html', cwd + '/deploy_results_oai_cn5g.html') def generateFooter(self): self.file.write('
End of Build Report -- Copyright 2020 OpenAirInterface. All Rights Reserved.
\n') self.file.write('
\n') self.file.write('\n') def coding_formatting_log(self): cwd = os.getcwd() self.file.write('

OAI Coding / Formatting Guidelines Check

\n') if os.path.isfile(cwd + '/src/oai_rules_result.txt'): cmd = 'grep NB_FILES_FAILING_CHECK ' + cwd + '/src/oai_rules_result.txt | sed -e "s#NB_FILES_FAILING_CHECK=##"' nb_fail = subprocess.check_output(cmd, shell=True, universal_newlines=True) cmd = 'grep NB_FILES_CHECKED ' + cwd + '/src/oai_rules_result.txt | sed -e "s#NB_FILES_CHECKED=##"' nb_total = subprocess.check_output(cmd, shell=True, universal_newlines=True) if int(nb_fail.strip()) == 0: self.file.write('
\n') if self.git_pull_request: self.file.write(' All modified files in Pull-Request follow OAI rules. -> (' + nb_total.strip() + ' were checked)\n') else: self.file.write(' All files in repository follow OAI rules. -> (' + nb_total.strip() + ' were checked)\n') self.file.write('
\n') else: self.file.write('
\n') if self.git_pull_request: self.file.write(' ' + nb_fail.strip() + ' modified files in Pull-Request DO NOT follow OAI rules. -> (' + nb_total.strip() + ' were checked)\n') else: self.file.write(' ' + nb_fail.strip() + ' files in repository DO NOT follow OAI rules. -> (' + nb_total.strip() + ' were checked)\n') self.file.write('
\n') if os.path.isfile(cwd + '/src/oai_rules_result_list.txt'): self.file.write(' \n') self.file.write('
\n') self.file.write('

Please apply the following command to this(ese) file(s):

\n') self.file.write('

cd src && clang-format -i filename(s)

\n') self.file.write(' \n') self.file.write(' \n') with open(cwd + '/src/oai_rules_result_list.txt', 'r') as filelist: for line in filelist: self.file.write(' \n') filelist.close() self.file.write('
Filename
' + line.strip() + '
\n') self.file.write('
\n') else: self.file.write('
\n') self.file.write(' Was NOT performed (with CLANG-FORMAT tool). \n') self.file.write('
\n') self.file.write('
\n') def analyze_sca_log(self): cwd = os.getcwd() if os.path.isfile(cwd + '/archives/cppcheck_build.log'): self.file.write('

Static Code Analysis

\n') if os.path.isfile(cwd + '/archives/cppcheck.xml'): nb_errors = 0 nb_warnings = 0 nb_uninitvar = 0 nb_uninitStructMember = 0 nb_memleak = 0 nb_doubleFree = 0 nb_resourceLeak = 0 nb_nullPointer = 0 nb_arrayIndexOutOfBounds = 0 nb_bufferAccessOutOfBounds = 0 nb_unknownEvaluationOrder = 0 with open(cwd + '/archives/cppcheck.xml', 'r') as xmlfile: for line in xmlfile: result = re.search('severity="warning"', line) if result is not None: nb_warnings += 1 result = re.search('severity="error"', line) if result is not None: nb_errors += 1 result = re.search('uninitvar', line) if result is not None: nb_uninitvar += 1 result = re.search('uninitStructMember', line) if result is not None: nb_uninitStructMember += 1 result = re.search('memleak', line) if result is not None: nb_memleak += 1 result = re.search('doubleFree', line) if result is not None: nb_doubleFree += 1 result = re.search('resourceLeak', line) if result is not None: nb_resourceLeak += 1 result = re.search('nullPointer', line) if result is not None: nb_nullPointer += 1 result = re.search('arrayIndexOutOfBounds', line) if result is not None: nb_arrayIndexOutOfBounds += 1 result = re.search('bufferAccessOutOfBounds', line) if result is not None: nb_bufferAccessOutOfBounds += 1 result = re.search('unknownEvaluationOrder', line) if result is not None: nb_unknownEvaluationOrder += 1 xmlfile.close() if (nb_errors == 0) and (nb_warnings == 0): self.file.write('
\n') self.file.write(' CPPCHECK found NO error and NO warning \n') self.file.write('
\n') elif (nb_errors == 0): self.file.write('
\n') self.file.write(' CPPCHECK found NO error and ' + str(nb_warnings) + ' warnings \n') self.file.write('
\n') else: self.file.write('
\n') self.file.write(' CPPCHECK found ' + str(nb_errors) + ' errors and ' + str(nb_warnings) + ' warnings \n') self.file.write('
\n') if (nb_errors > 0) or (nb_warnings > 0): self.file.write(' \n') self.file.write('
\n') self.file.write('
\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') nb_others = nb_uninitvar + nb_uninitStructMember + nb_memleak + nb_doubleFree + nb_resourceLeak + nb_nullPointer + nb_arrayIndexOutOfBounds + nb_arrayIndexOutOfBounds + nb_bufferAccessOutOfBounds + nb_unknownEvaluationOrder nb_others = nb_errors - nb_others self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write('
Error / Warning TypeNb ErrorsNb Warnings
Uninitialized variable' + str(nb_uninitvar) + 'N/A
Uninitialized struct member' + str(nb_uninitStructMember) + 'N/A
Memory leak' + str(nb_memleak) + 'N/A
Memory is freed twice' + str(nb_doubleFree) + 'N/A
Resource leak' + str(nb_resourceLeak) + 'N/A
Possible null pointer dereference' + str(nb_nullPointer) + 'N/A
Array access out of bounds' + str(nb_arrayIndexOutOfBounds) + 'N/A
Buffer is accessed out of bounds' + str(nb_bufferAccessOutOfBounds) + 'N/A
Expression depends on order of evaluation of side effects' + str(nb_unknownEvaluationOrder) + 'N/A
Others' + str(nb_others) + '' + str(nb_warnings) + '
Total' + str(nb_errors) + '' + str(nb_warnings) + '
\n') self.file.write('
\n') self.file.write('

Full details in artifact (cppcheck.xml)

\n') self.file.write('

Graphical Interface tool : cppcheck-gui -l cppcheck.xml

\n') self.file.write('
\n') self.file.write('
\n') else: self.file.write('
\n') self.file.write(' Was NOT performed (with CPPCHECK tool). \n') self.file.write('
\n') def buildSummaryHeader(self): self.file.write('

Docker Image Build Summary

\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') def buildSummaryFooter(self): self.file.write('
Stage NameImage KindOAI SMF cNF
\n') self.file.write('
\n') if self.nb_warnings > 0: self.file.write('

Compilation Warnings Details

\n') self.file.write(' \n') self.file.write('
\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(self.warning_rows) self.file.write(' \n') self.file.write('
FileLine NumberStatusMessage
\n') self.file.write('
\n') def testBuildSummaryHeader(self): self.file.write('

Test Images Build Summary

\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') def testBuildSummaryFooter(self): self.file.write('
Stage NameTest AMF ServerTest AMF ClientTest UDM Server
\n') self.file.write('
\n') def initialGitSetup(self): self.file.write(' \n') self.file.write(' Initial Git Setup\n') self.analyze_docker_build_git_part('SMF') self.file.write(' \n') def analyze_docker_build_git_part(self, nfType): if nfType != 'SMF': self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return logFileName = 'smf_docker_image_build.log' self.file.write(' Builder Image\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/' + logFileName): status = False section_start_pattern = 'git config --global http' section_end_pattern = 'WORKDIR /openair-smf/build/scripts' section_status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False status = True logfile.close() if status: cell_msg = '
'
				cell_msg += 'OK:\n'
				cell_msg += ' -- All Git Operations went successfully
\n' else: cell_msg = '
'
				cell_msg += 'KO::\n'
				cell_msg += ' -- Some Git Operations went WRONG
\n' else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def installLibsPackagesRow(self): self.file.write(' \n') self.file.write(' SW libs and packages Installation\n') self.analyze_install_log('SMF') self.file.write(' \n') def analyze_install_log(self, nfType): if nfType != 'SMF': self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return logFileName = 'smf_docker_image_build.log' self.file.write(' Builder Image\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/' + logFileName): status = False section_start_pattern = 'build_smf --install-deps --force' section_end_pattern = 'build_smf --clean --Verbose --build-type Release --jobs' section_status = False package_install = False fmt_build_start = False fmt_build_status = False folly_build_start = False folly_build_status = False spdlog_build_start = False spdlog_build_status = False pistache_build_start = False pistache_build_status = False json_build_start = False json_build_status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False if section_status: result = re.search('SMF deps installation successful', line) if result is not None: status = True result = re.search('Install fmt from source', line) if result is not None: package_install = True fmt_build_start = True result = re.search('Installing: /usr/local/lib/pkgconfig/fmt.pc', line) if result is not None: fmt_build_status = True result = re.search('Cloning into \'folly\'', line) if result is not None: folly_build_start = True result = re.search('Installing: /usr/local/lib/libfollybenchmark.a', line) if result is not None: folly_build_status = True result = re.search('Install spdlog from', line) if result is not None: spdlog_build_start = True result = re.search('Install Pistache from', line) if result is not None: spdlog_build_status = True pistache_build_start = True result = re.search('Installing: /usr/local/lib/libpistache.a', line) if result is not None: pistache_build_status = True result = re.search('Install Nlohmann Json', line) if result is not None: json_build_status = True result = re.search('Installing: /usr/local/lib/cmake/nlohmann_json/nlohmann_jsonTargets.cmake', line) if result is not None: json_build_status = True logfile.close() if status: cell_msg = '
'
				cell_msg += 'OK:\n'
			else:
				cell_msg = '	  
'
				cell_msg += 'KO:\n'
			cell_msg += ' -- build_smf --install-deps --force\n'
			if package_install:
				cell_msg += '   ** Packages Installation: OK\n'
			else:
				cell_msg += '   ** Packages Installation: KO\n'
			if fmt_build_status:
				cell_msg += '   ** fmt Installation: OK\n'
			else:
				cell_msg += '   ** fmt Installation: KO\n'
			if folly_build_status:
				cell_msg += '   ** folly Installation: OK\n'
			else:
				cell_msg += '   ** folly Installation: KO\n'
			if spdlog_build_status:
				cell_msg += '   ** spdlog Installation: OK\n'
			else:
				cell_msg += '   ** spdlog Installation: KO\n'
			if pistache_build_status:
				cell_msg += '   ** pistache Installation: OK\n'
			else:
				cell_msg += '   ** pistache Installation: KO\n'
			if json_build_status:
				cell_msg += '   ** Nlohmann Json Installation: OK\n'
			else:
				cell_msg += '   ** Nlohmann Json Installation: KO\n'
			cell_msg += '
\n' else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def buildCompileRows(self): self.file.write(' \n') self.file.write(' cNF Compile / Build\n') self.analyze_build_log('SMF', True) self.file.write(' \n') self.file.write(' \n') self.analyze_compile_log('SMF', True) self.file.write(' \n') def analyze_build_log(self, nfType, imageKind): if nfType != 'SMF' and nfType != 'AMF-Server' and nfType != 'AMF-Client' and nfType != 'UDM-Server': if imageKind: self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return if nfType == 'SMF': logFileName = 'smf_docker_image_build.log' if nfType == 'AMF-Server': logFileName = 'amf_server_docker_image_build.log' if nfType == 'AMF-Client': logFileName = 'amf_client_docker_image_build.log' if nfType == 'UDM-Server': logFileName = 'udm_server_docker_image_build.log' if imageKind: self.file.write(' Builder Image\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/' + logFileName): status = False if nfType == 'SMF': section_start_pattern = 'build_smf --clean --Verbose --build-type Release --jobs' section_end_pattern = 'FROM ubuntu:bionic as oai-smf$' pass_pattern = 'smf installed' if nfType == 'AMF-Server': section_start_pattern = 'mkdir build && cd build && cmake .. && make' section_end_pattern = 'FROM ubuntu:bionic as test-amf-server$' pass_pattern = 'Built target amf-server' path_pattern = 'src/test/amf' if nfType == 'AMF-Client': section_start_pattern = 'mkdir build && cd build && cmake .. && make' section_end_pattern = 'FROM ubuntu:bionic as test-amf-client$' pass_pattern = 'Built target amf-client' path_pattern = 'src/test/amf_client' if nfType == 'UDM-Server': section_start_pattern = 'mkdir build && cd build && cmake .. && make' section_end_pattern = 'FROM ubuntu:bionic as test-udm-server$' pass_pattern = 'Built target udm-server' path_pattern = 'src/test/udm' section_status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False if section_status: result = re.search(pass_pattern, line) if result is not None: status = True logfile.close() if status: cell_msg = '
'
				cell_msg += 'OK:\n'
			else:
				cell_msg = '	  
'
				cell_msg += 'KO:\n'
			if nfType != 'SMF':
				cell_msg += ' -- cd ' + path_pattern + '\n'
			cell_msg += ' -- ' + section_start_pattern + '
\n' else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def analyze_compile_log(self, nfType, imageKind): if nfType != 'SMF' and nfType != 'AMF-Server' and nfType != 'AMF-Client' and nfType != 'UDM-Server': if imageKind: self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return if nfType == 'SMF': logFileName = 'smf_docker_image_build.log' if nfType == 'AMF-Server': logFileName = 'amf_server_docker_image_build.log' if nfType == 'AMF-Client': logFileName = 'amf_client_docker_image_build.log' if nfType == 'UDM-Server': logFileName = 'udm_server_docker_image_build.log' if imageKind: self.file.write(' Builder Image\n') cwd = os.getcwd() nb_errors = 0 nb_warnings = 0 if os.path.isfile(cwd + '/archives/' + logFileName): if nfType == 'SMF': section_start_pattern = 'build_smf --clean --Verbose --build-type Release --jobs' section_end_pattern = 'FROM ubuntu:bionic as oai-smf$' if nfType == 'AMF-Server': section_start_pattern = 'mkdir build && cd build && cmake .. && make' section_end_pattern = 'FROM ubuntu:bionic as test-amf-server$' if nfType == 'AMF-Client': section_start_pattern = 'mkdir build && cd build && cmake .. && make' section_end_pattern = 'FROM ubuntu:bionic as test-amf-client$' if nfType == 'UDM-Server': section_start_pattern = 'mkdir build && cd build && cmake .. && make' section_end_pattern = 'FROM ubuntu:bionic as test-udm-server$' section_status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False if section_status: result = re.search('error:', line) if result is not None: nb_errors += 1 result = re.search('warning:', line) if result is not None: nb_warnings += 1 if nfType == 'SMF': correctLine = re.sub("^.*/openair-smf","/openair-smf",line.strip()) wordsList = correctLine.split(None,2) filename = re.sub(":[0-9]*:[0-9]*:","", wordsList[0]) linenumber = re.sub(filename + ':',"", wordsList[0]) linenumber = re.sub(':[0-9]*:',"", linenumber) error_warning_status = re.sub(':',"", wordsList[1]) error_warning_msg = re.sub('^.*' + error_warning_status + ':', '', correctLine) self.warning_rows += '' + filename + '' + linenumber + '' + error_warning_status + '' + error_warning_msg + '\n' logfile.close() if nb_warnings == 0 and nb_errors == 0: cell_msg = '
'
			elif nb_warnings < 20 and nb_errors == 0:
				cell_msg = '	   
'
			else:
				cell_msg = '	   
'
			if nb_errors > 0:
				cell_msg += str(nb_errors) + ' errors found in compile log\n'
			cell_msg += str(nb_warnings) + ' warnings found in compile log
\n' if nfType == 'SMF': self.nb_warnings = nb_warnings else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def copyToTargetImage(self): self.file.write(' \n') self.file.write(' SW libs Installation / Copy from Builder\n') self.analyze_copy_log('SMF') self.file.write(' \n') def analyze_copy_log(self, nfType): if nfType != 'SMF': self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return logFileName = 'smf_docker_image_build.log' self.file.write(' Target Image\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/' + logFileName): section_start_pattern = 'FROM ubuntu:bionic as oai-smf$' section_end_pattern = 'WORKDIR /openair-smf/etc' section_status = False status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False status = True logfile.close() if status: cell_msg = '
'
				cell_msg += 'OK:\n'
			else:
				cell_msg = '	   
'
				cell_msg += 'KO:\n'
			cell_msg += '
\n' else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def copyConfToolsToTargetImage(self): self.file.write(' \n') self.file.write(' Copy Template Conf / Tools from Builder\n') self.analyze_copy_conf_tool_log('SMF') self.file.write(' \n') def analyze_copy_conf_tool_log(self, nfType): if nfType != 'SMF': self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return logFileName = 'smf_docker_image_build.log' self.file.write(' Target Image\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/' + logFileName): section_start_pattern = 'WORKDIR /openair-smf/etc' section_end_pattern = 'Successfully tagged oai-smf' section_status = False status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False status = True logfile.close() if status: cell_msg = '
'
				cell_msg += 'OK:\n'
			else:
				cell_msg = '	   
'
				cell_msg += 'KO:\n'
			cell_msg += '
\n' else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def imageSizeRow(self): self.file.write(' \n') self.file.write(' Image Size\n') self.analyze_image_size_log('SMF', True) self.file.write(' \n') def analyze_image_size_log(self, nfType, imageKind): if nfType != 'SMF' and nfType != 'AMF-Server' and nfType != 'AMF-Client' and nfType != 'UDM-Server': if imageKind: self.file.write(' N/A\n') self.file.write(' Wrong NF Type for this Report\n') return if nfType == 'SMF': logFileName = 'smf_docker_image_build.log' if nfType == 'AMF-Server': logFileName = 'amf_server_docker_image_build.log' if nfType == 'AMF-Client': logFileName = 'amf_client_docker_image_build.log' if nfType == 'UDM-Server': logFileName = 'udm_server_docker_image_build.log' if imageKind: self.file.write(' Target Image\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/' + logFileName): if nfType == 'SMF': section_start_pattern = 'Successfully tagged oai-smf' section_end_pattern = 'OAI-SMF DOCKER IMAGE BUILD' if nfType == 'AMF-Server': section_start_pattern = 'Successfully tagged test-amf-server' section_end_pattern = 'TEST-AMF-SERVER DOCKER IMAGE BUILD' if nfType == 'AMF-Client': section_start_pattern = 'Successfully tagged test-amf-client' section_end_pattern = 'TEST-AMF-CLIENT DOCKER IMAGE BUILD' if nfType == 'UDM-Server': section_start_pattern = 'Successfully tagged test-udm-server' section_end_pattern = 'TEST-UDM-SERVER DOCKER IMAGE BUILD' section_status = False status = False with open(cwd + '/archives/' + logFileName, 'r') as logfile: for line in logfile: result = re.search(section_start_pattern, line) if result is not None: section_status = True result = re.search(section_end_pattern, line) if result is not None: section_status = False if section_status: if nfType == 'SMF': if self.git_pull_request: result = re.search('oai-smf *ci-tmp', line) else: result = re.search('oai-smf *develop', line) if nfType == 'AMF-Server': result = re.search('test-amf-server *test-deploy', line) if nfType == 'AMF-Client': result = re.search('test-amf-client *test-deploy', line) if nfType == 'UDM-Server': result = re.search('test-udm-server *test-deploy', line) if result is not None: result = re.search('ago *([0-9A-Z]+)', line) if result is not None: size = result.group(1) status = True logfile.close() if status: cell_msg = '
'
				cell_msg += 'OK:  ' + size + '\n'
			else:
				cell_msg = '	   
'
				cell_msg += 'KO:\n'
			cell_msg += '
\n' else: cell_msg = '
'
			cell_msg += 'KO: logfile (' + logFileName + ') not found
\n' self.file.write(cell_msg) def testBuildCompileRows(self): self.file.write(' \n') self.file.write(' cNF Compile / Build\n') self.analyze_build_log('AMF-Server', False) self.analyze_build_log('AMF-Client', False) self.analyze_build_log('UDM-Server', False) self.file.write(' \n') self.file.write(' \n') self.analyze_compile_log('AMF-Server', False) self.analyze_compile_log('AMF-Client', False) self.analyze_compile_log('UDM-Server', False) self.file.write(' \n') def testImageSizeRow(self): self.file.write(' \n') self.file.write(' Image Size\n') self.analyze_image_size_log('AMF-Server', False) self.analyze_image_size_log('AMF-Client', False) self.analyze_image_size_log('UDM-Server', False) self.file.write(' \n') def sanityCheckSummaryHeader(self): self.file.write('

Sanity Check Deployment Summary

\n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') self.file.write(' \n') def sanityCheckSummaryFooter(self): self.file.write('
Stage NameOAI SMF cNFOAI SPGWU (as UPF) cNFTest AMF-ServerTest UDM-ServerTest AMF-Client
\n') self.file.write('
\n') def sanityCheckDeployRow(self): self.file.write(' \n') self.file.write(' Container Start\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/amf_server_config.log') and os.path.isfile(cwd + '/archives/udm_server_config.log') and os.path.isfile(cwd + '/archives/amf_client_config.log'): cell_msg = '
OK
\n' for x in range(0, 5): self.file.write(cell_msg) else: cell_msg = '
KO?
\n' for x in range(0, 5): self.file.write(cell_msg) self.file.write(' \n') def sanityCheckTestRow(self): self.file.write(' \n') self.file.write(' Local Test\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/smf_check_run.log'): nb_sm_req_from_amf = 0 nb_pdu_create_smf_req = 0 nb_encode_pdu_establish_accept = 0 nb_pdu_session_pending = 0 nb_pdu_session_ue_establish_req = 0 nb_pdu_status_active = 0 with open(cwd + '/archives/smf_check_run.log', 'r') as logfile: for line in logfile: result = re.search('Received a SM context create request from AMF', line) if result is not None: nb_sm_req_from_amf += 1 result = re.search('PDU Session Create SM Context Request', line) if result is not None: nb_pdu_create_smf_req += 1 result = re.search('Encode PDU Session Establishment Accept', line) if result is not None: nb_encode_pdu_establish_accept += 1 result = re.search('Set PDU Session Status to PDU_SESSION_ESTABLISHMENT_PENDING', line) if result is not None: nb_pdu_session_pending += 1 result = re.search('PDU_SESSION_ESTABLISHMENT_UE_REQUESTED', line) if result is not None: nb_pdu_session_ue_establish_req += 1 result = re.search('Set PDU Session Status to PDU_SESSION_ACTIVE', line) if result is not None: nb_pdu_status_active += 1 logfile.close() if nb_sm_req_from_amf > 0 and nb_pdu_create_smf_req > 0 and nb_encode_pdu_establish_accept > 0 and nb_pdu_session_pending > 0 and nb_pdu_session_ue_establish_req > 0 and nb_pdu_status_active > 0: cell_msg = '
OK:\n'
			else:
				cell_msg = '	   
KO:\n'
			if nb_sm_req_from_amf > 0:
				cell_msg += '  -- Received a SM context create request from AMF            : OK\n'
			else:
				cell_msg += '  -- Received a SM context create request from AMF            : KO\n'
			if nb_pdu_create_smf_req > 0:
				cell_msg += '  -- PDU Session Create SM Context Request                    : OK\n'
			else:
				cell_msg += '  -- PDU Session Create SM Context Request                    : KO\n'
			if nb_encode_pdu_establish_accept > 0:
				cell_msg += '  -- Encode PDU Session Establishment Accept                  : OK\n'
			else:
				cell_msg += '  -- Encode PDU Session Establishment Accept                  : KO\n'
			if nb_pdu_session_pending > 0:
				cell_msg += '  -- PDU Session Status to PDU_SESSION_ESTABLISHMENT_PENDING  : OK\n'
			else:
				cell_msg += '  -- PDU Session Status to PDU_SESSION_ESTABLISHMENT_PENDING  : KO\n'
			if nb_pdu_session_ue_establish_req > 0:
				cell_msg += '  -- PDU_SESSION_ESTABLISHMENT_UE_REQUESTED                   : OK\n'
			else:
				cell_msg += '  -- PDU_SESSION_ESTABLISHMENT_UE_REQUESTED                   : KO\n'
			if nb_pdu_status_active > 0:
				cell_msg += '  -- Set PDU Session Status to PDU_SESSION_ACTIVE             : OK\n'
			else:
				cell_msg += '  -- Set PDU Session Status to PDU_SESSION_ACTIVE             : KO\n'
			cell_msg += '
\n' else: cell_msg = '
KO?
\n' self.file.write(cell_msg) self.file.write(' \n') def sanityCheckConfigRow(self): self.file.write(' \n') self.file.write(' Container Config\n') cwd = os.getcwd() if os.path.isfile(cwd + '/archives/smf_config.log'): cmd = 'grep -c OK ' + cwd + '/archives/smf_config.log' try: is_ok = subprocess.check_output(cmd, shell=True, universal_newlines=True) except: is_ok = '0' if int(is_ok.strip()) == 0: cell_msg = '
KO?
\n' else: cell_msg = '
OK
\n' else: cell_msg = '
KO?
\n' self.file.write(cell_msg) if os.path.isfile(cwd + '/archives/spgwu_config.log'): cmd = 'grep -c OK ' + cwd + '/archives/spgwu_config.log' try: is_ok = subprocess.check_output(cmd, shell=True, universal_newlines=True) except: is_ok = '0' if int(is_ok.strip()) == 0: cell_msg = '
KO?
\n' else: cell_msg = '
OK
\n' else: cell_msg = '
KO?
\n' self.file.write(cell_msg) cell_msg = '
N/A
\n' for x in range(0, 3): self.file.write(cell_msg) self.file.write(' \n') def testSummaryHeader(self): self.file.write('

Test Summary

\n') self.file.write('
\n') self.file.write(' Not performed yet. \n') self.file.write('
\n') def testSummaryFooter(self): self.file.write('
\n') def Usage(): print('----------------------------------------------------------------------------------------------------------------------') print('generateHtmlReport.py') print(' Generate an HTML report for the Jenkins pipeline on openair-smf.') print('----------------------------------------------------------------------------------------------------------------------') print('Usage: python3 generateHtmlReport.py [options]') print(' --help Show this help.') print('---------------------------------------------------------------------------------------------- Mandatory Options -----') print(' --job_name=[Jenkins Job name]') print(' --job_id=[Jenkins Job Build ID]') print(' --job_url=[Jenkins Job Build URL]') print(' --git_url=[Git Repository URL]') print(' --git_src_branch=[Git Source Branch Name]') print(' --git_src_commit=[Git Source Commit SHA-ONE]') print('----------------------------------------------------------------------------------------------- Optional Options -----') print(' --git_pull_request=True') print(' --git_target_branch=[Git Target Branch Name]') print(' --git_target_commit=[Git Target Commit SHA-ONE]') #-------------------------------------------------------------------------------------------------------- # # Start of main # #-------------------------------------------------------------------------------------------------------- argvs = sys.argv argc = len(argvs) HTML = HtmlReport() while len(argvs) > 1: myArgv = argvs.pop(1) if re.match('^\-\-help$', myArgv, re.IGNORECASE): Usage() sys.exit(0) elif re.match('^\-\-job_name=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-job_name=(.+)$', myArgv, re.IGNORECASE) HTML.job_name = matchReg.group(1) elif re.match('^\-\-job_id=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-job_id=(.+)$', myArgv, re.IGNORECASE) HTML.job_id = matchReg.group(1) elif re.match('^\-\-job_url=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-job_url=(.+)$', myArgv, re.IGNORECASE) HTML.job_url = matchReg.group(1) elif re.match('^\-\-git_url=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-git_url=(.+)$', myArgv, re.IGNORECASE) HTML.git_url = matchReg.group(1) elif re.match('^\-\-git_src_branch=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-git_src_branch=(.+)$', myArgv, re.IGNORECASE) HTML.git_src_branch = matchReg.group(1) elif re.match('^\-\-git_src_commit=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-git_src_commit=(.+)$', myArgv, re.IGNORECASE) HTML.git_src_commit = matchReg.group(1) elif re.match('^\-\-git_src_commit_msg=(.+)$', myArgv, re.IGNORECASE): # Not Mandatory matchReg = re.match('^\-\-git_src_commit_msg=(.+)$', myArgv, re.IGNORECASE) HTML.git_src_commit_msg = matchReg.group(1) elif re.match('^\-\-git_pull_request=(.+)$', myArgv, re.IGNORECASE): # Can be silent: would be false! matchReg = re.match('^\-\-git_pull_request=(.+)$', myArgv, re.IGNORECASE) if matchReg.group(1) == 'true' or matchReg.group(1) == 'True': HTML.git_pull_request = True elif re.match('^\-\-git_target_branch=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-git_target_branch=(.+)$', myArgv, re.IGNORECASE) HTML.git_target_branch = matchReg.group(1) elif re.match('^\-\-git_target_commit=(.+)$', myArgv, re.IGNORECASE): matchReg = re.match('^\-\-git_target_commit=(.+)$', myArgv, re.IGNORECASE) HTML.git_target_commit = matchReg.group(1) else: sys.exit('Invalid Parameter: ' + myArgv) if HTML.job_name == '' or HTML.job_id == '' or HTML.job_url == '': sys.exit('Missing Parameter in job description') if HTML.git_url == '' or HTML.git_src_branch == '' or HTML.git_src_commit == '': sys.exit('Missing Parameter in Git Repository description') if HTML.git_pull_request: if HTML.git_target_commit == '' or HTML.git_target_branch == '': sys.exit('Missing Parameter in Git Pull Request Repository description') HTML.generate()