From d5a010aa2e40cafabf012b096f508b144f9d3d8b Mon Sep 17 00:00:00 2001 From: opensource-tnbt Date: Wed, 29 Jul 2020 17:56:51 +0530 Subject: SDV: First commit. Moving contents from parent cirv to cirv-sdv Remove pycache Signed-off-by: Sridhar K. N. Rao Change-Id: I3744157eb6616591159abe3eca133160d803dda3 --- .gitreview | 4 + INFO.yaml | 62 +++++ LICENSE | 13 + README.md | 13 + check | 179 ++++++++++++++ ci/build-cirv.sh | 124 ++++++++++ pylintrc | 396 +++++++++++++++++++++++++++++++ requirements.txt | 17 ++ sdv/NwLinksValid/__init__.py | 17 ++ sdv/NwLinksValid/nwlinksvalidator.py | 38 +++ sdv/SoftwarePostValid/__init__.py | 19 ++ sdv/SoftwarePostValid/swpostvalidator.py | 42 ++++ sdv/SoftwarePreValid/__init__.py | 19 ++ sdv/SoftwarePreValid/airship.py | 267 +++++++++++++++++++++ sdv/SoftwarePreValid/swprevalidator.py | 42 ++++ sdv/__init__.py | 0 sdv/conf/00_common.conf | 18 ++ sdv/conf/01_swprevalid.conf | 33 +++ sdv/conf/02_swpostvalid.conf | 4 + sdv/conf/03_nwlinksvalid.conf | 1 + sdv/conf/__init__.py | 265 +++++++++++++++++++++ sdv/core/__init__.py | 19 ++ sdv/core/component_factory.py | 32 +++ sdv/core/loader/__init__.py | 18 ++ sdv/core/loader/loader.py | 129 ++++++++++ sdv/core/loader/loader_servant.py | 183 ++++++++++++++ sdv/docs/valid.rst | 28 +++ sdv/valid | 147 ++++++++++++ setup.cfg | 24 ++ setup.py | 29 +++ test-requirements.txt | 8 + tox.ini | 25 ++ yamllintrc | 25 ++ 33 files changed, 2240 insertions(+) create mode 100644 .gitreview create mode 100644 INFO.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100755 check create mode 100755 ci/build-cirv.sh create mode 100644 pylintrc create mode 100644 requirements.txt create mode 100755 sdv/NwLinksValid/__init__.py create mode 100644 sdv/NwLinksValid/nwlinksvalidator.py create mode 100755 sdv/SoftwarePostValid/__init__.py create mode 100644 sdv/SoftwarePostValid/swpostvalidator.py create mode 100755 sdv/SoftwarePreValid/__init__.py create mode 100644 sdv/SoftwarePreValid/airship.py create mode 100644 sdv/SoftwarePreValid/swprevalidator.py create mode 100644 sdv/__init__.py create mode 100644 sdv/conf/00_common.conf create mode 100644 sdv/conf/01_swprevalid.conf create mode 100644 sdv/conf/02_swpostvalid.conf create mode 100644 sdv/conf/03_nwlinksvalid.conf create mode 100644 sdv/conf/__init__.py create mode 100644 sdv/core/__init__.py create mode 100644 sdv/core/component_factory.py create mode 100644 sdv/core/loader/__init__.py create mode 100644 sdv/core/loader/loader.py create mode 100644 sdv/core/loader/loader_servant.py create mode 100644 sdv/docs/valid.rst create mode 100755 sdv/valid create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 test-requirements.txt create mode 100644 tox.ini create mode 100644 yamllintrc 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 @@ + + +# 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 </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*(# )??$ + +# 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 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[0-9]+)(?P[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 -- cgit 1.2.3-korg