aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoropensource-tnbt <sridhar.rao@spirent.com>2020-07-29 17:56:51 +0530
committeropensource-tnbt <sridhar.rao@spirent.com>2020-07-29 18:02:29 +0530
commitd5a010aa2e40cafabf012b096f508b144f9d3d8b (patch)
tree51c734e7c343c5a7dff37b32be1e5a190012c5b0
parent083133e1e619cb0b8bedb08562260ae2d0774553 (diff)
SDV: First commit.
Moving contents from parent cirv to cirv-sdv Remove pycache Signed-off-by: Sridhar K. N. Rao <sridhar.rao@spirent.com> Change-Id: I3744157eb6616591159abe3eca133160d803dda3
-rw-r--r--.gitreview4
-rw-r--r--INFO.yaml62
-rw-r--r--LICENSE13
-rw-r--r--README.md13
-rwxr-xr-xcheck179
-rwxr-xr-xci/build-cirv.sh124
-rw-r--r--pylintrc396
-rw-r--r--requirements.txt17
-rwxr-xr-xsdv/NwLinksValid/__init__.py17
-rw-r--r--sdv/NwLinksValid/nwlinksvalidator.py38
-rwxr-xr-xsdv/SoftwarePostValid/__init__.py19
-rw-r--r--sdv/SoftwarePostValid/swpostvalidator.py42
-rwxr-xr-xsdv/SoftwarePreValid/__init__.py19
-rw-r--r--sdv/SoftwarePreValid/airship.py267
-rw-r--r--sdv/SoftwarePreValid/swprevalidator.py42
-rw-r--r--sdv/__init__.py0
-rw-r--r--sdv/conf/00_common.conf18
-rw-r--r--sdv/conf/01_swprevalid.conf33
-rw-r--r--sdv/conf/02_swpostvalid.conf4
-rw-r--r--sdv/conf/03_nwlinksvalid.conf1
-rw-r--r--sdv/conf/__init__.py265
-rw-r--r--sdv/core/__init__.py19
-rw-r--r--sdv/core/component_factory.py32
-rw-r--r--sdv/core/loader/__init__.py18
-rw-r--r--sdv/core/loader/loader.py129
-rw-r--r--sdv/core/loader/loader_servant.py183
-rw-r--r--sdv/docs/valid.rst28
-rwxr-xr-xsdv/valid147
-rw-r--r--setup.cfg24
-rw-r--r--setup.py29
-rw-r--r--test-requirements.txt8
-rw-r--r--tox.ini25
-rw-r--r--yamllintrc25
33 files changed, 2240 insertions, 0 deletions
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..2252299
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,4 @@
+[gerrit]
+host=gerrit.opnfv.org
+port=29418
+project=cirv-sdv.git
diff --git a/INFO.yaml b/INFO.yaml
new file mode 100644
index 0000000..0156233
--- /dev/null
+++ b/INFO.yaml
@@ -0,0 +1,62 @@
+---
+project: 'Common Infrastructure Realization & Validation (CIRV)'
+project_creation_date: '2019-09-17'
+project_category: 'Integration and Testing'
+lifecycle_state: 'Incubation'
+project_lead: &opnfv_cirv_ptl
+ name: 'Chen Liang'
+ email: 'chenliangyjy@chinamobile.com'
+ company: 'chinamobile.com'
+ id: 'chenliangyjy'
+ timezone: 'CST'
+primary_contact: *opnfv_cirv_ptl
+issue_tracking:
+ type: 'jira'
+ url: 'https://jira.opnfv.org/projects/CIRV'
+ key: 'CIRV'
+mailing_list:
+ type: 'groups.io'
+ url: 'opnfv-tech-discuss@lists.opnfv.org'
+ tag: '[CIRV]'
+realtime_discussion:
+ type: irc
+ server: 'freenode.net'
+ channel: '#opnfv-cirv'
+meetings:
+ - type: 'Zoom'
+ # yamllint disable rule:line-length
+ agenda: 'https://wiki.lfnetworking.org/pages/viewpage.action?pageId=25364128'
+ url: https://zoom.us/j/694881078
+ repeats: 'weekly'
+ time: 'Wed 1300-1400 UTC'
+repositories:
+ - 'cirv'
+committers:
+ - <<: *opnfv_cirv_ptl
+ - name: 'Trevor Cooper'
+ email: 'trevor.cooper@intel.com'
+ company: 'intel.com'
+ id: 'trev'
+ - name: 'Sridhar Rao'
+ email: 'sridhar.rao@spirent.com'
+ company: 'spirent.com'
+ id: 'sridharkn'
+ - name: 'Lincoln Lavoie'
+ email: 'lylavoie@iol.unh.edu'
+ company: 'iol.unh.edu'
+ id: 'lylavoie'
+ - name: 'Fu Qiao'
+ email: 'fuqiao@chinamobile.com'
+ company: 'chinamobile.com'
+ id: 'fuqiao'
+ - name: 'Chen Liang'
+ email: 'chenliangyjy@chinamobile.com'
+ company: 'chinamobile.com'
+ id: 'chenliangyjy'
+tsc:
+ approval: 'http://ircbot.wl.linuxfoundation.org/meetings/opnfv-meeting/2019/opnfv-meeting.2019-09-17-13.01.log.html'
+ changes:
+ - type: 'rename'
+ name: 'CNTT-RI -> CIRV'
+ link: 'http://ircbot.wl.linuxfoundation.org/meetings/opnfv-meeting/2019/opnfv-meeting.2019-10-29-13.00.log.html'
+ # yamllint enable rule:line-length
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3922ffe
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2020 Open Platform for NFV Project, Inc. and its 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c86c618
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+<!---
+This work is licensed under a Creative Commons Attribution 4.0 International License.
+http://creativecommons.org/licenses/by/4.0
+-->
+
+# CIRV - Common Infrastructure Realization & Validation
+
+Documentation for this project is contained under the **./docs** directory.
+Additional information about CIRV project are available at [project wiki].
+
+---
+
+[project wiki]: https://wiki.opnfv.org/display/CIRV
diff --git a/check b/check
new file mode 100755
index 0000000..7e5884b
--- /dev/null
+++ b/check
@@ -0,0 +1,179 @@
+#!/bin/bash
+
+# Copyright 2017 Intel Corporation.
+#
+# 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.
+
+# CIRV code checker
+
+PYLINT="pylint"
+PYLINT_RC='pylintrc'
+PYLINT_RATING_GATE="10"
+PYLINT_RATING_MIN=$PYLINT_RATING_GATE
+FILE_REGEX="(^valid|\.py)$"
+FILE_LIST="/tmp/cirv_check_list.txt"
+BC=`which bc`
+
+# print usage if requested
+function usage() {
+ cat <<EOM
+Usage: $0 [TARGET]...
+
+Performs code check for defined TARGETs. Target can be file or directory.
+In case that directory is specified, then it will be searched recursively
+for all python files.
+If TARGET is not specified, then all python files from current CIRV
+repository will be checked.
+File will pass check if its pylint rating is greater or equal to $PYLINT_RATING_GATE.
+Otherwise gained pylint rating will be displayed.
+
+
+ -h, --help Script usage
+ -b, --black Suppress colours. Output will be black&white.
+ -m, --modified Script will check python files, which have
+ been modified within current repository.
+
+Examples:
+ ./check
+
+ Check all python files in current CIRV repository
+
+ ./check sdv/valid
+
+ Check just one file.
+
+ ./check -m
+
+ Check all modified files in current repository
+
+ ./check sdv
+
+ Check all python files in given directories
+
+EOM
+}
+
+# compare pylint result with predefined gate
+function rating_is_ok() {
+ # bc is not part of basic Centos installation
+ # so let us check if it is available
+ if [ "x$BC" == "x" ] ; then
+ # no bc, so do integer comparison only
+ int_rating=`echo $1 | sed -e 's/\..*$//'`
+ int_rating_min=`echo $PYLINT_RATING_MIN | sed -e 's/\..*$//'`
+ [ $int_rating -lt $int_rating_min ] && PYLINT_RATING_MIN=$int_rating
+ if [ $int_rating -lt $PYLINT_RATING_GATE ] ; then
+ return 1
+ else
+ return 0
+ fi
+ else
+ if (( $(echo "$1<$PYLINT_RATING_MIN" | bc -l) )) ; then
+ PYLINT_RATING_MIN=$1
+ fi
+ if (( $(echo "$1<$PYLINT_RATING_GATE" | bc -l) )) ; then
+ return 1
+ else
+ return 0
+ fi
+ fi
+}
+
+##### MAIN #####
+# check if help is requested
+if [ "x$1" == "x-h" -o "x$1" == "x--help" ] ; then
+ usage
+ exit 0
+fi
+
+# set colours
+if [ "x$1" == "x-b" -o "x$1" == "x--black" ] ; then
+ shift
+ RED=""
+ GREEN=""
+ BLACK=""
+else
+ RED="\e[31m"
+ GREEN="\e[32m"
+ BLACK="\e[0m"
+fi
+
+
+# check if pylint is available
+if ! which $PYLINT &>/dev/null ; then
+ echo "$PYLINT is not available, thus check can't be executed"
+ exit 1
+fi
+
+# check if we were run within cirv directory
+if [ ! -f INFO.yaml 2> /dev/null ] ; then
+ echo "`basename $0` must be run from root directory"
+ exit 2
+fi
+
+# get list of files to be checked
+rm $FILE_LIST &> /dev/null
+if [ "x$1" == "x-m" -o "x$1" == "x--modified" ] ; then
+ # check of modified files requested
+ git status --porcelain | cut -b4- | egrep -i "${FILE_REGEX}" | sort > $FILE_LIST
+elif [ "x$*" == "x" ] ; then
+ # list is empty, check all python files
+ git ls-tree --name-only -r HEAD | egrep -i "${FILE_REGEX}" | sort > $FILE_LIST
+else
+ for item in $* ; do
+ if [ -d $item ] ; then
+ git ls-tree --name-only -r HEAD $item | egrep -i "${FILE_REGEX}" | sort >> $FILE_LIST
+ elif [ -f $item ] ; then
+ echo $item >> $FILE_LIST
+ else
+ echo "$item doesn't exist, thus check was aborted"
+ exit 3
+ fi
+ done
+fi
+
+# check if there is anything to check
+echo "Execution of pylint checks:"
+if [ -s $FILE_LIST ] ; then
+ for pyfile in `cat $FILE_LIST | sort` ; do
+ # get base name
+ pyfile_basename="'"`basename $pyfile .py`"'"
+ # run pylint and extract final rating
+ output=`$PYLINT --rcfile $PYLINT_RC $pyfile 2>/dev/null`
+ rating=`echo -e $output | tail -n3 | grep rated | sed -e 's/^.*rated at \(-\?[0-9.]*\).*$/\1/'`
+ # evaluate and display aquired rating
+ if [ "x$rating" == "x" ] ; then
+ # rating is not available for files without python statements
+ printf " %-70s %-6s\n" $pyfile "NA"
+ elif rating_is_ok $rating ; then
+ printf " %-70s ${GREEN}%-6s${BLACK}\n" $pyfile "OK"
+ else
+ echo -e "$output" | awk '/^\*+ Module|^[A-Z]\:/'
+ printf " %-70s ${RED}%-6s${BLACK}\n" $pyfile $rating
+ fi
+ done
+else
+ echo "Nothing to check."
+ exit 4
+fi
+
+# clean up
+rm $FILE_LIST &> /dev/null
+
+if [ "$PYLINT_RATING_MIN" != "$PYLINT_RATING_GATE" ] ; then
+ echo -e "Pylint check has failed. All files must have score ${PYLINT_RATING_GATE}.\n"
+ exit 1
+else
+ exit 0
+fi
+##### MAIN end #####
diff --git a/ci/build-cirv.sh b/ci/build-cirv.sh
new file mode 100755
index 0000000..e57b758
--- /dev/null
+++ b/ci/build-cirv.sh
@@ -0,0 +1,124 @@
+#!/bin/bash
+#
+# Copyright 2020-2018 Spirent Communications, Intel Corporation., Tieto
+#
+# 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.
+
+# CIRV build execution script
+
+# Usage:
+# build-cirv.sh job_type
+# where job_type is one of "verify", "merge", "daily"
+# Version-1: 'verify'
+
+#
+# exit codes
+#
+
+EXIT=0
+EXIT_SANITY_FAILED=1
+EXIT_PYLINT_FAILED=2
+
+#
+# configuration
+#
+
+SWV_BIN="./sdv/valid"
+LOG_FILE_PREFIX="/tmp/cirv_build"
+DATE=$(date -u +"%Y-%m-%d_%H-%M-%S")
+BRANCH=${GIT_BRANCH##*/}
+CIRVENV_DIR="$HOME/cirvenv"
+# WORKSPACE="./"
+
+#
+# main
+#
+
+echo
+
+# enter workspace dir
+cd $WORKSPACE
+
+
+# create virtualenv if needed
+if [ ! -e $CIRVENV_DIR ] ; then
+ echo "Create CIRV environment"
+ echo "========================="
+ virtualenv --python=python3 "$CIRVENV_DIR"
+ echo
+fi
+
+# acivate and update virtualenv
+echo "Update CIRV environment"
+echo "========================="
+source "$CIRVENV_DIR"/bin/activate
+pip install -r ./requirements.txt
+echo
+
+
+# execute pylint to check code quality
+function execute_cirv_pylint_check {
+ if ! ./check -b ; then
+ EXIT=$EXIT_PYLINT_FAILED
+ fi
+}
+
+# verify basic cirv functionality
+function execute_cirv_sanity {
+ DATE_SUFFIX=$(date -u +"%Y-%m-%d_%H-%M-%S")
+ LOG_FILE="${LOG_FILE_PREFIX}_sanity_${DATE_SUFFIX}.log"
+ echo "Execution of CIRV sanity checks:"
+ for PARAM in '--version' '--help'; do
+ echo -e "------------------------------------------------" >> $LOG_FILE
+ echo "$SWV_BIN $PARAM " >> $LOG_FILE
+ echo -e "------------------------------------------------" >> $LOG_FILE
+ $SWV_BIN $PARAM &>> $LOG_FILE
+ if $SWV_BIN $PARAM &>> $LOG_FILE ; then
+ printf " %-70s %-6s\n" "$SWV_BIN $PARAM" "OK"
+ else
+ printf " %-70s %-6s\n" "$SWV_BIN $PARAM" "FAILED"
+ EXIT=$EXIT_SANITY_TC_FAILED
+ fi
+ echo >> $LOG_FILE
+ done
+}
+
+# execute job based on passed parameter
+case $1 in
+ "verify")
+ echo "================="
+ echo "CIRV verify job"
+ echo "================="
+
+ execute_cirv_pylint_check
+ execute_cirv_sanity
+
+ exit $EXIT
+ ;;
+ "merge")
+ echo "================"
+ echo "CIRV merge job"
+ echo "================"
+
+ exit $EXIT
+ ;;
+ *)
+ echo "================"
+ echo "CIRV daily job"
+ echo "================"
+
+ exit $EXIT
+ ;;
+esac
+
+exit $EXIT
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 0000000..4900d67
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,396 @@
+# Copyright 2015-2020 Intel Corporation.
+#
+# 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.
+
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS,docs,conf
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=1
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=E1602,E1603,E1601,E1606,E1607,E1604,E1605,E1608,W0401,W1604,W1605,W1606,W1607,W1601,W1602,W1603,W1622,W1623,W1620,W1621,W1608,W1609,W1624,W1625,W1618,W1626,W1627,I0021,I0020,W0704,R0903,W1613,W1638,W1611,W1610,W1617,W1616,W1615,W1614,W1630,W1619,W1632,W1635,W1634,W1637,W1636,W1639,W1612,W1628,W1633,W1629,I0011,W1640,R1705
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=filter,apply,file
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=yes
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,35}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=__.*__
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=120
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis
+ignored-modules=
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=6
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=25
+
+# Maximum number of return / yield for function / method body
+max-returns=10
+
+# Maximum number of branch for function / method body
+max-branches=25
+
+# Maximum number of statements in function / method body
+max-statements=70
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=15
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..7d0c0ff
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,17 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+# Copyright (c) 2020 Spirent Communications
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+PyYAML # MIT
+urllib3 # MIT
+GitPython<2.1.12;python_version<'3.0' # BSD License (3 clause)
+GitPython;python_version>='3.0' # BSD License (3 clause)
+requests!=2.20.0 # Apache-2.0
+netaddr # BSD
+openpyxl
diff --git a/sdv/NwLinksValid/__init__.py b/sdv/NwLinksValid/__init__.py
new file mode 100755
index 0000000..99456db
--- /dev/null
+++ b/sdv/NwLinksValid/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+"""Nework-Links Validator interface and helpers.
+"""
+
+# flake8: noqa
+from NwLinksValid.nwlinksvalidator import *
diff --git a/sdv/NwLinksValid/nwlinksvalidator.py b/sdv/NwLinksValid/nwlinksvalidator.py
new file mode 100644
index 0000000..5e06590
--- /dev/null
+++ b/sdv/NwLinksValid/nwlinksvalidator.py
@@ -0,0 +1,38 @@
+# Copyright 2020 Spirent Communications.
+#
+# 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.
+
+"""
+Abstract class for N/W Lnks Prevalidations.
+Implementors, please inherit from this class.
+"""
+
+
+class INwLinksValidator():
+ """ Model for a Links Validator """
+ def __init__(self):
+ """ Initialization of the Interface """
+ self._default_nwlinks_validation = None
+
+ @property
+ def validation_nwlinks_defaults(self):
+ """ Default Validation values """
+ return True
+
+ def validate_compute_node_links(self):
+ """ Validating Compute Node Links"""
+ raise NotImplementedError('Please call an implementation.')
+
+ def validate_control_node_links(self):
+ """ Validating Controller Node Links"""
+ raise NotImplementedError('Please call an implementation.')
diff --git a/sdv/SoftwarePostValid/__init__.py b/sdv/SoftwarePostValid/__init__.py
new file mode 100755
index 0000000..0a964b6
--- /dev/null
+++ b/sdv/SoftwarePostValid/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""Sw Validator interface and helpers.
+"""
+
+# flake8: noqa
+from SoftwarePostValid.swpostvalidator import *
diff --git a/sdv/SoftwarePostValid/swpostvalidator.py b/sdv/SoftwarePostValid/swpostvalidator.py
new file mode 100644
index 0000000..4776123
--- /dev/null
+++ b/sdv/SoftwarePostValid/swpostvalidator.py
@@ -0,0 +1,42 @@
+# Copyright 2020 Spirent Communications.
+#
+# 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.
+
+"""
+Abstract class for Software Postvalidations.
+Implementors, please inherit from this class.
+"""
+
+
+class ISwPostValidator():
+ """ Model for a Sw Validator """
+ def __init__(self):
+ """ Initialization of the Interface """
+ self._default_swpost_validation = None
+
+ @property
+ def validation_swpost_defaults(self):
+ """ Default Validation values """
+ return True
+
+ def validate_configuration_mandatory(self):
+ """
+ Validating Mandatory Configuration
+ """
+ raise NotImplementedError('Please call an implementation.')
+
+ def validate_configuration_optional(self):
+ """
+ Validating Optional Configuration
+ """
+ raise NotImplementedError('Please call an implementation.')
diff --git a/sdv/SoftwarePreValid/__init__.py b/sdv/SoftwarePreValid/__init__.py
new file mode 100755
index 0000000..8307b66
--- /dev/null
+++ b/sdv/SoftwarePreValid/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2020 Spirent Communications.
+#
+# 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.
+
+"""Sw Validator interface and helpers.
+"""
+
+# flake8: noqa
+from SoftwarePreValid.swprevalidator import *
diff --git a/sdv/SoftwarePreValid/airship.py b/sdv/SoftwarePreValid/airship.py
new file mode 100644
index 0000000..bd93aa2
--- /dev/null
+++ b/sdv/SoftwarePreValid/airship.py
@@ -0,0 +1,267 @@
+# Copyright 2020 Spirent Communications.
+#
+# 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.
+
+"""
+Airship implementation of Software Predeployment Validation
+"""
+
+import os
+import shutil
+from pathlib import Path
+import git
+import urllib3
+import yaml
+from conf import settings
+from SoftwarePreValid import swprevalidator
+
+
+def check_link(link):
+ """
+ Function the check the availability of Hyperlinks
+ """
+ timeout = urllib3.util.Timeout(connect=5.0, read=7.0)
+ http = urllib3.PoolManager(timeout=timeout)
+ try:
+ http.request('HEAD', link)
+ except urllib3.exceptions.LocationValueError as err:
+ print(err.args)
+ return False
+ except urllib3.exceptions.MaxRetryError as err:
+ print(err.args)
+ return False
+ except urllib3.exceptions.RequestError as err:
+ print(err.args)
+ return False
+ except urllib3.exceptions.ConnectTimeoutError as err:
+ print(err.args)
+ return False
+ except urllib3.exceptions.PoolError as err:
+ print(err.args)
+ return False
+ except urllib3.exceptions.HTTPError as err:
+ print(err.args)
+ return False
+ return True
+
+
+class Airship(swprevalidator.ISwPreValidator):
+ """
+ Ariship Sw Validation
+ """
+ def __init__(self):
+ """ Airship class constructor """
+ super().__init__()
+ self.url = settings.getValue('AIRSHIP_MANIFEST_URL')
+ self.branch = settings.getValue('AIRSHIP_MANIFEST_BRANCH')
+ self.dl_path = settings.getValue('AIRSHIP_MANIFEST_DOWNLOAD_PATH')
+ self.site_name = settings.getValue('AIRSHIP_MANIFEST_SITE_NAME')
+ self.manifest = None
+ self.dirpath = Path(self.dl_path, 'airship')
+ self.tmdirpath = Path(self.dl_path, 'treasuremap')
+ self.locations = []
+
+ def clone_repo(self):
+ """
+ Cloning the repos
+ """
+ git.Repo.clone_from(self.url,
+ self.dirpath,
+ branch=self.branch)
+ git.Repo.clone_from('https://github.com/airshipit/treasuremap',
+ self.tmdirpath,
+ branch=settings.getValue(
+ 'AIRSHIP_TREASUREMAP_VERSION'))
+
+ def cleanup_manifest(self):
+ """
+ Remove existing manifests
+ """
+ # Next Remove any manifest files, if it exists
+ if self.dirpath.exists() and self.dirpath.is_dir():
+ shutil.rmtree(self.dirpath)
+ if self.tmdirpath.exists() and self.tmdirpath.is_dir():
+ shutil.rmtree(self.tmdirpath)
+
+ def manifest_exists_locally(self):
+ """
+ Check if manifests exists locally
+ """
+ if self.dirpath.exists() and self.dirpath.is_dir():
+ return True
+ return False
+
+ def validate_hyperlinks(self):
+ """
+ Hyperlink Validation
+ """
+ self.cleanup_manifest()
+ # Next, clone the repo to the provided path.
+ self.clone_repo()
+
+ if self.dirpath.exists() and self.dirpath.is_dir():
+ # Get the file(s) where links are defined.
+ self.find_locations(
+ os.path.join(self.dirpath, 'type',
+ 'cntt', 'software',
+ 'config', 'versions.yaml'))
+ for location in self.locations:
+ if check_link(location):
+ print("The Link: %s is VALID" % (location))
+ else:
+ print("The Link: %s is INVALID" % (location))
+
+ # pylint: disable=consider-using-enumerate
+ def find_locations(self, yamlfile):
+ """
+ Find all the hyperlinks in the manifests
+ """
+ with open(yamlfile, 'r') as filep:
+ lines = filep.readlines()
+ for index in range(len(lines)):
+ line = lines[index].strip()
+ if line.startswith('location:'):
+ link = line.split(":", 1)[1]
+ if "opendev" in link:
+ if ((len(lines) > index+1) and
+ (lines[index+1].strip().startswith(
+ 'reference:'))):
+ ref = lines[index+1].split(":", 1)[1]
+ link = link + '/commit/' + ref.strip()
+ if link.strip() not in self.locations:
+ print(link)
+ self.locations.append(link.strip())
+ if 'docker.' in line:
+ link = line.split(":", 1)[1]
+ link = link.replace('"', '')
+ parts = link.split('/')
+ if len(parts) == 3:
+ link = ('https://index.' +
+ parts[0].strip() +
+ '/v1/repositories/' +
+ parts[1] + '/' + parts[2].split(':')[0] +
+ '/tags/' + parts[2].split(':')[-1])
+ if link.strip() not in self.locations:
+ print(link)
+ self.locations.append(link.strip())
+
+ # pylint: disable=too-many-nested-blocks, too-many-boolean-expressions
+ def validate_configuration_mandatory(self):
+ """
+ Configuration checking of mandatory parameters
+ """
+ if not self.manifest_exists_locally():
+ self.clone_repo()
+ # We will perform validation one-by-one:
+ # The Operating System Flavor
+ os_done = False
+ os_filename = os.path.join(self.tmdirpath,
+ 'global',
+ 'software',
+ 'charts',
+ 'ucp',
+ 'drydock',
+ 'maas.yaml')
+ with open(os_filename, 'r') as osref:
+ osfiles = yaml.load_all(osref, Loader=yaml.FullLoader)
+ for osf in osfiles:
+ if ('data' in osf and
+ 'values' in osf['data'] and
+ 'conf' in osf['data']['values'] and
+ 'maas' in osf['data']['values']['conf'] and
+ 'images' in osf['data']['values']['conf']['maas'] and
+ ('default_os' in
+ osf['data']['values']['conf']['maas']['images'])):
+ if (settings.getValue('OPERATING_SYSTEM') in
+ osf['data']['values']['conf']['maas']['images'][
+ 'default_os']):
+ print('Operating System is VALID')
+ os_done = True
+ if not os_done:
+ print("Operating System is INVALID")
+
+ filesdir = os.path.join(self.dirpath,
+ 'site',
+ self.site_name,
+ 'profiles',
+ 'host')
+ hostprofile = None
+ os_ver_done = False
+ if os.path.isdir(filesdir):
+ for filename in os.listdir(filesdir):
+ filename = os.path.join(filesdir, filename)
+ with open(filename, 'r') as fileref:
+ hostprofile = yaml.load(fileref, Loader=yaml.FullLoader)
+ if 'data' in hostprofile:
+ if 'platform' in hostprofile['data']:
+ if 'image' in hostprofile['data']['platform']:
+ if (hostprofile['data']['platform']['image'] in
+ settings.getValue('OS_VERSION_NAME')):
+ print('Operating System Version is VALID')
+ os_ver_done = True
+ break
+ if not os_ver_done:
+ print("Operating System Version is INVALID")
+ # Virtualization - Hugepages and CPU Isolation
+ hugepages_size_done = False
+ hugepages_count_done = False
+ filesdir = os.path.join(self.dirpath,
+ 'type',
+ 'cntt',
+ 'profiles',
+ 'hardware')
+ if os.path.isdir(filesdir):
+ for filename in os.listdir(filesdir):
+ filename = os.path.join(filesdir, filename)
+ with open(filename, 'r') as fileref:
+ hwprofile = yaml.load(fileref, Loader=yaml.FullLoader)
+ if ('data' in hwprofile and
+ 'hugepages' in hwprofile['data'] and
+ 'dpdk' in hwprofile['data']['hugepages']):
+ if ('size' in hwprofile['data']['hugepages']['dpdk'] and
+ (settings.getValue('HUGEPAGES_SIZE') in
+ hwprofile['data']['hugepages']['dpdk']['size'])):
+ print('Hugepages Size is VALID')
+ else:
+ print('Hugepages Size is INVALID')
+ hugepages_size_done = True
+ if ('count' in hwprofile['data']['hugepages']['dpdk'] and
+ (settings.getValue('HUGEPAGES_COUNT') ==
+ hwprofile['data']['hugepages']['dpdk']['count'])):
+ print('Hugepages COUNT is VALID')
+ else:
+ print('Hugepages COUNT is INVALID')
+ hugepages_count_done = True
+ if hugepages_size_done and hugepages_count_done:
+ break
+
+ # Virtual Switch - Switch and Configuration
+ # Openstack-Version
+ filename = os.path.join(self.tmdirpath,
+ 'global',
+ 'software',
+ 'config',
+ 'versions.yaml')
+ if os.path.exists(filename):
+ if settings.getValue('OPENSTACK_VERSION') in open(filename).read():
+ print('Openstack Version is valid')
+ else:
+ print('Openstack version if INVALID')
+ # Openstack Services
+ # Bootstrap
+
+ def validate_configuration_optional(self):
+ """
+ Validate Optional COnfigurations
+ """
+ return False
diff --git a/sdv/SoftwarePreValid/swprevalidator.py b/sdv/SoftwarePreValid/swprevalidator.py
new file mode 100644
index 0000000..bef141b
--- /dev/null
+++ b/sdv/SoftwarePreValid/swprevalidator.py
@@ -0,0 +1,42 @@
+# Copyright 2020 Spirent Communications.
+#
+# 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.
+
+"""
+Abstract class for Software Prevalidations.
+Implementors, please inherit from this class.
+"""
+
+
+class ISwPreValidator():
+ """ Model for a Sw Validator """
+ def __init__(self):
+ """ Initialization of the Interface """
+ self._default_swpre_validation = None
+
+ @property
+ def validation_swpre_defaults(self):
+ """ Default Validation values """
+ return True
+
+ def validate_hyperlinks(self):
+ """ Validate Hyperlinks"""
+ raise NotImplementedError('Please call an implementation.')
+
+ def validate_configuration_mandatory(self):
+ """ Validate Mandatory Configurations """
+ raise NotImplementedError('Please call an implementation.')
+
+ def validate_configuration_optional(self):
+ """ Validate Optional Configurations """
+ raise NotImplementedError('Please call an implementation.')
diff --git a/sdv/__init__.py b/sdv/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdv/__init__.py
diff --git a/sdv/conf/00_common.conf b/sdv/conf/00_common.conf
new file mode 100644
index 0000000..5326ecb
--- /dev/null
+++ b/sdv/conf/00_common.conf
@@ -0,0 +1,18 @@
+import os
+
+# default log output directory for all logs
+LOG_DIR = '/tmp'
+
+# default log for all "small" executables
+LOG_FILE_DEFAULT = 'valid-overall.log'
+
+ROOT_DIR = os.path.normpath(os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), '../'))
+SW_PRE_VALID_DIR = os.path.join(ROOT_DIR, 'SoftwarePreValid')
+SW_POST_VALID_DIR = os.path.join(ROOT_DIR, 'SoftwarePostValid')
+NW_LINKS_VALID_DIR = os.path.join(ROOT_DIR, 'NwLinksValid')
+
+# 'debug', 'info', 'warning', 'error', 'critical'
+VERBOSITY = 'warning'
+
+EXCLUDE_MODULES = ['']
diff --git a/sdv/conf/01_swprevalid.conf b/sdv/conf/01_swprevalid.conf
new file mode 100644
index 0000000..46043ce
--- /dev/null
+++ b/sdv/conf/01_swprevalid.conf
@@ -0,0 +1,33 @@
+
+# Modify this value to any Installer projects that have
+# manifests (templates and configuration files)
+SW_PRE_VALIDATOR = 'Airship'
+
+# Mandatory Requirements [ Configuration Check ]
+OPENSTACK_VERSION = 'ocata'
+OPERATING_SYSTEM = 'centos'
+OS_VERSION_NAME = 'xenial'
+HUGEPAGES_SIZE = '1G'
+HUGEPAGES_COUNT = 32
+OPENSTACK_CUSTOM_SERVICE_LIST = ['barbican', 'sahara']
+VIRTUAL_SWITCH_COMPUTE = 'ovs'
+VIRTUAL_SWITCH_VERSION = '2.9.2'
+BOOTSTRAP_PROTOCOL = 'pxe'
+CPU_ISOLATION = '2-19,22-39'
+
+# Airship Specific configurations.
+AIRSHIP_MANIFEST_URL = 'https://gerrit.opnfv.org/gerrit/airship'
+AIRSHIP_MANIFEST_BRANCH = 'master'
+AIRSHIP_MANIFEST_DOWNLOAD_PATH = '/tmp'
+AIRSHIP_MANIFEST_SITE_NAME = 'intel-pod10'
+AIRSHIP_TREASUREMAP_VERSION = 'v1.7'
+
+
+# Optional Requirements [Could be Installer Specific ]
+OVS_DPDK_ARGUMENTS = 'test'
+OVERCLOUD_LOGGING_CLIENT = 'fluentd'
+OVERCLOUD_MONITORING_CLIENT = 'collectd'
+LMA_SERVER_MONITORING = 'prometheus'
+LMA_SERVER_LOGGING = 'efk'
+OPENSTACK_CONTAINER_ORCHESTRATION = 'kubernetes'
+AIRSHIP_MANIFEST_VERSION = 1.7
diff --git a/sdv/conf/02_swpostvalid.conf b/sdv/conf/02_swpostvalid.conf
new file mode 100644
index 0000000..1ed9279
--- /dev/null
+++ b/sdv/conf/02_swpostvalid.conf
@@ -0,0 +1,4 @@
+
+# Modify this value to any Installer projects that have
+# manifests (templates and configuration files)
+SW_POST_VALIDATOR = 'Airship'
diff --git a/sdv/conf/03_nwlinksvalid.conf b/sdv/conf/03_nwlinksvalid.conf
new file mode 100644
index 0000000..6e83066
--- /dev/null
+++ b/sdv/conf/03_nwlinksvalid.conf
@@ -0,0 +1 @@
+NW_LINKS_VALIDATOR = 'lldpd'
diff --git a/sdv/conf/__init__.py b/sdv/conf/__init__.py
new file mode 100644
index 0000000..ef97aa7
--- /dev/null
+++ b/sdv/conf/__init__.py
@@ -0,0 +1,265 @@
+# Copyright 2015-2017 Intel Corporation.
+#
+# 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.
+
+"""Settings and configuration handlers.
+
+Settings will be loaded from several .conf files
+and any user provided settings file.
+"""
+
+# pylint: disable=invalid-name
+
+import copy
+import os
+import re
+import logging
+import pprint
+
+_LOGGER = logging.getLogger(__name__)
+
+# regex to parse configuration macros from 04_vnf.conf
+# it will select all patterns starting with # sign
+# and returns macro parameters and step
+# examples of valid macros:
+# #VMINDEX
+# #MAC(AA:BB:CC:DD:EE:FF) or #MAC(AA:BB:CC:DD:EE:FF,2)
+# #IP(192.168.1.2) or #IP(192.168.1.2,2)
+# #EVAL(2*#VMINDEX)
+_PARSE_PATTERN = r'(#[A-Z]+)(\(([^(),]+)(,([0-9]+))?\))?'
+
+
+class Settings(object):
+ """Holding class for settings.
+ """
+ def __init__(self):
+ pass
+
+ def _eval_param(self, param):
+ # pylint: disable=invalid-name
+ """ Helper function for expansion of references to 'valid' parameters
+ """
+ if isinstance(param, str):
+ # evaluate every #PARAM reference inside parameter itself
+ macros = re.findall(
+ r'#PARAM\((([\w\-]+)(\[[\w\[\]\-\'\"]+\])*)\)',
+ param)
+ if macros:
+ for macro in macros:
+ # pylint: disable=eval-used
+ try:
+ tmp_val = str(
+ eval("self.getValue('{}'){}".format(macro[1],
+ macro[2])))
+ param = param.replace('#PARAM({})'.format(macro[0]),
+ tmp_val)
+ # silently ignore that option required by
+ # PARAM macro can't be evaluated;
+ # It is possible, that referred parameter
+ # will be constructed during runtime
+ # and re-read later.
+ except IndexError:
+ pass
+ except AttributeError:
+ pass
+ return param
+ elif isinstance(param, (list, tuple)):
+ tmp_list = []
+ for item in param:
+ tmp_list.append(self._eval_param(item))
+ return tmp_list
+ elif isinstance(param, dict):
+ tmp_dict = {}
+ for (key, value) in param.items():
+ tmp_dict[key] = self._eval_param(value)
+ return tmp_dict
+ else:
+ return param
+
+ def getValue(self, attr):
+ """Return a settings item value
+ """
+ if attr in self.__dict__:
+ if attr == 'TEST_PARAMS':
+ return getattr(self, attr)
+ else:
+ master_value = getattr(self, attr)
+ return self._eval_param(master_value)
+ else:
+ raise AttributeError("%r object has no attribute %r" %
+ (self.__class__, attr))
+
+ def __setattr__(self, name, value):
+ """Set a value
+ """
+ # skip non-settings. this should exclude built-ins amongst others
+ if not name.isupper():
+ return
+
+ # we can assume all uppercase keys are valid settings
+ super(Settings, self).__setattr__(name, value)
+
+ def setValue(self, name, value):
+ """Set a value
+ """
+ if name is not None and value is not None:
+ super(Settings, self).__setattr__(name, value)
+
+ def load_from_file(self, path):
+ """Update ``settings`` with values found in module at ``path``.
+ """
+ import imp
+
+ custom_settings = imp.load_source('custom_settings', path)
+
+ for key in dir(custom_settings):
+ if getattr(custom_settings, key) is not None:
+ setattr(self, key, getattr(custom_settings, key))
+
+ def load_from_dir(self, dir_path):
+ """Update ``settings`` with contents of the .conf files at ``path``.
+
+ Each file must be named Nfilename.conf, where N is a single or
+ multi-digit decimal number. The files are loaded in ascending order of
+ N - so if a configuration item exists in more that one file the setting
+ in the file with the largest value of N takes precedence.
+
+ :param dir_path: The full path to the dir from which to load the .conf
+ files.
+
+ :returns: None
+ """
+ regex = re.compile(
+ "^(?P<digit_part>[0-9]+)(?P<alfa_part>[a-z]?)_.*.conf$")
+
+ def get_prefix(filename):
+ """
+ Provide a suitable function for sort's key arg
+ """
+ match_object = regex.search(os.path.basename(filename))
+ return [int(match_object.group('digit_part')),
+ match_object.group('alfa_part')]
+
+ # get full file path to all files & dirs in dir_path
+ file_paths = os.listdir(dir_path)
+ file_paths = [os.path.join(dir_path, x) for x in file_paths]
+
+ # filter to get only those that are a files, with a leading
+ # digit and end in '.conf'
+ file_paths = [x for x in file_paths if os.path.isfile(x) and
+ regex.search(os.path.basename(x))]
+
+ # sort ascending on the leading digits and afla (e.g. 03_, 05a_)
+ file_paths.sort(key=get_prefix)
+
+ # load settings from each file in turn
+ for filepath in file_paths:
+ self.load_from_file(filepath)
+
+ def load_from_dict(self, conf):
+ """
+ Update ``settings`` with values found in ``conf``.
+
+ Unlike the other loaders, this is case insensitive.
+ """
+ for key in conf:
+ if conf[key] is not None:
+ if isinstance(conf[key], dict):
+ # recursively update dict items, e.g. TEST_PARAMS
+ setattr(self, key.upper(),
+ merge_spec(getattr(self, key.upper()), conf[key]))
+ else:
+ setattr(self, key.upper(), conf[key])
+
+ def restore_from_dict(self, conf):
+ """
+ Restore ``settings`` with values found in ``conf``.
+
+ Method will drop all configuration options and restore their
+ values from conf dictionary
+ """
+ self.__dict__.clear()
+ tmp_conf = copy.deepcopy(conf)
+ for key in tmp_conf:
+ self.setValue(key, tmp_conf[key])
+
+ def load_from_env(self):
+ """
+ Update ``settings`` with values found in the environment.
+ """
+ for key in os.environ:
+ setattr(self, key, os.environ[key])
+
+ def __str__(self):
+ """Provide settings as a human-readable string.
+
+ This can be useful for debug.
+
+ Returns:
+ A human-readable string.
+ """
+ tmp_dict = {}
+ for key in self.__dict__:
+ tmp_dict[key] = self.getValue(key)
+
+ return pprint.pformat(tmp_dict)
+
+ #
+ # validation methods used by step driven testcases
+ #
+ def validate_getValue(self, result, attr):
+ """Verifies, that correct value was returned
+ """
+ # getValue must be called to expand macros and apply
+ # values from TEST_PARAM option
+ assert result == self.getValue(attr)
+ return True
+
+ def validate_setValue(self, _dummy_result, name, value):
+ """Verifies, that value was correctly set
+ """
+ assert value == self.__dict__[name]
+ return True
+
+
+settings = Settings()
+
+
+def merge_spec(orig, new):
+ """Merges ``new`` dict with ``orig`` dict, and returns orig.
+
+ This takes into account nested dictionaries. Example:
+
+ >>> old = {'foo': 1, 'bar': {'foo': 2, 'bar': 3}}
+ >>> new = {'foo': 6, 'bar': {'foo': 7}}
+ >>> merge_spec(old, new)
+ {'foo': 6, 'bar': {'foo': 7, 'bar': 3}}
+
+ You'll notice that ``bar.bar`` is not removed. This is the desired result.
+ """
+ for key in orig:
+ if key not in new:
+ continue
+
+ # Not allowing derived dictionary types for now
+ # pylint: disable=unidiomatic-typecheck
+ if type(orig[key]) == dict:
+ orig[key] = merge_spec(orig[key], new[key])
+ else:
+ orig[key] = new[key]
+
+ for key in new:
+ if key not in orig:
+ orig[key] = new[key]
+
+ return orig
diff --git a/sdv/core/__init__.py b/sdv/core/__init__.py
new file mode 100644
index 0000000..2441d38
--- /dev/null
+++ b/sdv/core/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2015 Intel Corporation.
+#
+# 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.
+
+"""Core structural interfaces and their implementations
+"""
+
+# flake8: noqa
+import core.component_factory
diff --git a/sdv/core/component_factory.py b/sdv/core/component_factory.py
new file mode 100644
index 0000000..396aa28
--- /dev/null
+++ b/sdv/core/component_factory.py
@@ -0,0 +1,32 @@
+# Copyright 2020 Spirent Communications.
+#
+# 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.
+
+"""
+Create Components.
+"""
+
+
+def create_swprevalidator(swprevalidator_class):
+ """ Create Pre-Validators"""
+ return swprevalidator_class()
+
+
+def create_swpostvalidator(swpostvalidator_class):
+ """ Create Post-Validators"""
+ return swpostvalidator_class()
+
+
+def create_linkvalidator(linkprevalidator_class):
+ """ Create Link-Validators"""
+ return linkprevalidator_class()
diff --git a/sdv/core/loader/__init__.py b/sdv/core/loader/__init__.py
new file mode 100644
index 0000000..e86c48e
--- /dev/null
+++ b/sdv/core/loader/__init__.py
@@ -0,0 +1,18 @@
+# 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.
+
+"""
+Core: Loader Component.
+"""
+
+# flake8: noqa
+from .loader import Loader
diff --git a/sdv/core/loader/loader.py b/sdv/core/loader/loader.py
new file mode 100644
index 0000000..c9f8e96
--- /dev/null
+++ b/sdv/core/loader/loader.py
@@ -0,0 +1,129 @@
+# Copyright 2020 Intel Corporation, Spirent Communications.
+#
+# 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.
+
+"""
+Abstract class for Software Prevalidations.
+Implementors, please inherit from this class.
+"""
+
+from conf import settings
+from core.loader.loader_servant import LoaderServant
+from SoftwarePreValid.swprevalidator import ISwPreValidator
+from SoftwarePostValid.swpostvalidator import ISwPostValidator
+from NwLinksValid.nwlinksvalidator import INwLinksValidator
+
+
+# pylint: disable=too-many-public-methods
+class Loader():
+ """Loader class - main object context holder.
+ """
+ _swvalidator_loader = None
+
+ def __init__(self):
+ """Loader ctor - initialization method.
+
+ All data is read from configuration each time Loader instance is
+ created. It is up to creator to maintain object life cycle if this
+ behavior is unwanted.
+ """
+ self._swprevalidator_loader = LoaderServant(
+ settings.getValue('SW_PRE_VALID_DIR'),
+ settings.getValue('SW_PRE_VALIDATOR'),
+ ISwPreValidator)
+ self._swpostvalidator_loader = LoaderServant(
+ settings.getValue('SW_POST_VALID_DIR'),
+ settings.getValue('SW_POST_VALIDATOR'),
+ ISwPostValidator)
+ self._nwlinksvalidator_loader = LoaderServant(
+ settings.getValue('NW_LINKS_VALID_DIR'),
+ settings.getValue('NW_LINKS_VALIDATOR'),
+ INwLinksValidator)
+
+ def get_swprevalidator(self):
+ """ Returns a new instance configured Software Validator
+ :return: ISwPreValidator implementation if available, None otherwise
+ """
+ return self._swprevalidator_loader.get_class()()
+
+ def get_swprevalidator_class(self):
+ """Returns type of currently configured Software Validator.
+
+ :return: Type of ISwPreValidator implementation if available.
+ None otherwise.
+ """
+ return self._swprevalidator_loader.get_class()
+
+ def get_swprevalidators(self):
+ """
+ Get Prevalidators
+ """
+ return self._swprevalidator_loader.get_classes()
+
+ def get_swprevalidators_printable(self):
+ """
+ Get Prevalidators for printing
+ """
+ return self._swprevalidator_loader.get_classes_printable()
+
+ def get_swpostvalidator(self):
+ """ Returns a new instance configured Software Validator
+ :return: ISwPostValidator implementation if available, None otherwise
+ """
+ return self._swpostvalidator_loader.get_class()()
+
+ def get_swpostvalidator_class(self):
+ """Returns type of currently configured Software Validator.
+
+ :return: Type of ISwPostValidator implementation if available.
+ None otherwise.
+ """
+ return self._swpostvalidator_loader.get_class()
+
+ def get_swpostvalidators(self):
+ """
+ Get Postvalidators
+ """
+ return self._swpostvalidator_loader.get_classes()
+
+ def get_swpostvalidators_printable(self):
+ """
+ Get Postvalidators for printing
+ """
+ return self._swpostvalidator_loader.get_classes_printable()
+
+ def get_nwlinksvalidator(self):
+ """ Returns a new instance configured Nw-Links Validator
+ :return: INwLinksValidator implementation if available, None otherwise
+ """
+ return self._nwlinksvalidator_loader.get_class()()
+
+ def get_nwlinksvalidator_class(self):
+ """Returns type of currently configured Nw-Links Validator.
+
+ :return: Type of NwLinksValidator implementation if available.
+ None otherwise.
+ """
+ return self._nwlinksvalidator_loader.get_class()
+
+ def get_nwlinkvalidators(self):
+ """
+ Get Linkvalidators
+ """
+ return self._nwlinksvalidator_loader.get_classes()
+
+ def get_nwlinkvalidators_printable(self):
+ """
+ Get Linkvalidators for printing
+ """
+ return self._nwlinksvalidator_loader.get_classes_printable()
diff --git a/sdv/core/loader/loader_servant.py b/sdv/core/loader/loader_servant.py
new file mode 100644
index 0000000..4e55c67
--- /dev/null
+++ b/sdv/core/loader/loader_servant.py
@@ -0,0 +1,183 @@
+# Copyright 2020 Intel Corporation, Spirent Communications.
+#
+# 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.
+
+"""
+Loader Support Module.
+"""
+
+import os
+from os import sys
+import imp
+import fnmatch
+import logging
+from conf import settings
+
+
+class LoaderServant():
+ """Class implements basic dynamic import operations.
+ """
+ _class_name = None
+ _path = None
+ _interface = None
+
+ def __init__(self, path, class_name, interface):
+ """LoaderServant constructor
+
+ Intializes all data needed for import operations.
+
+ Attributes:
+ path: path to directory which contains implementations derived from
+ interface.
+ class_name: Class name which will be returned in get_class
+ method, if such definition exists in directory
+ represented by path,
+ interface: interface type. Every object which doesn't
+ implement this particular interface will be
+ filtered out.
+ """
+ self._class_name = class_name
+ self._path = path
+ self._interface = interface
+
+ def get_class(self):
+ """Returns class type based on parameters passed in __init__.
+
+ :return: Type of the found class.
+ None if class hasn't been found
+ """
+
+ return self.load_module(path=self._path,
+ interface=self._interface,
+ class_name=self._class_name)
+
+ def get_classes(self):
+ """Returns all classes in path derived from interface
+
+ :return: Dictionary with following data:
+ - key: String representing class name,
+ - value: Class type.
+ """
+ return self.load_modules(path=self._path,
+ interface=self._interface)
+
+ def get_classes_printable(self):
+ """Returns all classes derived from _interface found in path
+
+ :return: String - list of classes in printable format.
+ """
+
+ out = self.load_modules(path=self._path,
+ interface=self._interface)
+ results = []
+
+ # sort modules to produce the same output everytime
+ for (name, mod) in sorted(out.items()):
+ desc = (mod.__doc__ or 'No description').strip().split('\n')[0]
+ results.append((name, desc))
+
+ header = 'Classes derived from: ' + self._interface.__name__
+ output = [header + '\n' + '=' * len(header) + '\n']
+
+ for (name, desc) in results:
+ output.append('* %-18s%s' % ('%s:' % name, desc))
+
+ output.append('')
+
+ output.append('')
+
+ return '\n'.join(output)
+
+ @staticmethod
+ def load_module(path, interface, class_name):
+ """Imports everything from given path and returns class type
+
+ This is based on following conditions:
+ - Class is derived from interface,
+ - Class type name matches class_name.
+
+ :return: Type of the found class.
+ None if class hasn't been found
+ """
+
+ results = LoaderServant.load_modules(
+ path=path, interface=interface)
+
+ if class_name in results:
+ logging.info(
+ "Class found: %s.", class_name)
+ return results.get(class_name)
+
+ return None
+
+ @staticmethod
+ def load_modules(path, interface):
+ """Returns dictionary of class name/class type found in path
+
+ This is based on following conditions:
+ - classes found under path are derived from interface.
+ - class is not interface itself.
+
+ :return: Dictionary with following data:
+ - key: String representing class name,
+ - value: Class type.
+ """
+ result = {}
+
+ for _, mod in LoaderServant._load_all_modules(path):
+ # find all classes derived from given interface, but suppress
+ # interface itself and any abstract class starting with iface name
+ gens = dict((k, v) for (k, v) in list(mod.__dict__.items())
+ if isinstance(v, type) and
+ issubclass(v, interface) and
+ not k.startswith(interface.__name__))
+ if gens:
+ for (genname, gen) in list(gens.items()):
+ result[genname] = gen
+ return result
+
+ @staticmethod
+ def _load_all_modules(path):
+ """Load all modules from ``path`` directory.
+
+ This is based on the design used by OFTest:
+ https://github.com/floodlight/oftest/blob/master/oft
+
+ :param path: Path to a folder of modules.
+
+ :return: List of modules in a folder.
+ """
+ mods = []
+
+ for root, _, filenames in os.walk(path):
+ # Iterate over each python file
+ for filename in fnmatch.filter(filenames, '[!.]*.py'):
+ modname = os.path.splitext(os.path.basename(filename))[0]
+
+ # skip module load if it is excluded by configuration
+ if modname in settings.getValue('EXCLUDE_MODULES'):
+ continue
+
+ try:
+ if modname in sys.modules:
+ mod = sys.modules[modname]
+ else:
+ mod = imp.load_module(
+ modname, *imp.find_module(modname, [root]))
+ except ImportError:
+ logging.error('Could not import file %s', filename)
+ raise
+
+ mods.append((modname, mod))
+
+ return mods
diff --git a/sdv/docs/valid.rst b/sdv/docs/valid.rst
new file mode 100644
index 0000000..6aeb8a2
--- /dev/null
+++ b/sdv/docs/valid.rst
@@ -0,0 +1,28 @@
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. http://creativecommons.org/licenses/by/4.0
+.. (c) OPNFV, Intel Corporation, AT&T and others.
+
+CIRV Software Validation Tool
+=============================
+This tool is designed to perform Software Configuration Validation, which covers:
+
+1. Pre-Deployment (of VIM or Container Management Software) Validation of Software Configuration
+2. Post-Deployment (of VIM or Container Management Software) Validation of Software Configuration
+3. Network-Link Checking - Validating VLAN and IP configurations
+
+
+Installation
+************
+This tool does not have any installation. However, there are requirements in terms of Python packages, which can be installed using pip3. Refer to requirements.txt file for the package list.
+
+Usage
+*****
+Example Commands:
+
+1. To run all the validations: ./valid
+2. Help: ./valid --help
+3. Version Check: ./valid --version
+4. List Sofware Pre-Deployment validators: ./valid --list-swpredepv
+5. List Sofware Post-Deployment validators: ./valid --list-swpostdepv
+6. List all validations: ./valid --list-validations
+7. Run only single validation [WORK IN PROGRESS]
diff --git a/sdv/valid b/sdv/valid
new file mode 100755
index 0000000..1a9a252
--- /dev/null
+++ b/sdv/valid
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+
+# Copyright 2020 Spirent Communications
+#
+# 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.
+
+"""VALID main script.
+"""
+
+import logging
+import os
+import sys
+import argparse
+import time
+import datetime
+from conf import settings
+import core.component_factory as component_factory
+from core.loader import Loader
+
+VERBOSITY_LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL
+}
+
+_CURR_DIR = os.path.dirname(os.path.realpath(__file__))
+_LOGGER = logging.getLogger()
+
+def parse_arguments():
+ """
+ Parse command line arguments.
+ """
+ parser = argparse.ArgumentParser(prog=__file__, formatter_class=
+ argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument('--version', action='version', version='%(prog)s 0.1')
+ parser.add_argument('--list-validations', action='store_true',
+ help='list all validations')
+ parser.add_argument('--list-swpredepv', action='store_true',
+ help='list all Software pre-dep validations and exit')
+ parser.add_argument('--list-swpostdepv', action='store_true',
+ help='list all Software post-dep validations and exit')
+ parser.add_argument('--list-nwlinksv', action='store_true',
+ help='list all Link validations and exit')
+ parser.add_argument('exact_validation_name', nargs='*', help='Exact names of\
+ validations to run. E.g "valid nwlinks"\
+ runs only nwlink-validations.\
+ To run all omit positional arguments')
+ args = vars(parser.parse_args())
+
+ return args
+
+
+def configure_logging(level):
+ """Configure logging.
+ """
+ name, ext = os.path.splitext(settings.getValue('LOG_FILE_DEFAULT'))
+ rename_default = "{name}_{uid}{ex}".format(name=name,
+ uid=settings.getValue(
+ 'LOG_TIMESTAMP'),
+ ex=ext)
+ log_file_default = os.path.join(
+ settings.getValue('RESULTS_PATH'), rename_default)
+ _LOGGER.setLevel(logging.DEBUG)
+ stream_logger = logging.StreamHandler(sys.stdout)
+ stream_logger.setLevel(VERBOSITY_LEVELS[level])
+ stream_logger.setFormatter(logging.Formatter(
+ '[%(levelname)-5s] %(asctime)s : (%(name)s) - %(message)s'))
+ _LOGGER.addHandler(stream_logger)
+ file_logger = logging.FileHandler(filename=log_file_default)
+ file_logger.setLevel(logging.DEBUG)
+ file_logger.setFormatter(logging.Formatter(
+ '%(asctime)s : %(message)s'))
+ _LOGGER.addHandler(file_logger)
+
+def handle_list_options(args):
+ """ Process --list cli arguments if needed
+
+ :param args: A dictionary with all CLI arguments
+ """
+ if args['list_swpredepv']:
+ print(Loader().get_swprevalidators_printable())
+ sys.exit(0)
+
+ if args['list_swpostdepv']:
+ print(Loader().get_swpostvalidators_printable())
+ sys.exit(0)
+
+ if args['list_nwlinksv']:
+ print(Loader().get_nwlinkvalidators_printable())
+ sys.exit(0)
+
+
+def main():
+ """Main function.
+ """
+ args = parse_arguments()
+
+ # define the timestamp to be used by logs and results
+ date = datetime.datetime.fromtimestamp(time.time())
+ timestamp = date.strftime('%Y-%m-%d_%H-%M-%S')
+ settings.setValue('LOG_TIMESTAMP', timestamp)
+
+
+ # configure settings
+ settings.load_from_dir(os.path.join(_CURR_DIR, 'conf'))
+
+ # if required, handle list-* operations
+ handle_list_options(args)
+
+ results_dir = "results_" + timestamp
+ results_path = os.path.join(settings.getValue('LOG_DIR'), results_dir)
+ settings.setValue('RESULTS_PATH', results_path)
+ # create results directory
+ if not os.path.exists(results_path):
+ os.makedirs(results_path)
+
+ configure_logging(settings.getValue('VERBOSITY'))
+
+ loader = Loader()
+ swprevalidators = loader.get_swprevalidators()
+ if settings.getValue('SW_PRE_VALIDATOR') not in swprevalidators:
+ _LOGGER.error('There are no swvalidators matching \'%s\' found in'
+ ' \'%s\'. Exiting...', settings.getValue('SW_PRE_VALIDATOR'),
+ settings.getValue('SW_PRE_VALID_DIR'))
+ sys.exit(1)
+ swv_pre_ctrl = component_factory.create_swprevalidator(
+ loader.get_swprevalidator_class())
+ # First validate hyperlinks
+ swv_pre_ctrl.validate_hyperlinks()
+ # Next validate mandatory configuration
+ swv_pre_ctrl.validate_configuration_mandatory()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..e0429a9
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,24 @@
+[metadata]
+name = cirv
+summary = Common Infrastructure Realization & Validation
+description-file =
+ README.md
+author = OPNFV
+author-email = opnfv-tech-discuss@lists.opnfv.org
+home-page = https://wiki.opnfv.org/display/functest
+classifier =
+ Intended Audience :: Information Technology
+ Intended Audience :: System Administrators
+ License :: OSI Approved :: Apache Software License
+ Operating System :: POSIX :: Linux
+ Programming Language :: Python
+ Programming Language :: Python :: 2
+ Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.5
+
+[files]
+packages = sdv
+
+[wheel]
+universal = 1
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..566d844
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# 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.
+
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
+import setuptools
+
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+ import multiprocessing # noqa
+except ImportError:
+ pass
+
+setuptools.setup(
+ setup_requires=['pbr>=2.0.0'],
+ pbr=True)
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..23c3b64
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,8 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+flake8 # MIT
+yamllint
+bashate<1.0.0;python_version=='2.7' # Apache-2.0
+bashate;python_version>='3.6' # Apache-2.0
+pylint
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..5fafde1
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,25 @@
+[tox]
+envlist = pep8,yamllint,pylint
+
+[testenv]
+deps =
+ -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
+
+[testenv:pep8]
+basepython = python3
+commands = flake8
+
+[testenv:yamllint]
+basepython = python3
+files =
+ INFO.yaml
+ sdv
+commands =
+ yamllint -s {[testenv:yamllint]files} -c {toxinidir}/yamllintrc
+
+[testenv:pylint]
+basepython = python3
+commands =
+ pylint sdv
diff --git a/yamllintrc b/yamllintrc
new file mode 100644
index 0000000..bb11492
--- /dev/null
+++ b/yamllintrc
@@ -0,0 +1,25 @@
+# Copyright 2020 Tieto
+#
+# 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.
+
+extends: relaxed
+
+rules:
+ empty-lines:
+ max-start: 1
+ max-end: 1
+ colons:
+ max-spaces-after: 1
+ max-spaces-before: 1
+ line-length:
+ max: 160