Providing plug-in support:
Typically, a test program is written to perform certain basic tests on a test
machine. For example, one might write an "obmc_boot" program that performs
various boot tests on the Open BMC machine.
Experience has shown that over time, additional testing needs often arise.
Examples of such additional testing needs might include:
- Data base logging of results
- Performance measurements
- Memory leak analysis
- Hardware verification
- Error log (sels) analysis
- SOL_console
The developer could add additional parms to obmc_boot and likewise add
supporting code in obmc_boot each time a need arises. Users would employ
these new functions as follows:
obmc_boot --perf=1 --mem_leak=1 --db_logging=1 --db_userid=xxxx
However, another option would be to add general-purpose plug-in support to
obmc_boot. This would allow the user to indicate to obmc_boot which plug-in
packages it ought to run. Such plug-in packages could be written in any
langauge whatsoever: Robot, python, bash, perl, C++.
An example call to obmc_boot would then look something like this:
obmc_boot --plug_in_dir_paths="Perf:Mem_leak:DB_logging"
Now all the obmc_boot developer needs to do is call the plug-in processing
module (process_plug_in_packages.py) at various call points which are agreed
upon by the obmc_boot developer and the plug-in developers. Example call
points which can be implemented are:
setup - Called at the start of obmc_boot
pre_boot - Called before each boot test initiated by obmc_boot
post_boot - Called after each boot test initiated by obmc_boot
cleanup - Called at the end of obmc_boot
This allows the choice of options to be passed as data to obmc_boot. The
advantages of this approach are:
- Much less maintenance of the original test program (obmc_boot).
- Since plug-ins are separate from the main test program, users are free to
have plug-ins that suit their environments. One user may wish to log results
to a database that is of no interest to the rest of the world. Such a plug-in
can be written and need never be pushed to gerrit/github.
- One can even write temporary plug-ins designed just to collect data or stop
when a particular defect occurs.
In our current environment, the concept has proven exceedingly useful. We
have over 40 permanent plug-ins and in our temp plug-in directory, we still
have over 80 plug-ins.
Change-Id: Iee0ea950cffaef202d56da4dae7c044b6366a59c
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_plug_in.py b/lib/gen_plug_in.py
new file mode 100755
index 0000000..4c90473
--- /dev/null
+++ b/lib/gen_plug_in.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+
+r"""
+This module provides functions which are useful for running plug-ins.
+"""
+
+import sys
+import os
+import commands
+import glob
+
+import gen_print as gp
+import gen_misc as gm
+
+# Some help text that is common to more than one program.
+plug_in_dir_paths_help_text = \
+ 'This is a colon-separated list of plug-in directory paths. If one' +\
+ ' of the entries in the list is a plain directory name (i.e. no' +\
+ ' path info), it will be taken to be a native plug-in. In that case,' +\
+ ' %(prog)s will search for the native plug-in in the "plug-ins"' +\
+ ' subdirectory of each path in the PATH environment variable until it' +\
+ ' is found. Also, integrated plug-ins will automatically be appended' +\
+ ' to your plug_in_dir_paths list. An integrated plug-in is any plug-in' +\
+ ' found using the PATH variable that contains a file named "integrated".'
+
+mch_class_help_text = \
+ 'The class of machine that we are testing (e.g. "op" = "open power",' +\
+ ' "obmc" = "open bmc", etc).'
+
+PATH_LIST = gm.return_path_list()
+
+
+###############################################################################
+def get_plug_in_base_paths():
+
+ r"""
+ Get plug-in base paths and return them as a list.
+
+ This function searches the PATH_LIST (created from PATH environment
+ variable) for any paths that have a "plug_ins" subdirectory. All such
+ paths are considered plug_in_base paths.
+ """
+
+ global PATH_LIST
+
+ plug_in_base_path_list = []
+
+ for path in PATH_LIST:
+ candidate_plug_in_base_path = path + "plug_ins/"
+ if os.path.isdir(candidate_plug_in_base_path):
+ plug_in_base_path_list.append(candidate_plug_in_base_path)
+
+ return plug_in_base_path_list
+
+###############################################################################
+# Define global plug_in_base_path_list and call get_plug_in_base_paths to set
+# its value.
+plug_in_base_path_list = get_plug_in_base_paths()
+
+
+###############################################################################
+def find_plug_in_package(plug_in_name):
+
+ r"""
+ Find and return the normalized directory path of the specified plug in.
+ This is done by searching the global plug_in_base_path_list.
+
+ Description of arguments:
+ plug_in_name The unqualified name of the plug-in
+ package.
+ """
+
+ global plug_in_base_path_list
+ for plug_in_base_dir_path in plug_in_base_path_list:
+ candidate_plug_in_dir_path = os.path.normpath(plug_in_base_dir_path +
+ plug_in_name) + \
+ os.sep
+ if os.path.isdir(candidate_plug_in_dir_path):
+ return candidate_plug_in_dir_path
+
+ return ""
+
+###############################################################################
+
+
+###############################################################################
+def validate_plug_in_package(plug_in_dir_path,
+ mch_class="obmc"):
+
+ r"""
+ Validate the plug in package and return the normalized plug-in directory
+ path.
+
+ Description of arguments:
+ plug_in_dir_path The "relative" or absolute path to a plug
+ in package directory.
+ mch_class The class of machine that we are testing
+ (e.g. "op" = "open power", "obmc" = "open
+ bmc", etc).
+ """
+
+ gp.dprint_executing()
+
+ if os.path.isabs(plug_in_dir_path):
+ # plug_in_dir_path begins with a slash so it is an absolute path.
+ candidate_plug_in_dir_path = os.path.normpath(plug_in_dir_path) +\
+ os.sep
+ if not os.path.isdir(candidate_plug_in_dir_path):
+ gp.print_error_report("Plug-in directory path \"" +
+ plug_in_dir_path + "\" does not exist.\n")
+ exit(1)
+ else:
+ # The plug_in_dir_path is actually a simple name (e.g.
+ # "OBMC_Sample")...
+ candidate_plug_in_dir_path = find_plug_in_package(plug_in_dir_path)
+ if candidate_plug_in_dir_path == "":
+ global PATH_LIST
+ gp.print_error_report("Plug-in directory path \"" +
+ plug_in_dir_path + "\" could not be found" +
+ " in any of the following directories:\n" +
+ gp.sprint_var(PATH_LIST))
+ exit(1)
+ # Make sure that this plug-in supports us...
+ supports_file_path = candidate_plug_in_dir_path + "supports_" + mch_class
+ if not os.path.exists(supports_file_path):
+ gp.print_error_report("The following file path could not be" +
+ " found:\n" +
+ gp.sprint_varx("supports_file_path",
+ supports_file_path) +
+ "\nThis file is necessary to indicate that" +
+ " the given plug-in supports the class of" +
+ " machine we are testing, namely \"" +
+ mch_class + "\".\n")
+ exit(1)
+
+ return candidate_plug_in_dir_path
+
+###############################################################################
+
+
+###############################################################################
+def return_integrated_plug_ins(mch_class="obmc"):
+
+ r"""
+ Return a list of integrated plug-ins. Integrated plug-ins are plug-ins
+ which are selected without regard for whether the user has specified them.
+ In other words, they are "integrated" into the program suite. The
+ programmer designates a plug-in as integrated by putting a file named
+ "integrated" into the plug-in package directory.
+
+ Description of arguments:
+ mch_class The class of machine that we are testing
+ (e.g. "op" = "open power", "obmc" = "open
+ bmc", etc).
+ """
+
+ global plug_in_base_path_list
+
+ integrated_plug_ins_list = []
+
+ for plug_in_base_path in plug_in_base_path_list:
+ # Get a list of all plug-in paths that support our mch_class.
+ mch_class_candidate_list = glob.glob(plug_in_base_path +
+ "*/supports_" + mch_class)
+ for candidate_path in mch_class_candidate_list:
+ integrated_plug_in_dir_path = os.path.dirname(candidate_path) +\
+ os.sep
+ integrated_file_path = integrated_plug_in_dir_path + "integrated"
+ if os.path.exists(integrated_file_path):
+ plug_in_name = \
+ os.path.basename(os.path.dirname(candidate_path))
+ if plug_in_name not in integrated_plug_ins_list:
+ # If this plug-in has not already been added to the list...
+ integrated_plug_ins_list.append(plug_in_name)
+
+ return integrated_plug_ins_list
+
+###############################################################################
+
+
+###############################################################################
+def return_plug_in_packages_list(plug_in_dir_paths,
+ mch_class="obmc"):
+
+ r"""
+ Return a list of plug-in packages given the plug_in_dir_paths string.
+ This function calls validate_plug_in_package so it will fail if
+ plug_in_dir_paths contains any invalid plug-ins.
+
+ Description of arguments:
+ plug_in_dir_path The "relative" or absolute path to a plug
+ in package directory.
+ mch_class The class of machine that we are testing
+ (e.g. "op" = "open power", "obmc" = "open
+ bmc", etc).
+ """
+
+ if plug_in_dir_paths != "":
+ plug_in_packages_list = plug_in_dir_paths.split(":")
+ else:
+ plug_in_packages_list = []
+
+ # Get a list of integrated plug-ins (w/o full path names).
+ integrated_plug_ins_list = return_integrated_plug_ins(mch_class)
+ # Put both lists together in plug_in_packages_list with no duplicates.
+ # NOTE: This won't catch duplicates if the caller specifies the full path
+ # name of a native plug-in but that should be rare enough.
+
+ plug_in_packages_list = plug_in_packages_list + integrated_plug_ins_list
+
+ plug_in_packages_list = \
+ list(set([validate_plug_in_package(path, mch_class)
+ for path in plug_in_packages_list]))
+
+ return plug_in_packages_list
+
+###############################################################################