VOL-4805

This script is an inital attempt at capturing and documenting testing
resources for interactive and programatic retrieval.  Command line
arguments and eventual filesystem based detection will hints for a
suite of tests to run to evaluate VOLTHA code changes.

make check: run python unit tests

usage:
  % flog.py --help
  % flog.py --usage
  % flog.py --type regression
  % flog.py --attr olt

Recreate patch: start over
  o approval given by: daf & torsten
  o a cycle of squashing roaches reported by lint checking resulted
    in multiple conflicting patches in gerrit.  Abandon the mess and
    submit a clean patch.
  o Only delta introduced is using the updated standard copyright notice.
  o git merge to get branch and master back in sync.

Change-Id: I24292f2b72d134879939f43a84b31ab153d10fa9
diff --git a/Makefile b/Makefile
old mode 100755
new mode 100644
diff --git a/VERSION b/VERSION
old mode 100755
new mode 100644
diff --git a/requirements.txt b/requirements.txt
old mode 100755
new mode 100644
diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 0000000..863543e
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1,26 @@
+# repo:voltha-system-tests
+
+flog.py
+-------
+This script will be used as a central resource for identifying tests to run
+when sources are changed.  Invoke with arguments and a list of testing
+resources or jenkins jobs to run will be returned.
+
+Unit tests are included for posterity: make check.
+
+Consider the code alpha quality and highly subject to change.  Initial use
+is inteded for interactive use, over time results will evolve to return
+a list of directories and test suites passed to jenkins for consumption
+or better yet logic able to checkout and invoke (~bbsim) tests locally.
+
+Yes hardcoded jenkins URLs in the meta directory are gross but their
+presence will be short lived.
+
+mem_consumption.py
+------------------
+
+sizing.py
+---------
+
+which_deployment_owns_device.sh
+-------------------------------
diff --git a/scripts/flog.py b/scripts/flog.py
new file mode 100755
index 0000000..9fb5fd3
--- /dev/null
+++ b/scripts/flog.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+'''This script is an aggregate for testing resources.'''
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  GLOBALS  [---##
+##-------------------##
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+import pprint
+
+from flog.main        import utils           as main_utils
+from flog.main        import argparse        as main_getopt
+
+# from flog.meta        import onos
+from flog.meta        import voltha
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+def process():
+    '''Perform actions based on command line args.'''
+
+    argv  = main_getopt.get_argv()
+    debug = argv['debug']
+    trace = argv['trace']
+
+    todos = [] 
+
+    ## -----------------------------
+    ## Accumulate tests by attribute
+    ## -----------------------------
+    for attr in argv['attr']:
+        if debug:
+            print('** ATTR: %s' % attr)
+        todos += voltha.Utils().get([attr])
+
+    ## ------------------------
+    ## Accumulate tests by type
+    ## ------------------------
+    for test_type in argv['type']:
+        if debug:
+            print('** TYPE: %s' % test_type)
+        todos += voltha.Utils().get([test_type])
+
+    ## ---------------------------------------
+    ## Append an explicit list of tests to run
+    ## ---------------------------------------
+    for incl in argv['incl']:
+        if debug:
+            print('** INCL: %s' % incl)
+        todos += [incl]
+
+    ## -------------------------------------
+    ## Filter results by substring or config
+    ## -------------------------------------
+    # if len(argv['excl']) > 1:
+    #    for excl in argv['excl']:
+    #        if any([excl in val for val in todos]):
+    #            continue
+    #        
+
+    todos = list(set(todos)) # unique
+
+    # -------------------------------------
+    # Err on the side of testing everything
+    # -------------------------------------
+    if len(todos) == 0: 
+        todos += voltha.Utils().get(['all'])
+       
+    for todo in todos:
+        print(todo)
+
+    return
+
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+def main(argv_raw):
+    '''Here we go.'''
+
+    main_getopt.getopts(argv_raw)
+    process()
+    sys.exit(0)
+
+##----------------##
+##---]  MAIN  [---##
+##----------------##
+if __name__ == "__main__":
+    main(sys.argv[1:]) # NOSONAR
+
+# [EOF]
diff --git a/scripts/flog/__init__.py b/scripts/flog/__init__.py
new file mode 100644
index 0000000..e7b07ea
--- /dev/null
+++ b/scripts/flog/__init__.py
@@ -0,0 +1,30 @@
+# -*- python -*-
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+from pathlib import Path
+
+# pylint: disable=using-constant-test
+if True:
+    # artifical scope enables local var use
+    parent = Path('..').resolve().as_posix()
+    sys.path.insert(0, parent)
+
+# [EOF]
diff --git a/scripts/flog/main/__init__.py b/scripts/flog/main/__init__.py
new file mode 100644
index 0000000..b8a587e
--- /dev/null
+++ b/scripts/flog/main/__init__.py
@@ -0,0 +1,37 @@
+# -*- python -*-
+"""Augment module searchpath and existence indicates directory is a module."""
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+from pathlib import Path
+
+## ---------------------------------------
+## Artificial scope created for local vars
+## ---------------------------------------
+# pylint: disable=invalid-name
+# pylint: disable=using-constant-test
+if True:
+    root = '../..'
+    mod_path = Path(root).resolve().as_posix()
+    if mod_path not in sys.path:
+        sys.path.insert(0, mod_path)
+
+# [EOF]
diff --git a/scripts/flog/main/argparse.py b/scripts/flog/main/argparse.py
new file mode 100644
index 0000000..01cdc4b
--- /dev/null
+++ b/scripts/flog/main/argparse.py
@@ -0,0 +1,181 @@
+# -*- python -*-
+'''A module for parsing script command line arguments.
+
+..seealso: https://docs.python.org/3/library/argparse.html##
+'''
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  GLOBALS  [---##
+##-------------------##
+ARGV      = None
+namespace = None
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import argparse
+
+from flog.main        import utils              as main_utils
+from flog.main        import help            as main_help
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+def get_argv():
+    """Retrieve parsed command line switches.
+
+    ..pre: getopts() was called earlier.
+
+    :return: Parsed command line argument storage
+    :rtype : dict
+    """
+
+    global ARGV
+    global namespace
+
+    if ARGV is None:
+        # Normalize argspace/namespace into a getopt/dictionary
+        # Program wide syntax edits needed: args['foo'] => args.foo
+        arg_dict = {}
+        for arg in vars(namespace):
+            arg_dict[arg] = getattr(namespace, arg)
+        ARGV = arg_dict
+
+    return ARGV
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+def getopts(argv, debug=None) -> None:
+    """Parse command line args, check options and pack into a hashmap
+
+    :param argv: values passed on the command line
+    :param debug: optional flag to enable debug mode
+
+    :return: Digested command line arguments
+    :rtype : dict
+
+    :raises  ValueError
+
+    .. versionadded:: 1.0
+    """
+
+    global namespace
+
+    iam = main_utils.iam()
+
+    if debug is None:
+        debug = False
+
+    parser = argparse.ArgumentParser\
+             (
+                 description = '''Report test dependencies based on selection criteria.'''
+                 # epilog = 'extra-help-text'
+             )
+
+    ## -----------------------------------------------------------------------
+    ## [TEST: categories]
+    ## -----------------------------------------------------------------------
+    parser.add_argument('--attr',
+                        action  = 'append',
+                        default = [],
+                        choices=\
+                        [
+                            'olt',   # optical line termination
+                            #
+                            'onu',   # optical network unit
+                            #
+                            'epon',
+                            'pon',   # passive optical network
+                            'gpon',  # gigabit-pon
+                            'xpon',
+                        ],
+                        help = 'Enable testing by attribute',
+                    )
+
+    ## -----------------------------------------------------------------------
+    ## [TEST:types]
+    ## -----------------------------------------------------------------------
+    parser.add_argument('--type',
+                        action  = 'append',
+                        default = [],
+                        choices=\
+                        [
+                            'burnin',       # profile, stress testing
+                            'integration',  # trigger inter-dependencies
+                            'oink',         # kitchen sink testing
+                            'regression',   # have we broken the renaissance ?
+                            'scale',        #
+                            'system',       #
+                            'smoke',        # quick: 60s > [n]
+                            'standalone',   # dependency-less tests
+                            'suite',        #
+                            'unit',         # module/api/narrow focus.
+                        ],
+                        help = 'Enable testing by category',
+                    )
+
+    ## -----------------------------------------------------------------------
+    ## [FILTER]
+    ## -----------------------------------------------------------------------
+    parser.add_argument('--excl',
+                        action  = 'append',
+                        default = [],
+                        help    = 'FILTER: Probe resources to exclude',
+                    )
+    parser.add_argument('--incl',
+                        action  = 'append',
+                        default = [],
+                        help    = 'FILTER: Probe resources to include',
+                    )
+
+    ## -----------------------------------------------------------------------
+    ## [MODES]
+    ## -----------------------------------------------------------------------
+    parser.add_argument('--debug',
+                        action  = 'store_true',
+                        default = False,
+                        help    = 'Enable debug mode',
+                        )
+
+    parser.add_argument('--trace',
+                        action  = 'append',
+                        default = [],
+                        help    = 'Enable python debugging to trace a named resource.',
+                    )
+
+    parser.add_argument('--usage',
+                        action  = 'store_true',
+                        default = False,
+                        help    = 'Show usage examples',
+                        )
+    
+    parser.add_argument('--version', action='version', version='%(prog)s 1.0')
+
+
+    namespace = parser.parse_args()
+
+    # --------------------------------------------------------------
+    # [TODO] update --usage to accept a value, display context help.
+    # --------------------------------------------------------------
+    if namespace.usage:
+        main_help.usage()
+
+    return
+
+# [EOF]
+                 
diff --git a/scripts/flog/main/help.py b/scripts/flog/main/help.py
new file mode 100644
index 0000000..0141461
--- /dev/null
+++ b/scripts/flog/main/help.py
@@ -0,0 +1,71 @@
+# -*- python -*-
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import __main__
+import os
+import sys
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+def show_examples():
+    '''Display examples of command usage'''
+
+    cmd = os.path.basename(__main__.__file__)
+
+    print('''
+%% %s
+  Run all tests (default)
+
+%% %s --type regression
+  Run only regression tests.
+
+%% %s --type smoke
+  Run a quick battery of tests.
+
+%% %s --type suite --test unit --attr olt --attr onu
+  Run module tests for olt and onu.
+
+%% %s --type regression --attr gpon
+  Run regression tests for gpon logic.
+''' % (cmd, cmd, cmd, cmd, cmd))
+#    print( (cmd) * 5) # syntax ?!?
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+def usage():
+    """Display command arguments and usage
+
+    :param err: Error to display due to invalid arg parsing.
+    :type  err: String
+
+    :param arg: --help* command line argument digested by argparse.py
+    :type  arg: String
+
+    :raises  ValueError
+
+    ...versionadded: 1.1
+    """
+
+    cmd = os.path.basename(__main__.__file__)
+    print("USAGE: %s" % cmd)
+    show_examples()
+    sys.exit(0)
+
+# EOF
diff --git a/scripts/flog/main/test/__init__.py b/scripts/flog/main/test/__init__.py
new file mode 100644
index 0000000..4b4c0b2
--- /dev/null
+++ b/scripts/flog/main/test/__init__.py
@@ -0,0 +1,37 @@
+# -*- python -*-
+"""Augment module searchpath and existence indicates directory is a module."""
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+from pathlib import Path
+
+## ---------------------------------------
+## Artificial scope created for local vars
+## ---------------------------------------
+# pylint: disable=invalid-name
+# pylint: disable=using-constant-test
+if True:
+    root = '../../..'
+    mod_path = Path(root).resolve().as_posix()
+    if mod_path not in sys.path:
+        sys.path.insert(0, mod_path)
+
+# [EOF]
diff --git a/scripts/flog/main/test/test_file_utils.py b/scripts/flog/main/test/test_file_utils.py
new file mode 100644
index 0000000..b583900
--- /dev/null
+++ b/scripts/flog/main/test/test_file_utils.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+'''Unit test for main/utils.py'''
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  GLOBALS  [---##
+##-------------------##
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import unittest
+
+from flog.main         import utils           as main_utils
+
+
+class TestStringMethods(unittest.TestCase):
+
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def test_iam(self):
+
+        iam = main_utils.iam()
+        self.assertIn('test_iam', iam)
+        self.assertTrue(len(iam) > 4)
+
+##----------------##
+##---]  MAIN  [---##
+##----------------##
+if __name__ == "__main__":
+    main()
+
+# [EOF]
diff --git a/scripts/flog/main/utils.py b/scripts/flog/main/utils.py
new file mode 100644
index 0000000..7fd2eeb
--- /dev/null
+++ b/scripts/flog/main/utils.py
@@ -0,0 +1,70 @@
+# -*- python -*-
+## -----------------------------------------------------------------------
+## Intent: This module contains general helper methods
+## -----------------------------------------------------------------------
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+import pprint
+
+## ---------------------------------------------------------------------------
+## ---------------------------------------------------------------------------
+def iam():
+    """Return name of a called method."""
+
+    func_name = sys._getframe(1).f_code.co_name # pylint: disable=protected-access
+    iam       = "%s::%s" % (__name__, func_name)
+    return iam
+
+## -----------------------------------------------------------------------
+## Intent: Display a message then exit with non-zero status.
+##   This method cannot be intercepted by try/except
+## -----------------------------------------------------------------------
+def error(msg, exit_with=None, fatal=None):
+    """Display a message then exit with non-zero status.
+
+    :param msg: Error mesage to display.
+    :type  msg: string
+
+    :param exit_with: Shell exit status.
+    :type  exit_with: int, optional (default=2)
+
+    :param fatal: When true raise an exception.
+    :type  fatal: bool (default=False)
+
+    """
+
+    if exit_with is None:
+        exit_with = 2
+
+    if fatal is None:
+        fatal = false
+
+    if msg:
+        if fatal:
+            raise Exception("ERROR: %s" % msg)
+        else:
+            print("")
+            print("ERROR: %s" % msg)
+
+    sys.exit(exit_with)
+
+# EOF
diff --git a/scripts/flog/meta/README.md b/scripts/flog/meta/README.md
new file mode 100644
index 0000000..01916fd
--- /dev/null
+++ b/scripts/flog/meta/README.md
@@ -0,0 +1,29 @@
+# -----------------------------------------------------------------------
+##  PATCH: olt app
+## ACTION: Build an ONOS image
+# -----------------------------------------------------------------------
+
+## -----------------------------------------------------------------------
+## Default tests
+## -----------------------------------------------------------------------
+   * https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-multi-uni-multiple-olts-test-bbsim/
+
+## -----------------------------------------------------------------------
+## Periodic
+## Regression
+## -----------------------------------------------------------------------
+   * https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-openonu-go-test-bbsim
+   * https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-multiple-olts-test-bbsim
+   * https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-multiple-olts-openonu-go-test-bbsim
+
+## -----------------------------------------------------------------------
+## All tests
+## -----------------------------------------------------------------------
+   * https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/
+
+## -----------------------------------------------------------------------
+## [SEE ALSO]
+## -----------------------------------------------------------------------
+   * https://jira.opencord.org/browse/VOL-4805
+
+# [EOF]
diff --git a/scripts/flog/meta/__init__.py b/scripts/flog/meta/__init__.py
new file mode 100644
index 0000000..b8a587e
--- /dev/null
+++ b/scripts/flog/meta/__init__.py
@@ -0,0 +1,37 @@
+# -*- python -*-
+"""Augment module searchpath and existence indicates directory is a module."""
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+from pathlib import Path
+
+## ---------------------------------------
+## Artificial scope created for local vars
+## ---------------------------------------
+# pylint: disable=invalid-name
+# pylint: disable=using-constant-test
+if True:
+    root = '../..'
+    mod_path = Path(root).resolve().as_posix()
+    if mod_path not in sys.path:
+        sys.path.insert(0, mod_path)
+
+# [EOF]
diff --git a/scripts/flog/meta/all b/scripts/flog/meta/all
new file mode 100644
index 0000000..05d50c4
--- /dev/null
+++ b/scripts/flog/meta/all
@@ -0,0 +1,7 @@
+## ---------------------------------------------------------------------------
+## Default test suites
+## ---------------------------------------------------------------------------
+
+https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/
+
+# [EOF]
diff --git a/scripts/flog/meta/olt b/scripts/flog/meta/olt
new file mode 100644
index 0000000..b56b7ff
--- /dev/null
+++ b/scripts/flog/meta/olt
@@ -0,0 +1,7 @@
+## ---------------------------------------------------------------------------
+## ONOS/OLT Test Suites
+## ---------------------------------------------------------------------------
+
+https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-multi-uni-multiple-olts-test-bbsim
+
+# [EOF]
diff --git a/scripts/flog/meta/regression b/scripts/flog/meta/regression
new file mode 100644
index 0000000..8cf00f8
--- /dev/null
+++ b/scripts/flog/meta/regression
@@ -0,0 +1,9 @@
+## -----------------------------------------------------------------------
+## Regresison tests
+## -----------------------------------------------------------------------
+
+https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-openonu-go-test-bbsim
+https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-multiple-olts-test-bbsim
+https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/job/periodic-voltha-multiple-olts-openonu-go-test-bbsim
+
+# [EOF]
diff --git a/scripts/flog/meta/test/__init__.py b/scripts/flog/meta/test/__init__.py
new file mode 100644
index 0000000..4b4c0b2
--- /dev/null
+++ b/scripts/flog/meta/test/__init__.py
@@ -0,0 +1,37 @@
+# -*- python -*-
+"""Augment module searchpath and existence indicates directory is a module."""
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import sys
+from pathlib import Path
+
+## ---------------------------------------
+## Artificial scope created for local vars
+## ---------------------------------------
+# pylint: disable=invalid-name
+# pylint: disable=using-constant-test
+if True:
+    root = '../../..'
+    mod_path = Path(root).resolve().as_posix()
+    if mod_path not in sys.path:
+        sys.path.insert(0, mod_path)
+
+# [EOF]
diff --git a/scripts/flog/meta/test/test_voltha.py b/scripts/flog/meta/test/test_voltha.py
new file mode 100644
index 0000000..d69f38f
--- /dev/null
+++ b/scripts/flog/meta/test/test_voltha.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+'''Unit test for meta/voltha.py'''
+
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  GLOBALS  [---##
+##-------------------##
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+import unittest
+
+from flog.main         import utils           as main_utils
+from flog.meta         import voltha
+
+
+class TestStringMethods(unittest.TestCase):
+
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def test_by_all(self):
+        '''Verify result of default test lookup.'''
+
+        exp = ['https://jenkins.opencord.org/view/VOLTHA-2.X-Tests/']
+        for arg in ['all', 'invalid']:
+            got = voltha.Utils().get([arg])
+            self.assertCountEqual(got, exp)
+            
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def test_by_olt(self):
+        '''Verify result of test lookup by string arg 'olt'.''' 
+
+        exp = 'periodic-voltha-multi-uni-multiple-olts-test-bbsim'
+        for arg in ['olt']:
+            got = voltha.Utils().get([arg])
+            self.assertEqual(len(got), 1)
+            self.assertIn(exp, got[0])
+
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def test_by_regression(self):
+        '''Verify result of test lookup by string arg 'regression.'''
+
+        for arg in ['regression']:
+            got = voltha.Utils().get([arg])
+            self.assertTrue(len(got) > 2)
+        
+##----------------##
+##---]  MAIN  [---##
+##----------------##
+if __name__ == "__main__":
+    main()
+
+# [EOF]
diff --git a/scripts/flog/meta/voltha.py b/scripts/flog/meta/voltha.py
new file mode 100644
index 0000000..fd0410e
--- /dev/null
+++ b/scripts/flog/meta/voltha.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+'''Return voltha test suites based on criteria.'''
+
+# -----------------------------------------------------------------------#
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+# -----------------------------------------------------------------------
+
+##-------------------##
+##---]  GLOBALS  [---##
+##-------------------##
+
+##-------------------##
+##---]  IMPORTS  [---##
+##-------------------##
+from pathlib           import Path
+from flog.main         import utils        as main_utils
+
+
+class Utils():
+    """ . """
+
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def __init__(self, root=None):
+        """Module constructor
+
+        :param data: A data source (often a certificate) to extract hostnames from.
+        :type  data: arbitrary
+
+        :raises: ValueError
+        """
+
+        return
+
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def filecat(self, name:str) -> list:
+        '''Slurp contents of a config file in the meta/ directory.
+
+        :param name: Config file name to read.
+        :type  name: str
+        
+        :return: Config file contents with comments and whitespace removed.
+        :rtype:  list
+        '''
+        
+        mod_path = Path(__file__).resolve().parent.as_posix()
+        meta = Path(mod_path + '/' + name)
+
+        ans = []
+        with open(meta, mode='r', encoding='utf-8') as stream:
+            for line in stream:
+                fields = line.split('#')
+                val = fields[0].strip()
+                if len(val) > 2:
+                    ans += [val]
+
+        return ans
+    
+    ## -----------------------------------------------------------------------
+    ## -----------------------------------------------------------------------
+    def get(self, args:list) -> list:
+        '''Retrieve a list of tests from a named config file.
+
+        :param args: Config file names to load.
+        :type  args: list[str]
+
+        :return: Value(s) loaded from config files.
+        :rtype:  list
+
+        ..note: Default beahavior will return an exhaustive list of tests
+        ..note: rather than an empty list due to typos.
+        '''
+
+        meta = ['olt', 'regression']
+
+        ans = []
+        for arg in args:
+            if arg in meta:
+                ans += self.filecat(arg)
+            else:
+                ans += self.filecat('all')
+
+        return ans
+
+# [EOF]
diff --git a/scripts/makefile b/scripts/makefile
new file mode 100644
index 0000000..dafd2be
--- /dev/null
+++ b/scripts/makefile
@@ -0,0 +1,27 @@
+# -*- makefile -*-
+# -----------------------------------------------------------------------
+# -----------------------------------------------------------------------
+
+null 	    :=#
+space	    :=$(null) $(null)
+
+PYTHON      ?= /usr/bin/env python
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+all: try
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+test-args += -m unittest
+check test:
+	$(PYTHON) $(test-args) discover -v
+
+## -----------------------------------------------------------------------
+## -----------------------------------------------------------------------
+help:
+	@echo "USAGE: $(MAKE)"
+	@echo "  all"
+	@echo "  test     Invoke available unit tests (find . -name 'test_*.py')"
+
+# [EOF]
diff --git a/scripts/mem_consumption.py b/scripts/mem_consumption.py
index 9d2bea1..199d9b2 100644
--- a/scripts/mem_consumption.py
+++ b/scripts/mem_consumption.py
@@ -1,4 +1,6 @@
-# Copyright 2021-present Open Networking Foundation
+# -*- python -*-
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,6 +13,7 @@
 # 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.
+# -----------------------------------------------------------------------
 
 # This tool collects instantaneous memory usage for voltha pods
 
diff --git a/scripts/sizing.py b/scripts/sizing.py
index 3fb1815..5327f89 100644
--- a/scripts/sizing.py
+++ b/scripts/sizing.py
@@ -1,4 +1,6 @@
-# Copyright 2017-present Open Networking Foundation
+# -*- python -*-
+# -----------------------------------------------------------------------
+# Copyright 2022 Open Networking Foundation (ONF) and the ONF Contributors
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,6 +13,7 @@
 # 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.
+# -----------------------------------------------------------------------
 
 # This tool collects CPU and Memory informations for each container in the VOLTHA stack