MaxScale/script/maxscale_generate_support_info.py
2019-12-18 13:25:03 +02:00

233 lines
8.3 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2019 MariaDB Corporation Ab
#
# Use of this software is governed by the Business Source License included
# in the LICENSE.TXT file and at www.mariadb.com/bsl11.
#
# Change Date: 2023-12-18
#
# On the date above, in accordance with the Business Source License, use
# of this software will be governed by version 2 or later of the General
# Public License.
import os
import sys
import re
import fnmatch
import zipfile
import subprocess
import datetime
def main(argv):
maxctrl_un = ""
maxctrl_pw = ""
if len(argv) == 2 or len(argv) > 3:
print("Usage: {} [<maxctr_username> <maxctrl_password]".format(argv[0]))
elif len(argv) == 3:
maxctrl_un = argv[1]
maxctrl_pw = argv[2]
main_conf_file_path = "/etc/maxscale.cnf"
config_files_dir = "/etc/maxscale.cnf.d/"
runtime_config_files_dir = "/var/lib/maxscale/maxscale.cnf.d/"
log_files_dir = "/var/log/maxscale/"
time_now = datetime.datetime.now().strftime("%y%m%d_%H%M%S")
output_file_path = os.getcwd() + "/" + "diagnostics_files_" + time_now + ".zip"
print("Assembling typical config and log files for submitting to support.")
print("Using the following paths for config files: " + main_conf_file_path +
", " + config_files_dir + ", " + runtime_config_files_dir)
print("Using the following paths for log files: " + log_files_dir)
print("Output is written to " + output_file_path)
print("\n")
try:
output_file = zipfile.ZipFile(output_file_path, mode='w',
compression=zipfile.ZIP_DEFLATED)
except IOError as e:
print("Error when opening file " + output_file_path + ": " + e.strerror)
return
format_str = "Adding file {} to archive."
# Write contents of primary config file
contents = get_config_file(main_conf_file_path)
if len(contents) > 0:
print(format_str.format(main_conf_file_path))
output_file.writestr(main_conf_file_path, contents)
# Write contents of additional config files
add_config_files_from_dir(config_files_dir, output_file)
# Write contents of runtime config files
add_config_files_from_dir(runtime_config_files_dir, output_file)
# Write contents of log files
if os.path.isdir(log_files_dir):
for file in os.listdir(log_files_dir):
if fnmatch.fnmatch(file, "*.log"):
file_path = log_files_dir + file
contents = read_log_file(file_path)
if len(contents) > 0:
print(format_str.format(file_path))
output_file.writestr(file_path, contents)
# Run maxctrl and add output
contents = run_max_ctrl(maxctrl_un, maxctrl_pw)
if len(contents) > 0:
file_name = "maxctrl_output.txt"
print(format_str.format(file_name))
output_file.writestr(file_name, contents)
# Run gdb and add output
contents = read_core_file()
if len(contents) > 0:
file_name = "gdb_output.txt"
print(format_str.format(file_name))
output_file.writestr(file_name, contents)
# Run some commands to gather general system info.
contents = get_system_info()
if len(contents) > 0:
file_name = "system_info.txt"
print(format_str.format(file_name))
output_file.writestr(file_name, contents)
output_file.close()
def get_config_file(path):
lines = []
regex = re.compile("(password|passwd)\s*=\s*(\S+)")
if os.path.isfile(path):
try:
file = open(path, 'r')
except IOError as e:
print("Error when opening file " + path + ": " + e.strerror)
else:
for line in file:
# If the line looks like it contains a password, only print ***
match = regex.search(line)
if match:
pw_inds = [match.start(2), match.end(2)]
censored_line = line[0:(pw_inds[0])] + "***" + line[(pw_inds[1]):]
lines.append(censored_line)
else:
lines.append(line)
file.close()
else:
print("File " + path + " was not found.")
return ''.join(lines)
def add_config_files_from_dir(directory_to_add, output_file):
format_str = "Adding file {} to archive."
if os.path.isdir(directory_to_add):
for file in os.listdir(directory_to_add):
if fnmatch.fnmatch(file, "*.cnf"):
file_path = directory_to_add + file
contents = get_config_file(file_path)
if len(contents) > 0:
print(format_str.format(file_path))
output_file.writestr(file_path, contents)
def read_log_file(path):
# Log files can be large, so only read at most N bytes from the end
max_byte_count = 500000
contents = ""
if os.path.isfile(path):
try:
file = open(path, 'r')
except IOError as e:
print("Error when opening file " + path + ": " + e.strerror)
else:
if os.stat(path).st_size > max_byte_count:
# Seek to the end, go back and find a newline
file.seek(0, os.SEEK_END)
file.seek(max(0, file.tell() - max_byte_count))
file.readline()
contents = file.read()
file.close()
else:
print("File " + path + " was not found.")
return contents
def run_max_ctrl(maxctrl_un, maxctrl_pw):
maxctrl_commands = ["show maxscale", "show services", "show filters", "show monitors",
"show servers"]
cmd_prefix = "maxctrl "
if len(maxctrl_un) > 0:
cmd_prefix += "--user={} --password={} ".format(maxctrl_un, maxctrl_pw)
total_output = ""
for command in maxctrl_commands:
complete_cmd = cmd_prefix + command
try:
maxctrl_output_bytes = subprocess.check_output(complete_cmd, shell=True,
stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
print("Error when calling maxctrl: command \"{}\" returned {}".format(complete_cmd, e.returncode))
break # If a command fails, stop trying
except IOError as e:
print("Error when calling maxctrl: {}".format(e.strerror))
break
else:
if len(maxctrl_output_bytes) > 0:
total_output += command + "\n" + maxctrl_output_bytes.decode("utf-8") + "\n"
return total_output
def read_core_file():
core_file_path = os.getcwd() + "/" + "core"
core_file_contents = ""
if os.path.isfile(core_file_path):
print("Core file found, running gdb to save call stack.")
gdb_command = "gdb --quiet -batch -ex \"thread apply all bt full\" -ex \"quit\" maxscale core"
try:
gdb_output_bytes = subprocess.check_output(gdb_command, shell=True, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
print("Error when calling gdb: command \"{}\" returned {}".format(gdb_command, e.returncode))
except IOError as e:
print("Error when calling gdb: {}".format(e.strerror))
else:
if len(gdb_output_bytes) > 0:
core_file_contents += gdb_command + "\n\n" + gdb_output_bytes.decode("utf-8")
return core_file_contents
def get_system_info():
commands = ["cat /etc/os-release", "lscpu", "cat /proc/meminfo"]
total_output = ""
for command in commands:
try:
output_bytes = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
# If a command fails, try the next one. It may work.
message = "Error gathering system info: command \"{}\" returned {}".format(
command, e.returncode)
total_output += command + "\n" + message + "\n"
print(message)
except IOError as e:
message = "Error gathering system info: command \"{}\" could not be ran: {}".format(
command, e.strerror)
total_output += command + "\n" + message + "\n"
print(message)
else:
if len(output_bytes) > 0:
total_output += command + "\n" + output_bytes.decode("utf-8") + "\n"
return total_output
if __name__ == "__main__":
main(sys.argv)