├── requirements.txt ├── comprehensive ├── libs │ ├── __init__.py │ └── local_library.py ├── testcases │ ├── __init__.py │ └── comprehensive_testcases.py ├── data │ ├── extended_datafile.yaml │ └── base_datafile.yaml ├── README.md ├── etc │ └── example_testbed.yaml ├── variant_example.py ├── job │ └── example_job.py └── base_example.py ├── basic ├── basic_example_job.py └── basic_example_script.py ├── COPYRIGHT ├── README.md ├── .gitignore └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /comprehensive/libs/__init__.py: -------------------------------------------------------------------------------- 1 | '''libs/__init__.py 2 | 3 | Defines the package 'libs' under comprehensive examples folder. This folder 4 | is intended for storing local libraries: libraries that are local to this module 5 | and cannot be shared/used by other modules 6 | 7 | The use of local libraries should be limited to as infrequently as possible: 8 | good testscript and library design should always be re-useable somewhere else. 9 | 10 | Note: 11 | the usage of local library files is entirely optional. 12 | 13 | It is not an AEtest script infrastructure requirement, but rather, a 14 | recommended standard to follow in order to produce clearly defined, 15 | re-useable & maintainable testscripts. 16 | ''' -------------------------------------------------------------------------------- /basic/basic_example_job.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Basic Example Job 3 | ----------------- 4 | 5 | This example shows the basic functionality of pyats with few passing tests. 6 | 7 | To run: 8 | 9 | cd pyats-sample-scripts/basic 10 | pyats run job basic_example_job.py 11 | 12 | ''' 13 | 14 | import os 15 | from pyats.easypy import run 16 | 17 | def main(): 18 | ''' 19 | main() function is the default easypy job file entry point. 20 | ''' 21 | 22 | # find the location of the script in relation to the job file 23 | script_path = os.path.dirname(os.path.abspath(__file__)) 24 | testscript = os.path.join(script_path, 'basic_example_script.py') 25 | 26 | # execute the testscript 27 | run(testscript=testscript) 28 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------ 2 | # Copyright 2017 Cisco Systems 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ------------------------------------------------------------------ 16 | -------------------------------------------------------------------------------- /comprehensive/testcases/__init__.py: -------------------------------------------------------------------------------- 1 | '''testcases/__init__.py 2 | 3 | Defines the package 'testcases' under comprehensive examples folder. This folder 4 | is intended for storing "testcase files": a library of testcases available for 5 | import & usage in actual testscripts. 6 | 7 | The goal of the testcases/ folder is to maintain a library of testcase classes & 8 | definitions. This allows testscripts to import and inherit from a common source 9 | as opposed to add-hoc definition of testcases in various scripts, leading to 10 | potential circular imports. 11 | 12 | Testcase files are not intended to be run directly. Rather, they should always 13 | be inherited and provided with & data/parameters before execution. Any libraries 14 | and procedure/functions it references should be imported, from other libraries, 15 | rather than being contained here directly. 16 | 17 | Note: 18 | the usage of testcase files is entirely optional. 19 | 20 | It is not an AEtest script infrastructure requirement, but rather, a 21 | recommended standard to follow in order to produce clearly defined, 22 | re-useable & maintainable testscripts. 23 | ''' -------------------------------------------------------------------------------- /comprehensive/data/extended_datafile.yaml: -------------------------------------------------------------------------------- 1 | #******************************************************************************* 2 | #* EXTENSION DATAFILE 3 | #* 4 | #* The content of this file is no different than that of base_datafile.yaml, 5 | #* except that it "extends" it: the base_datafile.yaml values for the basis 6 | #* for this datafile to build on top of. 7 | #* 8 | #* Keep in mind that datafile extensions are done using recursive dictionary 9 | #* updates. 10 | #* 11 | #* To run the script with this datafile: 12 | #* bash$ python base_example.py -datafile=data/base_datafile.yaml 13 | #******************************************************************************* 14 | 15 | #**************************************** 16 | #* Extend More Datafiles 17 | #* 18 | #* each datafile may extend one or more datafiles. 19 | #* the value to 'extends' may be a single file or a list of files. 20 | extends: base_datafile.yaml 21 | 22 | parameters: 23 | parameter_A: datafile extended A 24 | 25 | testcases: 26 | 27 | ExampleTestcase: 28 | uid: 'ExampleTestcaseDatafileID_Extended!' 29 | groups: [group_A, group_B, group_C, group_D, group_E] 30 | 31 | parameters: 32 | local_B: datafile extended B 33 | 34 | data_A: attribute extended A 35 | 36 | LoopedTestcase: 37 | groups: [ha, sanity] 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyATS Sample Scripts 2 | 3 | The best method to master pyATS test framework is to learn-by-example. This 4 | repository contains various scripts that showcases the many features and 5 | packages of pyATS. 6 | 7 | ## General Information 8 | 9 | - Website: https://developer.cisco.com/site/pyats/ 10 | - Bug Tracker: https://github.com/CiscoTestAutomation/pyats/issues 11 | - Documentation: https://developer.cisco.com/site/pyats/docs/ 12 | 13 | ## Requirements 14 | pyATS & its examples supports Python 3.4+ on Linux & Mac systems. Windows is not yet supported. 15 | 16 | ## Getting Started 17 | 18 | The examples included in this repository expects you to have a Python environment with pyATS packages installed. Alternatively, you can always setup a new virtual environment as sandbox. 19 | 20 | ``` 21 | $ python3 -m venv pyats_sandbox 22 | $ cd pyats_sandbox 23 | $ source bin/activate 24 | $ pip install pyats 25 | 26 | $ git clone https://github.com/CiscoDevNet/pyats-sample-scripts.git 27 | ``` 28 | 29 | ## Sample Script Usage 30 | 31 | There are two ways to run a typical pyATS script: 32 | 33 | 1. through `pyats run job`, which generates log and archives 34 | 2. as standalone, and prints results to screen 35 | 36 | ```bash 37 | $ cd pyats-sample-scripts/basic 38 | 39 | $ pyats run job basic_example_job.py 40 | 41 | $ python basic_example_script.py 42 | ``` 43 | 44 | Refer to each job file's docstring on details of command-line arguments. 45 | 46 | -------------------------------------------------------------------------------- /comprehensive/libs/local_library.py: -------------------------------------------------------------------------------- 1 | '''local_library.py 2 | 3 | This is a local library file that lives with the testscript. Local libraries 4 | contains functions, classes & methods local to a testscript only. Because they 5 | are local and are not shared with other scripts/modules, the use of them should 6 | be minimized (not used if possible). Common code/libraries should be shared with 7 | the testing community in repositories such as xbu_shared. 8 | 9 | ''' 10 | 11 | # 12 | # import statements 13 | # 14 | import logging 15 | 16 | # 17 | # create a logger for this module 18 | # 19 | logger = logging.getLogger(__name__) 20 | 21 | #********************************** 22 | #* Function & Class Definitions 23 | #* 24 | def function_supporting_step(step): 25 | '''function_supporting_step 26 | 27 | This function demonstrate the use of steps within function APIs. This 28 | enables smaller breakdown of functions into smaller steps, and thus provides 29 | finer granularity in your testscript logs. 30 | 31 | Arguments 32 | --------- 33 | steps (obj): the step object to be passed in from the testscript 34 | 35 | ''' 36 | 37 | with step.start('function step one'): 38 | # do some meaningful testing 39 | pass 40 | 41 | with step.start('function step two'): 42 | # do some meaningful testing 43 | pass 44 | 45 | with step.start('function step three'): 46 | # do some meaningful testing 47 | pass 48 | 49 | return -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /comprehensive/README.md: -------------------------------------------------------------------------------- 1 | # Comprehensive Example 2 | 3 | This is a comprehensive example written to showcase pyATS AEtest features. 4 | This example is typically automatically copied into your pyATS instance as part 5 | of the installation process. 6 | 7 | The goal of this example is to demonstrate the following basics: 8 | 9 | - how AEtest works: features & flow 10 | - writing a straighforward, basic testscript: 11 | - script components and structure 12 | - various script features 13 | - script inheritance & variance 14 | - driving testscript with data/parameters 15 | - passing arguments from jobfile/commandline into the testscript. 16 | - importing local libraries & etc. 17 | 18 | and is written to be self-explanatory. It is intended to act as a semi-guide, 19 | walking users through important features and explaining them using live 20 | examples, supplementing the full user guide at: 21 | https://developer.cisco.com/site/pyats/docs/ 22 | 23 | This example is executable both under standalone execution, and through 24 | pyATS command line `pyats run job`. You can modify parts of this code to see 25 | how it changes execution behaviors. 26 | 27 | ``` 28 | Folder Structure 29 | ---------------- 30 | pyats-sample-scripts/comprehensive/ 31 | |-- README.md 32 | |-- base_example.py 33 | |-- variant_example.py 34 | |-- job 35 | | `-- example_job.py 36 | |-- etc 37 | | `-- example_testbed.yaml 38 | |-- testcases 39 | | |-- __init__.py 40 | | `-- comprehensive_testcases.py 41 | `-- libs 42 | |-- __init__.py 43 | `-- local_library.py 44 | ``` 45 | 46 | Each file listed above contains appropriate headers describing their usages. 47 | 48 | 49 | ## Usages 50 | 51 | ```bash 52 | $ cd pyats-sample-scripts/comprehensive/ 53 | 54 | # standalone execution 55 | # -------------------- 56 | $ python base_example.py --testbed etc/example_testbed.yaml 57 | $ python variant_example.py --testbed etc/example_testbed.yaml 58 | 59 | # pyats job execution 60 | # ------------------- 61 | $ pyats run job job/example_job.py --testbed-file etc/example_testbed.yaml 62 | ``` -------------------------------------------------------------------------------- /comprehensive/etc/example_testbed.yaml: -------------------------------------------------------------------------------- 1 | # Example Testbed 2 | testbed: 3 | name: example_testbed 4 | servers: 5 | tftp: 6 | server: "ott2lab-tftp1" 7 | address: "223.255.254.254" 8 | path: "" 9 | username: "username" 10 | password: "password" 11 | ntp: 12 | server: 102.0.0.102 13 | 14 | devices: 15 | ott-tb1-n7k3: 16 | alias: 'uut' 17 | type: 'router' 18 | tacacs: 19 | login_prompt: "login:" 20 | password_prompt: "Password:" 21 | username: "admin" 22 | passwords: 23 | tacacs: password 24 | enable: password 25 | line: password 26 | connections: 27 | a: 28 | protocol: telnet 29 | ip: "10.85.87.25" 30 | port: 2003 31 | b: 32 | protocol: telnet 33 | ip: "10.85.87.25" 34 | port: 2004 35 | alt: 36 | protocol : telnet 37 | ip : "5.25.25.3" 38 | clean: 39 | pre_clean: | 40 | switchname %{self} 41 | license grace-period 42 | feature telnet 43 | interface mgmt0 44 | ip addr %{self.connections.alt.ip}/24 45 | no shut 46 | vrf context management 47 | ip route 101.0.0.0/24 5.19.27.251 48 | ip route 102.0.0.0/24 5.19.27.251 49 | post_clean: | 50 | switchname %{self} 51 | license grace-period 52 | feature telnet 53 | interface mgmt0 54 | ip addr %{self.connections.alt.ip}/24 55 | no shut 56 | vrf context management 57 | ip route 101.0.0.0/24 5.19.27.251 58 | ip route 102.0.0.0/24 5.19.27.251 59 | 60 | topology: 61 | 62 | links: 63 | link-1: 64 | alias: 'loopback-1' 65 | 66 | ott-tb1-n7k3: 67 | interfaces: 68 | Ethernet3/1: 69 | link: link-1 70 | type: ethernet 71 | 72 | Ethernet4/1: 73 | link: link-1 74 | type: ethernet 75 | 76 | Ethernet4/2: 77 | type: ethernet 78 | 79 | Ethernet4/3: 80 | type: ethernet -------------------------------------------------------------------------------- /comprehensive/data/base_datafile.yaml: -------------------------------------------------------------------------------- 1 | #******************************************************************************* 2 | #* BASE DATAFILE 3 | #* 4 | #* Datafiles allows users an additional flexibity to provide their testscript 5 | #* with data, feature enablement & etc. The concept is simple: take your 6 | #* script module and its classes, and recursively update the values found in 7 | #* the provided datfile into those objects. 8 | #* 9 | #* - Only a single datafile can be provided to a script 10 | #* - New/existing values will be overwritten, expect parameters: they are 11 | #* updated using dict.update() 12 | #* 13 | #* This is a good place to set/alter your script data points, testcase uid, 14 | #* grouping, processors & etc. 15 | #* 16 | #* To run the script with this datafile: 17 | #* bash$ python base_example.py -datafile=data/base_datafile.yaml 18 | #******************************************************************************* 19 | 20 | #**************************************** 21 | #* Testscript Parameters 22 | #* 23 | #* key/values used to update the script's parameters 24 | parameters: 25 | parameter_A: datafile value A 26 | parameter_B: datafile value B 27 | 28 | #**************************************** 29 | #* Testcases Block 30 | #* 31 | #* each key corresponds to a testcase class definition 32 | testcases: 33 | 34 | #**************************************** 35 | #* ExampleTestcase Class 36 | ExampleTestcase: 37 | 38 | #**************************************** 39 | #* modify testcase uid 40 | uid: 'ExampleTestcaseDatafileID' 41 | 42 | #**************************************** 43 | #* modify testcase description 44 | description: | 45 | block of text describing what this testcase does 46 | 47 | #**************************************** 48 | #* alter testcase grouping 49 | groups: [group_A, group_B, group_C, group_D] 50 | 51 | #**************************************** 52 | #* modify this testcase's parameters 53 | parameters: 54 | local_A: datafile value A 55 | local_B: datafile value B 56 | 57 | #**************************************** 58 | #* all other class attributes/values 59 | data_A: attribute data A 60 | data_B: attribute data B 61 | 62 | #**************************************** 63 | #* LoopedTestcase Class 64 | LoopedTestcase: 65 | groups: [] 66 | #**************************************** 67 | #* TestcaseWithSteps Class 68 | TestcaseWithSteps: 69 | uid: TestcaseWithStepsDatafileID -------------------------------------------------------------------------------- /comprehensive/testcases/comprehensive_testcases.py: -------------------------------------------------------------------------------- 1 | '''comprehensive_testcases.py 2 | 3 | This file contains some common testcases used by comprehensive base and variant 4 | examples. It serves as an example demonstrating the usage & benefits of using 5 | testcase files. 6 | 7 | For more on testcase files, refer to __init__.py of testcases/ folder. 8 | ''' 9 | 10 | # 11 | # import statements 12 | # 13 | from pyats import aetest 14 | import logging 15 | 16 | # 17 | # create a logger for this module 18 | # 19 | logger = logging.getLogger(__name__) 20 | 21 | #******************************************************************************* 22 | #* TESTCASE DEFINITIONS 23 | #* 24 | #* Tach testcase defined here can be imported in main scripts and used. 25 | #* 26 | #* The main advantage of defining testcases outside of the scope of testscripts 27 | #* is the ability to abstract out the test data required by the testcase. This 28 | #* allows testcases to be re-useable (instead of being hard-coded with values). 29 | #* 30 | #* This data-test abstraction is a supplement to testcase parameters. 31 | #* 32 | #******************************************************************************* 33 | 34 | 35 | #****************************** 36 | #* Dynamic Loop Testcase 37 | #* 38 | #* this testcase demonstrate dynamic looping: defining loops dynamically within 39 | #* test sections (as opposed to static looping using the @loop decorator). 40 | class DynamicLoopDemonstration(aetest.Testcase): 41 | '''Dynamic Loop Demonstration 42 | 43 | This testcase requires some data in order to run. These data needs to be 44 | defined when the testcase is inherited. 45 | 46 | Requires Data: 47 | loop_value_one (list): list of loop values for dynamic looping of 48 | test section one 49 | loop_value_twp (list): list of loop values for dynamic looping of 50 | test section two 51 | ''' 52 | 53 | @aetest.setup 54 | def setup(self): 55 | '''Setup section 56 | 57 | this section turns on looping of testcase section one and two. 58 | ''' 59 | 60 | #****************************** 61 | #* Dynamic Loop Testcase 62 | #* 63 | #* Marking sections for looping dynamically is done using function 64 | #* 'aetest.loop.mark'. To use, pass in the section to be marked for 65 | #* looping as the first argument. The rest of the options are exactly 66 | #* the same as using @loop decorator. 67 | #* 68 | #* dynamic looping works on both testcases and sections. This example 69 | #* is using test sections for demonstration 70 | #* 71 | #* note also that within this definition, we are referring to two 72 | #* testcase data attributes: 73 | #* DynamicLoopDemonstration.loop_value_one 74 | #* DynamicLoopDemonstration.loop_value_two 75 | #* 76 | #* both needs to be defined when the testcase is inherited. 77 | aetest.loop.mark(self.test_one, loop_parameter = self.loop_value_one) 78 | aetest.loop.mark(self.test_two, loop_parameter = self.loop_value_two) 79 | 80 | 81 | @aetest.test 82 | def test_one(self, loop_parameter): 83 | '''Test one 84 | 85 | Dynamically looped test section one. 86 | ''' 87 | 88 | logger.info('Loop iteration with parameter value: %s' % loop_parameter) 89 | 90 | @aetest.test 91 | def test_two(self, loop_parameter): 92 | '''Test two 93 | 94 | Dynamically looped test section two. 95 | ''' 96 | 97 | logger.info('Loop iteration with parameter value: %s' % loop_parameter) 98 | 99 | 100 | -------------------------------------------------------------------------------- /basic/basic_example_script.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Basic Example Script 3 | -------------------- 4 | 5 | A very simple test script example which include: 6 | common_setup 7 | testcases 8 | common_cleanup 9 | 10 | The purpose of this sample test script is to demonstrate 11 | "hello world" in aetest. 12 | 13 | You can run this script directly as: 14 | $ python basic_example_script.py 15 | ''' 16 | 17 | import logging 18 | 19 | from pyats import aetest 20 | 21 | # get your logger for your script 22 | log = logging.getLogger(__name__) 23 | 24 | class common_setup(aetest.CommonSetup): 25 | '''Common Setup Section 26 | 27 | Each script may only have a single common setup section. 28 | Common setup section is always run as the first section in a test script, 29 | and serves to perform all the "common" setups required for your script. 30 | 31 | Define a common setup section by subclassing aetest.CommonSetup class. 32 | It's a good convention to name it 'common_setup', as this section's 33 | reporting ID is always 'common_setup'. 34 | 35 | Each common setup may have 1+ subsections. Consider a subsection as a 36 | setup 'milestone/step'. A subsection is defined using @aetest.subsection 37 | decorator on a method. 38 | ''' 39 | 40 | @aetest.subsection 41 | def subsection_1(self): 42 | '''example subsection one''' 43 | log.info('hello world!') 44 | 45 | 46 | @aetest.subsection 47 | def subsection_2(self, section): 48 | ''' 49 | if the special 'section' keyword argument is defined in the subsection 50 | method, the current running subsection will be passed in. 51 | ''' 52 | log.info("inside %s" % (section)) 53 | 54 | 55 | class Testcase_One(aetest.Testcase): 56 | '''Testcases 57 | 58 | Testcases are the bread and butter of test automation. Each testcase should 59 | be a self-contained individual unit of testing, and are independent from 60 | other testcases (eg, testcases should be runnable out-of-order). 61 | 62 | Define a testcase by subclassing from aetest.Testcase and provide a 63 | meaningful class name - this will be used as the testcase's reporting ID. 64 | 65 | Each testcase may contain: 66 | - 1 x setup section 67 | - n x tests section 68 | - 1 x cleanup section 69 | 70 | Within each testcase, the class instance is perserved and reused for each 71 | section execution. Eg: self points to the same instance while this testcase 72 | runs. This is an important property of AEtest testcases. 73 | ''' 74 | 75 | @aetest.setup 76 | def setup(self, section): 77 | '''setup section 78 | 79 | create a setup section by defining a method and decorating it with 80 | @aetest.setup decorator. The method should be named 'setup' as good 81 | convention. 82 | 83 | setup sections are optional within a testcase, and is always runs first. 84 | ''' 85 | log.info("%s testcase setup/preparation" % self.uid) 86 | 87 | # set some variables 88 | self.a = 1 89 | self.b = 2 90 | 91 | @aetest.test 92 | def test_1(self, section): 93 | '''test section 94 | 95 | create a test section by defining a method and decorating it with 96 | @aetest.test decorator. The name of the method becomes the unique id 97 | labelling this test. There may be arbitrary number of tests within a 98 | testcase. 99 | 100 | test sections run in the order they appear within a testcase body. 101 | ''' 102 | log.info("test section: %s in testcase %s" % (section.uid, self.uid)) 103 | 104 | # testcase instance is preserved, eg 105 | assert self.a == 1 106 | 107 | @aetest.test 108 | def test_2(self, section): 109 | ''' 110 | you can also provide explicit results, reason and data using result API. 111 | These information will be captured in the result summary. 112 | ''' 113 | log.info("test section: %s in testcase %s" % (section.uid, self.uid)) 114 | 115 | if self.b == 2: 116 | self.passed('variable b contains the expected value', 117 | data = {'b': self.b}) 118 | else: 119 | self.failed('variable b did not contains the expected value', 120 | data = {'b': self.b}) 121 | 122 | @aetest.cleanup 123 | def cleanup(self): 124 | '''cleanup section 125 | 126 | create a cleanup section by defining a method a decorating it with 127 | @aetest.cleanup decorator. This method should be named 'cleanup' as good 128 | convention. 129 | 130 | cleanup sections are optional within a testcase, and is always run last. 131 | ''' 132 | log.info("%s testcase cleanup/teardown" % self.uid) 133 | 134 | class common_cleanup(aetest.CommonCleanup): 135 | '''Common Cleanup Section 136 | 137 | Each script may only have a single common cleanup section. 138 | Common cleanup section is always run as the last section in a test script, 139 | and serves to perform all the "common" cleanups required for your script. 140 | 141 | In addition, common-cleanup section should act as the safety net: in case 142 | of dramatic testcase failures, common cleanup section should be able to 143 | cleanup the lingering mess left behind in the testbed/devices under test. 144 | 145 | Define a common cleanup section by subclassing aetest.CommonCleanup class. 146 | It's a good convention to name it 'common_cleanup', as this section's 147 | reporting ID is always 'common_cleanup'. 148 | 149 | Similar to its counterpart, common cleanup may have 1+ subsections. 150 | Consider a subsection as a cleanup 'milestone/step'. A subsection is defined 151 | using @aetest.subsection decorator on a method. 152 | ''' 153 | 154 | # CommonCleanup follow exactly the same rule as CommonSetup regarding 155 | # subsection 156 | # You can have 1 to as many subsection as wanted 157 | # here is an example of 1 subsections 158 | 159 | @aetest.subsection 160 | def clean_everything(self): 161 | '''one subsection for simplicity''' 162 | 163 | log.info('goodbye world') 164 | 165 | if __name__ == '__main__': 166 | # standard boilerplate entrypoint if the script is run standalone 167 | # as python basic_example_script.py 168 | 169 | # perform any necessary setup here, eg, parse args. 170 | # ... 171 | 172 | # finally, call aetest.main() to start script execution 173 | aetest.main() 174 | -------------------------------------------------------------------------------- /comprehensive/variant_example.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | '''variant_example.py 4 | 5 | This is a comprehensive example variant script. It extends the base_example 6 | script by leveraging a few of its sections & testcases, and adding a few of its 7 | own. 8 | 9 | This is called the 'variant' script: it provides varianting (eg, extends, adds 10 | more to) to the base script. What we're trying to demonstrate here is the use 11 | of inheritances and re-useability of test script codes, etc. 12 | 13 | All basic AEtest features are demonstrated in the base script. This variant 14 | script is written assuming the user has a good understanding of how testscript 15 | flows, how various features work, etc. 16 | 17 | Arguments: 18 | This script requires two script arguments (argument_A and argument_B) to be 19 | passed in from the job file for demonstration purposes. 20 | 21 | argument_A: an argument 22 | argument_B: another argument 23 | 24 | Examples: 25 | # to run under standalone execution 26 | bash$ python variant_example.py 27 | 28 | # to run under pyats job engine 29 | bash$ pyats run job job/example_job.py 30 | 31 | References: 32 | For the complete and up-to-date user guide on pyATS, visit: 33 | https://developer.cisco.com/site/pyats/docs/ 34 | ''' 35 | 36 | # 37 | # optional author information 38 | # 39 | __author__ = 'Siming Yuan ' 40 | __copyright__ = 'Copyright 2017, Cisco Systems' 41 | __email__ = 'pyats-support-ext@cisco.com' 42 | __date__= 'Nov 14, 2017' 43 | 44 | 45 | # 46 | # import statements 47 | # 48 | import logging 49 | import argparse 50 | 51 | from pyats import aetest 52 | from pyats.log.utils import banner 53 | 54 | #********************************** 55 | #* Import From Base Script 56 | #* 57 | #* because this testscript inherits from the base example, it needs to import 58 | #* testcases/sections from it. 59 | #* Note that the act of importing testcases and common sections from another 60 | #* script doesn't actually run it. They need to be locally defined (inherited) 61 | #* in order for them to take effect. 62 | #* 63 | #* here, we'll reuse parts of the base_example. so let's import it 64 | import base_example 65 | 66 | #********************************** 67 | #* Import From Testcases 68 | #* 69 | #* importing testcases from the testcase library (under testcases/ folder). 70 | #* 71 | #* up to this point we've been focusing on defining testcases directly within 72 | #* testscripts. This was more intuitive from a training perspective, to teach 73 | #* content in a linear perspective, building up as we go. 74 | #* 75 | #* However, whilst this is fine for small-scale scripts for demo purpose, the 76 | #* resulting scripts often have have a mix-up of data hard-coding & such, 77 | #* leading to testscripts that are difficult to be re-used & extended. 78 | #* 79 | #* the use of a dedicated testcase library promotes good development habits: 80 | #* consolidating testcases into a central module, and inheriting whichever 81 | #* one you need in your scripts, provide the data & go. 82 | #* 83 | #* see more @ testcases/__init__.py for details 84 | #* 85 | #* Note: 86 | #* the usage of testcase files is entirely optional. 87 | #* 88 | #* It is not an AEtest script infrastructure requirement, but rather, a 89 | #* recommended standard to follow in order to produce clearly defined, 90 | #* re-useable & maintainable testscripts. 91 | from testcases import comprehensive_testcases 92 | 93 | 94 | # 95 | # create a logger for this module 96 | # 97 | logger = logging.getLogger(__name__) 98 | 99 | #********************************** 100 | #* Testscript Parameters 101 | #* 102 | #* we can reuse part of, or the entire of the base_example's parameters 103 | #* 104 | #* for example, let's take a copy of the base_example's testscript parameters, 105 | #* and extend on top of it. 106 | 107 | # 108 | # testscript parameters 109 | # 110 | parameters = base_example.parameters.copy() 111 | parameters.update(variant_parameter_A = 'variant A', 112 | variant_parameter_B = 'variant B') 113 | 114 | 115 | #******************************************************************************* 116 | #* COMMON SETUP SECTION 117 | #* 118 | #* instead of defining a whole new CommonSetup section, here we can inherit 119 | #* the base script's common_setup section, and add/overwrite subsections. 120 | class CommonSetup(base_example.CommonSetup): 121 | '''Common Setup Section 122 | 123 | This CommonSetup inherits from the base_example.CommonSetup, and adds more 124 | local subsections to it. 125 | 126 | ''' 127 | 128 | @aetest.subsection 129 | def using_parameters(self, **kwargs): 130 | '''demonstrating parameter overwriting 131 | 132 | base_example's CommonSetup also has the same 'using_parameters' section. 133 | This will overwrite it with our own, do extra stuff, then call the 134 | original one (class inheritance technique). 135 | ''' 136 | 137 | # kwargs contains all parameters 138 | logger.info('Variant Parameter A: %s' % kwargs['variant_parameter_A']) 139 | logger.info('Variant Parameter B: %s' % kwargs['variant_parameter_B']) 140 | 141 | # objects are great: call the parent subsection that we defined 142 | # it's just a method - call it with the proper arguments it requires. 143 | super().using_parameters(testbed = kwargs['testbed'], 144 | parameter_A = kwargs['parameter_A'], 145 | parameter_B = kwargs['parameter_B'], ) 146 | 147 | @aetest.subsection 148 | def new_subsection_in_variant(self): 149 | '''New Subsection 150 | 151 | demonstrating that after inheriting the previous CommonSetup, we can add 152 | more sections to it. 153 | ''' 154 | logger.info(banner('new subsection is now called')) 155 | 156 | #******************************************************************************* 157 | #* TESTCASES 158 | #* 159 | #* testcases can also be easily inherited. 160 | #* in addition, if you inherit a testcase and then apply loop on it, it will 161 | #* loop as well. 162 | #* for this example, we'll use loop uids instead of loop parameters, since the 163 | #* parent testcase doesn't require parameters 164 | @aetest.loop(uids = ['step_testcase_loop_1', 'step_testcase_loop_2']) 165 | class InheritedStepTestcase(base_example.TestcaseWithSteps): 166 | '''InheritedStepTestcase 167 | 168 | We are inheriting the parent testcase fully and not making any modifications 169 | to it. 170 | ''' 171 | 172 | #********************************** 173 | #* Testcase Groups 174 | #* 175 | #* parent groups are automatically inherited when inheriting testcases. 176 | #* this can be overwritten. 177 | 178 | groups = ['group_A', 'group_B', 'group_D'] 179 | 180 | #******************************************************************************* 181 | #* INHERITING FROM TESTCASE LIBRARY 182 | #* 183 | #* demonstrating inheriting a testcase from testcases/ testcase library 184 | class DynamicLoops(comprehensive_testcases.DynamicLoopDemonstration): 185 | '''Dynamic Loops 186 | 187 | Leveraging a testcase defined in comprehensive_testcases library and reusing 188 | it here. Note that testcase data values needed to be defined in order to 189 | use this testcase. 190 | ''' 191 | 192 | #********************************** 193 | #* Testcase Data 194 | #* 195 | #* this testcase requires two data values to be defined in order to run 196 | loop_value_one = [123, 456, 789] 197 | loop_value_two = ['abc', 'def', 'ghi'] 198 | 199 | #********************************** 200 | #* Local Cleanup 201 | #* 202 | #* of course, by the rules of inheritance, we can also create local 203 | #* sections. 204 | @aetest.cleanup 205 | def cleanup(self): 206 | pass 207 | 208 | 209 | #******************************************************************************* 210 | #* LOOPING & INHERITANCE 211 | #* 212 | #* if you inherit a testcase that was originally looping, the new testcase will 213 | #* also be looping. 214 | #* 215 | #* however, you can also overwrite its original loop parameters. 216 | @aetest.loop(a = [5, 6]) 217 | class VariantLoopedTestcase(base_example.LoopedTestcase): 218 | pass 219 | 220 | #******************************************************************************* 221 | #* PARAMETERS & INHERITANCE 222 | #* 223 | #* if a testcase is inherited, its parameters & attributes are also inherited. 224 | #* you can choose to overwrite them. 225 | #* 226 | class VariantExampleTestcase(base_example.ExampleTestcase): 227 | 228 | uid = 'VariantExampleTestcase' 229 | 230 | # overwriting parameter defaults 231 | parameters = { 232 | 'local_A': 'variant default A', 233 | 'local_B': 'variant default B', 234 | } 235 | 236 | # data can be overwritten 237 | data_A = base_example.ExampleTestcase.data_A * 10 238 | data_B = base_example.ExampleTestcase.data_B + ' ' + 'ABC' 239 | 240 | @aetest.cleanup 241 | def cleanup(self): 242 | '''Adding cleanup 243 | 244 | Let's add a cleanup Section to this variant testcase 245 | ''' 246 | pass 247 | 248 | 249 | class VariantNewTestcase(aetest.Testcase): 250 | '''Variant New Testcase 251 | 252 | Variant new testcase: this testcase is defined entirely new in this variant 253 | script. 254 | ''' 255 | 256 | @aetest.setup 257 | def setup(self): 258 | pass 259 | 260 | @aetest.test 261 | def test(self): 262 | pass 263 | 264 | @aetest.cleanup 265 | def cleanup(self): 266 | pass 267 | 268 | #******************************************************************************* 269 | #* STANDALONE EXECUTION 270 | #* 271 | #* this is the same as the base_example. If you feel like this is redundant, 272 | #* you can create a function to do this mundane work, and call it here. 273 | if __name__ == '__main__': 274 | 275 | # 276 | # local imports 277 | # 278 | import argparse 279 | from ats import topology 280 | 281 | # 282 | # set global loglevel 283 | # 284 | logging.root.setLevel('INFO') 285 | logging.root.setLevel('INFO') 286 | 287 | # 288 | # local standalone parsing 289 | # 290 | parser = argparse.ArgumentParser(description = "standalone parser") 291 | parser.add_argument('--testbed', dest = 'testbed', 292 | help = 'testbed YAML file', 293 | type = topology.loader.load, 294 | default = None) 295 | 296 | # do the parsing 297 | args = parser.parse_known_args()[0] 298 | 299 | # 300 | # calling aetest.main() to start testscript run 301 | # 302 | aetest.main(testbed = args.testbed) 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Cisco Systems, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /comprehensive/job/example_job.py: -------------------------------------------------------------------------------- 1 | '''example_job.py 2 | 3 | This is the example job file for both comprehensive examples: the base_example 4 | script and variant_example script. 5 | 6 | In this jobfile, we'll demonstrate the purpose & configurability of job files, 7 | and as well demonstrate some AEtest features. 8 | 9 | Examples: 10 | # to run under standalone execution 11 | bash$ python base_example.py 12 | 13 | # to run under pyats job engine 14 | bash$ pyats run job example_job.py 15 | 16 | References: 17 | For the complete and up-to-date user guide on pyATS, visit: 18 | https://developer.cisco.com/site/pyats/docs/ 19 | ''' 20 | 21 | # 22 | # optional author information 23 | # 24 | __author__ = 'Siming Yuan ' 25 | __copyright__ = 'Copyright 2017, Cisco Systems' 26 | __email__ = 'pyats-support-ext@cisco.com' 27 | __date__= 'Nov 14, 2017' 28 | 29 | # 30 | # import block 31 | # 32 | import os 33 | import logging 34 | import argparse 35 | 36 | from pyats.easypy import run 37 | 38 | # import logic statements from datastructures module 39 | from pyats.datastructures.logic import And, Or, Not 40 | 41 | #******************************************************************************* 42 | #* ENVIRONMENT 43 | #* 44 | #* often it may be necessary to parse environment variables to set up script 45 | #* inputs/arguments based on them. This allows dynamic information from the 46 | #* environment (such as EARMs) to control certain behavior of the job. 47 | #* 48 | #* Example 49 | #* ------- 50 | #* # beware that environment values are always in strings, and need 51 | #* # to be casted to python objects 52 | #* # note that below is effectively the same as writing 53 | #* # if 'force_all_tests_to_pass' in os.environ: 54 | #* # force_all_pass = bool(os.environ['force_all_tests_to_pass']) 55 | #* # else: 56 | #* # force_all_pass = False 57 | #* * 58 | #* force_all_pass = bool(os.environ.get('force_all_tests_to_pass', False)) 59 | #* 60 | #* # now call the script with this arg 61 | #* # it's not a good idea to make all tests pass :( but it's black magic! 62 | #* run(testscript='/path/to/script.py', 63 | #* force_all_pass = force_all_pass) 64 | #* 65 | #******************************************************************************* 66 | 67 | # 68 | # logic here to process environment variables. 69 | # 70 | loglevel = os.environ.get('loglevel', 'INFO') 71 | groups = os.environ.get('execution_group', None) 72 | my_variable = os.environ.get('my_variable', 'default_value') 73 | 74 | #******************************************************************************* 75 | #* PARSING COMMAND LINE ARGUMENTS 76 | #* 77 | #* Easypy & AEtest features argument propagation: propagating custom command 78 | #* line arguments to jobfile & the testscript. In a nutshell, the requirement 79 | #* is simple: parse only what you need using parse_known_args(), leave the 80 | #* rest in sys.argv. 81 | #* 82 | #* If your jobfile requires additional command line arguments, you'll need to 83 | #* create an argparse section here. 84 | #* 85 | #* note - argparse modules are already imported for your convenience above. 86 | #* 87 | #******************************************************************************* 88 | 89 | # 90 | # create your custom job file argument parser here 91 | # 92 | parser = argparse.ArgumentParser(description='example job file cli args parser') 93 | parser.add_argument('--argument_a', 94 | help='example argument a', 95 | default = None) 96 | parser.add_argument('--argument_b', 97 | help='example argument b', 98 | default = None) 99 | 100 | 101 | #******************************************************************************* 102 | #* TESTBED INFORMATION 103 | #* 104 | #* when executing a jobfile using pyats, testbed file should be provided to 105 | #* pyats job launcher using argument --testbed-file. The engine will then 106 | #* load this provided testbed file into topology objects, and pass it to each 107 | #* testscript as script argument "testbed". 108 | #* pyats run job myjobfile.py --testbed-file mytestbed.yaml 109 | #* 110 | #* as long as the argument is used properly, there's nothing extra the user 111 | #* has to do. Testscripts will automatically be passed the parameter 'testbed', 112 | #* along with all the topology objects. 113 | #* 114 | #* 115 | #* TOPOLOGY INFORMATION 116 | #* 117 | #* in an effort to abstract the script's topology/device requirements away 118 | #* from the actual testbed being used, user may choose to provide certain 119 | #* alias/uut information as script arguments. 120 | #* 121 | #* the idea behind it is simple: decouple the testscript from hard-coding 122 | #* device and interface/link names in the script by: 123 | #* - providing a label mapping from the jobfile, and/or 124 | #* - using the topology alias feature. 125 | #* 126 | #* topology module/objects supports alias-lookups: refering to testbed devices, 127 | #* links and interfaces by an 'alternative name', which maps to the actual HW 128 | #* names defined in testbed YAML file. 129 | #* 130 | #* in addition, users may also choose to provide a dictionary argument, mapping 131 | #* labels referenced in the testscript vs actual device names, and make use of 132 | #* this information within their testscripts. Eg: 133 | #* labels = { 134 | #* 'pe1': 'device_one', 135 | #* 'uut': 'device_two', 136 | #* } 137 | #* 138 | #* it is also a good idea to pass in a list of devices and links to be used 139 | #* by the testscript. This allows configurability of the testscripts, and 140 | #* choosing which links/interfaces/devices of the current topology to test on. 141 | #* routers = ['device_one', 'device_two', 'device_three'] 142 | #* links = ['link_one', 'link_two', 'link_three'] 143 | #* tgns = [] 144 | #* 145 | #* Handling Multiple Testbeds 146 | #* -------------------------- 147 | #* in the case where your job file is shared across multiple testbeds, you can 148 | #* test for the current testbed object and set your topology information 149 | #* accordingly. Eg: 150 | #* 151 | #* if runtime.testbed: 152 | #* 153 | #* if runtime.testbed.name == 'testbed_ONE': 154 | #* labels = {