├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── LICENSE ├── Manifest.in ├── README.md ├── TODO.py ├── docs ├── Makefile ├── api.rst ├── changelog.rst ├── conf.py ├── demand.rst ├── development.rst ├── examples.rst ├── index.rst ├── install.rst ├── interface.rst ├── make.bat ├── model.rst ├── model_file.rst ├── pyNTM_logo.png ├── rsvp_lsp.rst ├── srlg.rst ├── visualization.rst └── workflows.rst ├── examples ├── flex_model_parallel_source_dest_lsps.csv ├── igp_shortcuts_manual_metrics_prototype.py ├── igp_shortcuts_model_mult_lsps_in_path.csv ├── igp_shortcuts_model_mult_lsps_in_path_parallel_links_2.csv ├── igp_shortcuts_prototype.py ├── lsp_manual_metric_perf_model_prototype.py ├── lsp_model_test_file.csv ├── lsp_practice_code.py ├── model_test_topology_flexmodel.csv ├── network_modeling_client_code_examples_read_from_dict.py ├── network_modeling_with_visualization.py ├── parallel_link_model_with_visualization.py ├── perf_model_lsp_metric.csv ├── sample_network_model_file.csv ├── traffic_eng_example_model.csv ├── traffic_eng_feature_example.py └── vis_training.py ├── prototypes └── pypy3_pytest_notes.txt ├── pyNTM ├── __init__.py ├── circuit.py ├── demand.py ├── exceptions.py ├── flex_model.py ├── interface.py ├── master_model.py ├── node.py ├── performance_model.py ├── rsvp.py ├── srlg.py ├── utilities.py └── weathermap.py ├── requirements.txt ├── requirements_dev.txt ├── requirements_dev_pypy.txt ├── setup.cfg ├── setup.py └── test ├── __init__.py ├── flex_model_parallel_source_dest_lsps.csv ├── igp_routing_topology.csv ├── igp_shortcuts_model_mult_lsps_in_path.csv ├── igp_shortcuts_model_mult_lsps_in_path_parallel_links.csv ├── igp_shortcuts_model_mult_lsps_in_path_parallel_links_2.csv ├── interface_field_info_missing_routing_topology.csv ├── interface_field_info_missing_routing_topology_multidigraph.csv ├── lsp_configured_setup_bw_model.csv ├── lsp_manual_metric_test_model.csv ├── manual_lsp_metric_test_flex_model.csv ├── mismatched_ckt_int_capacity_topology_file.csv ├── mismatched_ckt_int_capacity_topology_parallel_links.csv ├── model_bad_node_in_demand.csv ├── model_bad_node_in_lsp.csv ├── model_test_topology.csv ├── model_test_topology_2.csv ├── multiple_rsvp_paths.csv ├── parallel_link_model_bad_circuit_id.csv ├── parallel_link_model_bad_node_in_demand.csv ├── parallel_link_model_bad_node_in_lsp.csv ├── parallel_link_model_test_topology.csv ├── parallel_link_model_test_topology_igp_only.csv ├── parallel_link_model_w_lsps.csv ├── rsvp_lsp_effective_metric_update.csv ├── test_circuit.py ├── test_demand.py ├── test_demand_parallel_link_model.py ├── test_igp_ecmp_routing.py ├── test_igp_shortcuts.py ├── test_interface.py ├── test_interface_parallel_link_model.py ├── test_model.py ├── test_node.py ├── test_parallel_link_model.py ├── test_rsvp_3rd_lsp_2_paths_parallel_links.csv ├── test_rsvp_3rd_lsp_two_paths.py ├── test_rsvp_3rd_lsp_two_paths_parallel_links.py ├── test_rsvp_4th_lsp_two_paths.py ├── test_rsvp_configured_setup_bw.py ├── test_rsvp_int_failure_traff_add.py ├── test_rsvp_manual_metric.py ├── test_rsvplsp_int_failure.py ├── test_rsvplsp_path_selection.py ├── test_rsvplsp_setup.py ├── test_srlg.py ├── test_srlg_parallel_link_model.py ├── test_traffic_eng_features.py ├── test_utilities.py ├── traffic_eng_test_model.csv └── traffic_eng_test_parallel_link_model.csv /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | .pytest_cache 31 | htmlcov/ 32 | .tox/ 33 | .coverage 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | .DS_Store 57 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: stable 4 | hooks: 5 | - id: black 6 | language_version: python3.8 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | os: linux 3 | dist: focal 4 | stages: 5 | - test 6 | jobs: 7 | include: 8 | - stage: test 9 | language: python 10 | dist: focal 11 | python: 3.6 12 | install: 13 | - pip install -r requirements.txt 14 | - pip install -r requirements_dev.txt 15 | - pip install . 16 | script: 17 | - pytest 18 | - black . 19 | after_success: coveralls 20 | deploy: 21 | provider: pypi 22 | username: __token__ 23 | password: $PYPI_UPLOAD_TOKEN 24 | distributions: sdist bdist_wheel 25 | skip_existing: true 26 | on: 27 | tags: true 28 | branch: master 29 | - stage: test 30 | language: python 31 | dist: focal 32 | python: 3.7 33 | install: 34 | - pip install -r requirements.txt 35 | - pip install -r requirements_dev.txt 36 | - pip install . 37 | script: 38 | - pytest 39 | - black . 40 | after_success: coveralls 41 | deploy: 42 | provider: pypi 43 | username: __token__ 44 | password: $PYPI_UPLOAD_TOKEN 45 | distributions: sdist bdist_wheel 46 | skip_existing: true 47 | on: 48 | tags: true 49 | branch: master 50 | - stage: test 51 | language: python 52 | dist: focal 53 | python: 3.8 54 | install: 55 | - pip install -r requirements.txt 56 | - pip install -r requirements_dev.txt 57 | - pip install . 58 | script: 59 | - pytest 60 | - black . 61 | after_success: coveralls 62 | deploy: 63 | provider: pypi 64 | username: __token__ 65 | password: $PYPI_UPLOAD_TOKEN 66 | distributions: sdist bdist_wheel 67 | skip_existing: true 68 | on: 69 | tags: true 70 | branch: master 71 | - stage: test 72 | language: python 73 | dist: focal 74 | python: pypy3 75 | install: 76 | - pip install -r requirements.txt 77 | - pip install -r requirements_dev_pypy.txt 78 | - pip install . 79 | script: 80 | - pytest 81 | after_success: coveralls 82 | deploy: 83 | provider: pypi 84 | username: __token__ 85 | password: $PYPI_UPLOAD_TOKEN 86 | distributions: sdist bdist_wheel 87 | skip_existing: true 88 | on: 89 | tags: true 90 | branch: master -------------------------------------------------------------------------------- /Manifest.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![PyPI](https://img.shields.io/pypi/v/pyntm.svg)](https://pypi.python.org/pypi/pyNTM) 3 | [![Build Status](https://app.travis-ci.com/tim-fiola/network_traffic_modeler_py3.svg?branch=master)](https://app.travis-ci.com/tim-fiola/network_traffic_modeler_py3) 4 | [![Coverage Status](https://coveralls.io/repos/github/tim-fiola/network_traffic_modeler_py3/badge.svg?branch=master)](https://coveralls.io/github/tim-fiola/network_traffic_modeler_py3?branch=master) 5 | [![Documentation Status](https://readthedocs.org/projects/pyntm/badge/?version=latest)](https://pyntm.readthedocs.io/en/latest/?badge=latest) 6 | 7 | 8 | pyNTM: network_traffic_modeler_py3 9 | ================================== 10 | 11 | * How will a failure on your wide area network (WAN) affect link utilizations? 12 | * What about a layer 3 node failure? 13 | * Will all of your RSVP auto-bandwidth LSPs be able to resignal after a link/node failure? 14 | * Can you WAN handle a 10% increase in traffic during steady state? What about failover state? 15 | 16 | These questions are non-trivial to answer for a medium to large size WAN that is meshy/interconnected, and these are the exact scenarios that a WAN simulation engine is designed to answer. 17 | 18 | This is a network traffic modeler written in python 3. The main use cases involve understanding how your layer 3 traffic will transit a given topology. You can modify the topology (add/remove layer 3 Nodes, Circuits, Shared Risk Link Groups), fail elements in the topology, or add new traffic Demands to the topology. pyNTM is a simulation engine that will converge the modeled topology to give you insight as to how traffic will transit a given topology, either steady state or with failures. 19 | 20 | This library allows users to define a layer 3 network topology, define a traffic matrix, and then run a simulation to determine how the traffic will traverse the topology, traverse a modified topology, and fail over. If you've used Cariden MATE or WANDL, this code solves for some of the same basic use cases those do. This package is in no way related to those, or any, commercial products. IGP and RSVP routing is supported. 21 | 22 | pyNTM can be used as an open source solution to answer WAN planning questions; you can also run pyNTM alongside a commercial solution as a validation/check on the commercial solution. 23 | 24 | 25 | Training 26 | ========= 27 | See the training modules at https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM- 28 | 29 | 30 | Documentation 31 | ============= 32 | 33 | See the documentation on [Read the Docs](http://pyntm.readthedocs.org). 34 | 35 | 36 | Examples 37 | ======== 38 | 39 | See the [example directory](https://github.com/tim-fiola/network_traffic_modeler_py3/blob/master/examples). 40 | 41 | Install 42 | ======= 43 | 44 | Install via pip: 45 | ```bash 46 | pip3 install pyNTM 47 | ``` 48 | 49 | For upgrade: 50 | ```bash 51 | pip3 install --upgrade pyNTM 52 | ``` 53 | 54 | 55 | pyNTM Model Classes 56 | ================================== 57 | In pyNTM, the Model objects house the network topology objects: traffic Demands, layer 3 Nodes, Circuits, Shared Risk Link Groups (SRLGs), Interfaces, etc. The Model classes control how all the contained objects interact with each other during Model convergence to produce simulation results. 58 | 59 | There are two subclasses of Model objects: the PerformanceModel object and the newer FlexModel object (introduced in version 1.6). 60 | Starting in version 1.7, what used to be called the Model class is now the PerformanceModel Class. The former Parallel_Link_Model 61 | class is now known as the FlexModel class. 62 | There are two main differences between the two types of objects: 63 | - The PerformanceModel object only allows a single Circuit between two layer 3 Nodes; while the FlexModel allows multiple Circuits between the same two Nodes. 64 | - The Performance Model will have better performance (measured in time to converge) than the FlexModel. This is because the FlexModel has additional checks to account for potential multiple Circuits between Nodes and other topology features such as IGP shortcuts. 65 | 66 | The legacy Model and Parallel_Link_Model should still work as they have been made subclasses of the PerformanceModel and FlexModel classes, respectively. 67 | 68 | The PerformanceModel class is good to use for the following topology criteria: 69 | - There is only one link (Circuit) between each layer 3 Node 70 | - IGP-only routing and/or RSVP LSP routing with no IGP shortcuts (traffic source and destination matches LSP source and destination) 71 | 72 | 73 | Which Model Class To Use 74 | ================================== 75 | All model classes support: 76 | - IGP routing 77 | - RSVP LSPs carrying traffic demands that have matching source and destination as the RSVP LSPs 78 | - RSVP auto-bandwidth or fixed bandwidth 79 | - RSVP LSP manual metrics 80 | 81 | The PerformanceModel class allows for: 82 | - Single Circuits between 2 Nodes 83 | - Error messages if it detects use of IGP shortcuts or multiple Circuits between 2 Nodes 84 | 85 | The FlexModel class allows for: 86 | - Multiple Circuits between 2 Nodes 87 | - RSVP LSP IGP shortcuts, whereby LSPs can carry traffic demands downstream, even if the demand does not have matching source and destination as the LSP 88 | 89 | In some cases, it's completely valid to model multiple Circuits between Nodes as a single Circuit. For example: in the case where there are multiple Circuits between Nodes but each Interface has the same metric and the use case is to model capacity between Nodes, it's often valid to combine the Circuit capacities and model as a single Circuit. In this case, the PerformanceModel object is recommended as it will give better performance. 90 | 91 | If it is important to keep each Circuit modeled separately because the parallel Interfaces have different metrics and/or differences in their capabilities to route RSVP, the FlexModel is the better choice. 92 | 93 | If there is any doubt as to which class to use, use the FlexModel class. 94 | 95 | Optimization 96 | ================================== 97 | 98 | There are two main areas where we are looking to optimize: 99 | - Performance - converging the model to produce a simulation, especially in a model with RSVP LSPs, is intensive. Improving the time it takes to converge the simulation results in better productivity and improved user experience 100 | - pyNTM supports the pypy3 interpreter, which results in 60-90% better performance than the python3 interpreter 101 | - In this case, *performance* refers to how long it takes to converge a model to produce a simulation; for larger models, pypy3 provides much better performance 102 | - Data retrieval - the simulation produces an extraordinary amount of data. Currently, the model is only retaining a fraction of the data generated during the model convergence. It's our goal to introduce something like an sqlite database in the model objects to hold all this information. This will improve user experience and allow SQL queries against the model object. 103 | 104 | 105 | Visualization 106 | ============= 107 | Info about the new WeatherMap class that provides visualization is available in the wiki: https://github.com/tim-fiola/network_traffic_modeler_py3/wiki/Visualizing-the-network-with-the-WeatherMap-Class 108 | 109 | 110 | License 111 | ======= 112 | 113 | Copyright 2019 Tim Fiola 114 | 115 | Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 116 | -------------------------------------------------------------------------------- /TODO.py: -------------------------------------------------------------------------------- 1 | # TODO: 2 | # ===== 3 | # TODO - Use Cases TO DO: 4 | # - Specific Model calls to 5 | # - Remove a Node 6 | # - Remove an Interface/Circuit 7 | # - Remove a demand 8 | # - Change demand magnitude 9 | # *** These can all be done now, but require a few API calls to do so 10 | 11 | # TODO - example script with SRLGs 12 | 13 | # TODO - Node tags 14 | # - add node tags for organizing/grouping/filtering-to nodes 15 | # - for network graph plot, have option to only show nodes with 16 | # certain user specified tags 17 | 18 | # TODO - Use Cases TO DO: 19 | # - Save an existing model to a file 20 | # - Specific Model calls to 21 | # - Remove a Node 22 | # - Remove an Interface/Circuit 23 | # - Remove a demand 24 | # - Change demand magnitude 25 | # *** These can all be done now, but require a few API calls to do s 26 | 27 | # TODO - User experience TO DO: 28 | # - save_model call that will save model data to file 29 | # - new client code example that uses a save_model call 30 | 31 | # TODO - test model with demands over LSPs and routed demands in same Model - check to see if did this 32 | 33 | # TODO - travis.yml - add jobs.include again to get pypy interpreter tested 34 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | ======= 3 | 4 | _MasterModel 5 | -------------- 6 | .. autoclass:: pyNTM.master_model._MasterModel 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | PerformanceModel 12 | ----------------- 13 | .. autoclass:: pyNTM.performance_model.PerformanceModel 14 | :members: 15 | :undoc-members: 16 | :show-inheritance: 17 | 18 | FlexModel 19 | ------------ 20 | .. autoclass:: pyNTM.flex_model.FlexModel 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | Node 26 | ---------- 27 | .. autoclass:: pyNTM.node.Node 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | Interface 33 | ---------- 34 | .. autoclass:: pyNTM.interface.Interface 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Demand 40 | ---------- 41 | .. autoclass:: pyNTM.demand.Demand 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | Circuit 47 | ---------- 48 | .. autoclass:: pyNTM.circuit.Circuit 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | 53 | RSVP_LSP 54 | ---------- 55 | .. autoclass:: pyNTM.rsvp.RSVP_LSP 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | Exceptions 61 | ---------- 62 | .. automodule:: pyNTM.exceptions 63 | :members: 64 | :undoc-members: 65 | :show-inheritance: 66 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 3.4.1 5 | ----- 6 | * Updated test environment to Focal linux (from Xenial) to allow ``dash`` and ``dash-cytoscape`` package import in CI/CD for visualization 7 | 8 | * Allows for moving WeatherMap from beta to production in future 9 | * It's no longer necessary to explicitly install the visualization requirements separately 10 | 11 | * Heavy updates for docs 12 | * Implemented black code formatting for local commits and in Travis CI/CD pipeline 13 | * Implemented standard column name for RSVP LSP attribute to describe a manually assigned metric: 14 | 15 | * FlexModel had ``lsp_metric`` column name in docstrings and examples 16 | * PerformanceModel had ``manual_metric`` column name in docstrings and examples 17 | * Standardized column name to ``manual_metric`` 18 | * This was a purely cosmetic change as the actual ``manual_metric`` is based on the column's order in the table, not the specific column name 19 | 20 | 21 | 3.4.0 22 | ----- 23 | * This build, while functional, was yanked from pypi for reasons related to troubleshooting a new CI/CD pipeline. Let's just say there was some drama around that, and the migration to travis-ci.com. These things happen. 24 | * 3.4.0 features moved to 3.4.1 build 25 | 26 | 3.3.1 27 | ----- 28 | * Fixed bug in FlexModel to account for complex topology scenario involving ECMP demand paths with multipe IGP shortcut LSPs and parallel links 29 | * Added spacing_factor as a WeatherMap configurable parameter 30 | * Added another test to functional tests within test_parallel_link_model for complex topology 31 | 32 | 3.3 33 | --- 34 | * Import of the WeatherMap class must be done from pyNTM.weathermap instead of directly from pyNTM. This prevents a warning message that is otherwise superficial unless you are using the WeatherMap class 35 | 36 | 3.2 37 | --- 38 | * Fixed bug in WeatherMap class that caused scripts with WeatherMap to run 2x and to not be able to run a WeatherMap class live from the python3 CLI 39 | 40 | 3.1 41 | --- 42 | * Removed automatic call of load_model_file class methods performing update_simulation() call automatically. 43 | 44 | 3.0 45 | --- 46 | * WeatherMap class added as beta feature to provide interactive visualization of network topology. This is a beta feature and is not undergoing unit testing. This feature is supported in the python3 interpreter, but not in the pypy3 interpreter. This feature gives a very interactive and informative visualization of the topology 47 | * path_detail Demand property support in all Model classes; provides clarity on how much traffic is passing on a given Demand's path and how much of that traffic transits each component in the path 48 | * Python 3.5 no longer supported 49 | * Python 3.8 support added to unit/functional testing 50 | * load_model_file class methods now perform update_simulation() call automatically. The update_simulation() call is only necessary to run after making a change to the topology after the model file has been loaded 51 | 52 | 53 | 2.1 54 | --- 55 | * Enforcing tab-separated data in model data files (used to allow spaces or tabs between data entries) 56 | * FlexModel class allows IGP RSVP shortcuts 57 | * FlexModel and PerformanceModel classes allow/honor RSVP LSP manual metrics 58 | * Made load_model_file for FlexModel and PerformanceModel classes more forgiving for number of lines allowed between tables 59 | 60 | 2.0 61 | --- 62 | * Made version 1.7 into major version 2.0 to account for possible backwards compatibilty 63 | 64 | 1.7 65 | --- 66 | * Renamed Model class to PerformanceModel 67 | * Renamed Parallel_Link_Model class to FlexModel 68 | * Optimization: general 18-25% performance improvement when measured by time to converge 69 | * Moved common code from PerformanceModel and FlexModel to _MasterModel parent class 70 | * Maintained unit testing coverage at 95% 71 | * Cleaned up documentation/docstrings 72 | 73 | 1.6 74 | --- 75 | * Added support for multiple links between nodes (Parallel_Link_Model) 76 | * Cached parallel_lsp_groups in Model and Parallel_Link_Model objects (performance optimization) 77 | * Added check for multiple links between nodes in Model object (not allowed) 78 | * Added Parent Class _MasterModel to hold common defs for Model and Parallel_Link_Model subclasses 79 | * Added simulation_diagnostics def in _MasterModel that gives potentially useful diagnostic info about the simulation results 80 | * Simple user interface (beta feature) supports RSVP LSPs 81 | 82 | 1.5 83 | --- 84 | * Updated code to account for networkx 85 | * Improved some docstrings 86 | 87 | 88 | 1.4 89 | --- 90 | * updated requirements.txt to allow use of beta features 91 | 92 | 93 | 1.3 94 | --- 95 | * improved docstring for Model load_model_file class method 96 | * updated requirements 97 | * fixed bugs in beta features: visualization and simple UI 98 | * updated unit testing 99 | 100 | 101 | 1.2 102 | --- 103 | * added shared-risk link group (SRLG) support for Nodes and Interfaces 104 | * added performance optimizations 105 | * simplified sections of code 106 | 107 | 1.1 108 | ---- 109 | * added configured, fixed setup bandwidth capability on RSVP LSPs 110 | * made small performance optimizations 111 | 112 | 1.0 113 | ---- 114 | * first release including pypi inetgration 115 | 116 | 117 | 118 | previous releases 119 | ------------------ 120 | * versions prior to v1.0 were not released to pip, but distributed as a github directory 121 | * initially a py2 version was made available `here `_ 122 | * the py2 version is not maintained anymore in favor of the current py3 releases 123 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../")) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = "pyNTM" 22 | copyright = "2019, Tim Fiola" 23 | author = "Tim Fiola" 24 | 25 | # The full version, including alpha/beta/rc tags 26 | release = "3.4.1" 27 | 28 | # index file 29 | master_doc = "index" 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | "sphinx.ext.todo", 38 | "sphinx.ext.viewcode", 39 | "sphinx.ext.autodoc", 40 | "sphinx.ext.autosectionlabel", 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ["_templates"] 45 | 46 | # List of patterns, relative to source directory, that match files and 47 | # directories to ignore when looking for source files. 48 | # This pattern also affects html_static_path and html_extra_path. 49 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 50 | 51 | 52 | # -- Options for HTML output ------------------------------------------------- 53 | 54 | # The theme to use for HTML and HTML Help pages. See the documentation for 55 | # a list of builtin themes. 56 | # 57 | html_theme = "sphinx_rtd_theme" 58 | 59 | # Add any paths that contain custom static files (such as style sheets) here, 60 | # relative to this directory. They are copied after the builtin static files, 61 | # so a file named "default.css" will overwrite the builtin "default.css". 62 | html_static_path = ["_static"] 63 | -------------------------------------------------------------------------------- /docs/demand.rst: -------------------------------------------------------------------------------- 1 | Demands 2 | ======= 3 | 4 | A representation of traffic load on the modeled network. 5 | 6 | A demand object must have a source node, a destination node, and a name. 7 | 8 | Demand Units 9 | ************ 10 | 11 | A demand's ``traffic`` property shows how many traffic *units* the demand carries. The ``traffic`` property is unit-less, meaning it is not described in Mbps, Gbps, etc. 12 | It is written in this generic way so that the user can determine the traffic units they want to deal with in their modeling exercises. 13 | 14 | For example, if a demand has ``traffic`` = 100, it can be 100Mpbs, 100Gbps, etc. This example, will use Gbps. 15 | 16 | If the entire demand transits a single interface (# ECMP = 1) with a ``capacity`` of 200 Gbps (and no other demands transit the interface), the interface's computed ``utilization`` will be 50%. 17 | 18 | A demand's path is 19 | 20 | 21 | The ``path_detail`` Property 22 | **************************** 23 | 24 | The demand object's ``path_detail`` property can be very useful to determine how much of the demand's 25 | traffic egresses each object (interface, LSP) in the path:: 26 | 27 | Returns a detailed breakdown of the Demand path. 28 | Each path will have the following information: 29 | 30 | items: The combination of Interfaces and/or LSPs that the Demand transits 31 | from source to destination 32 | 33 | splits: each item on the path (Interface and/or LSP) and the number of cumulative 34 | ECMP path splits that the Demand has transited as it egresses the source node for 35 | that element. 36 | 37 | path_traffic: the amount of traffic on that specific path for the demand. Path traffic will be the 38 | result of dividing the Demand's traffic by the **max** amount of path splits for an 39 | element in the path 40 | 41 | 42 | For example, sample demand ``Demand(source = A, dest = E, traffic = 24, name = 'dmd_a_e_1')`` has 24 units of traffic. 43 | 44 | Here is the ``path_0`` entry for the sample demand:: 45 | 46 | 'path_0': { 47 | 'items': [Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), 48 | remote_node_object = Node('B'), circuit_id = '1'), 49 | Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), 50 | remote_node_object = Node('E'), circuit_id = '27') 51 | ], 52 | 'path_traffic': 4.0, 53 | 'splits': {Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), 54 | remote_node_object = Node('B'), circuit_id = '1'): 2, 55 | Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), 56 | remote_node_object = Node('E'), circuit_id = '27'): 6} 57 | } 58 | 59 | 60 | The ``path_0`` component of the ``path_detail`` property in this example shows the following: 61 | 62 | * ``Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1')`` has **2** splits 63 | * ``Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), remote_node_object = Node('E'), circuit_id = '27')`` has **6** splits 64 | 65 | To get the amount of traffic load from the specific demand that transits each interface, divide the amount of traffic that the demand has by the number of splits for the object: 66 | 67 | * ``Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1')`` carries **24 / 2 = 12** units of traffic from ``Demand(source = A, dest = E, traffic = 24, name = 'dmd_a_e_1')`` 68 | * ``Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), remote_node_object = Node('E'), circuit_id = '27')`` carries **24 / 6 = 4** units of traffic from ``Demand(source = A, dest = E, traffic = 24, name = 'dmd_a_e_1')`` 69 | 70 | Since the minimum amount of traffic found on any object in ``path_0`` is 4 units of traffic, ``path_traffic`` for ``path_0`` = 4. 71 | 72 | For more information on demands,see the `demand docstrings`_. 73 | 74 | .. _demand docstrings: ./api.html#demand 75 | 76 | Please see the `pyNTM Training Modules repository module 2`_ for info and walk-through exercises for demands, including: 77 | 78 | * Finding traffic demands egressing a given interface 79 | * Finding all ECMP paths for a specific demand 80 | 81 | .. _pyNTM Training Modules repository module 2: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM-/blob/master/pyNTM_training_module_2_v2.pdf 82 | 83 | 84 | -------------------------------------------------------------------------------- /docs/development.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | If you wish to contribute PRs to pyNTM, the sections below describe how to set up your development environment. 5 | 6 | Currently, pyNTM adheres to the *black* code formatting and *flake8*. More info on *black* is below. 7 | 8 | The *black* code formatting and *flake8* checks occur in the CI/CD pipeline. 9 | 10 | The sections below cover how to set up your local dev environment to run these checks prior to submitting a pull request. 11 | 12 | Set Up Your Virtual Environment 13 | ------------------------------- 14 | 15 | Set up your virtual environment. 16 | 17 | `Virtualenv `_ is a tool for creating isolated 'virtual' python environments. For directions on how to run this modeler in a virtual environment and auto-download all dependencies, follow the steps below (authored by nelsg). 18 | 19 | Create your virtualenv 20 | ********************** 21 | 22 | Create an isolated virtual environment under the directory "network_traffic_modeler_py3" with python3:: 23 | 24 | $ virtualenv -p python3 venv 25 | 26 | Activate "venv" that sets up the required env variables:: 27 | 28 | $ source venv/bin/activate 29 | 30 | 31 | Clone the repo per https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository#cloning-a-repository:: 32 | 33 | (venv) % git clone https://github.com/tim-fiola/network_traffic_modeler_py3.git 34 | Cloning into 'network_traffic_modeler_py3'... 35 | remote: Enumerating objects: 1622, done. 36 | remote: Counting objects: 100% (378/378), done. 37 | remote: Compressing objects: 100% (256/256), done. 38 | remote: Total 1622 (delta 247), reused 214 (delta 120), pack-reused 1244 39 | Receiving objects: 100% (1622/1622), 770.31 KiB | 1.89 MiB/s, done. 40 | Resolving deltas: 100% (1107/1107), done. 41 | (venv) % 42 | 43 | Install pyNTM's required packages with "pip3":: 44 | (venv) % cd network_traffic_modeler_py3 45 | (venv) % pip3 install -r requirements.txt 46 | 47 | Installing Development Requirements 48 | ----------------------------------- 49 | 50 | To install the Python modules required for development, run:: 51 | 52 | % pip3 install -r requirements_dev.txt 53 | 54 | The Black Code Formatter 55 | ************************ 56 | 57 | pyNTM uses the `black `_ code formatter. ``black`` is an opinionated formatter that 58 | will make changes to your files to bring them in line with the ``black`` standards. 59 | 60 | Setting Up Pre-Commit Hooks 61 | *************************** 62 | 63 | The ``pre-commit`` package is installed when you install the ``requirements_dev.txt``. 64 | 65 | To run ``black`` automatically after each commit, you will need to install the pre-commit hooks. 66 | 67 | Run ``pre-commit install``:: 68 | 69 | % pre-commit install 70 | 71 | You should see a response that the hook has been installed:: 72 | 73 | pre-commit installed at .git/hooks/pre-commit 74 | 75 | This will set up a check that runs ``black`` prior to allowing a commit, allowing you to focus on making your code, instead of worrying about your formatting. 76 | 77 | Local Unit Testing 78 | ------------------ 79 | 80 | To run the unit tests locally: 81 | 82 | 1. Go to the repository's ``network_traffic_modeler_py3`` directory:: 83 | 84 | (venv) % pwd 85 | /path/to/network_traffic_modeler_py3 86 | (venv) % 87 | 88 | 89 | 2. Run ``pytest``:: 90 | 91 | ``% pytest`` 92 | 93 | If the tests fail to run due to ``ImportError``, depending on your OS, you may need to run one of the following ``pytest`` variations:: 94 | 95 | ``python -m pytest`` 96 | 97 | or:: 98 | 99 | ``python3 -m pytest`` 100 | 101 | .. tip:: 102 | When submitting a pull request, your build will be tested against black and the unit tests, so it's advantageous to test them locally prior so they don't fail in the CI pipeline. 103 | 104 | Remove your virtualenv 105 | ---------------------- 106 | 107 | If you wish to remove your virtualenv when you are complete, follow the steps below. 108 | 109 | Deactivate "venv" that unsets the virtual env variables:: 110 | 111 | $ deactivate 112 | 113 | Remove directory "venv":: 114 | 115 | $ rm -rf venv 116 | 117 | pypy3 118 | ----- 119 | 120 | pyNTM is compatible with the pypy3 interpreter. The pypy3 interpreter provides a 70-80% performance improvement over the python3 interpreter. 121 | 122 | .. tip:: 123 | By *performance*, we mean the time it takes to converge the model to produce a simulation (running ``update_simulation``). 124 | 125 | It is recommended, however, to *develop* in **python3**. Developing in **pypy3** is **NOT** recommended, because some of the developer tools are not compatible in a pypy3 environment. 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ========= 3 | 4 | Demo/Example scripts 5 | -------------------- 6 | 7 | These scripts are in the ``examples`` directory and are meant to showcase different capabilities and how to use them. 8 | 9 | The ``lsp_practice_code.py`` script demos how auto-bandwidth RSVP LSPs react to 10 | 11 | * link failures 12 | * adding traffic 13 | * adding additional LSPs 14 | 15 | The ``network_modeling_client_code_examples_read_from_dict.py`` file demo2 the following: 16 | 17 | * loading a network topology from a list of info (instead of a model file) 18 | * addition of new circuit and node to the network 19 | * viewing interface traffic 20 | * getting the shortest path 21 | * failing an interface 22 | * demand path changes before/after a link failure 23 | * adding traffic 24 | 25 | Demo Script With Visualization 26 | ------------------------------ 27 | 28 | There is also a visualization script, which showcases a current beta feature. 29 | See the `visualization `_ docs page for more details. 30 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyNTM documentation master file, created by 2 | sphinx-quickstart on Wed May 22 18:43:55 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pyNTM's documentation! 7 | ================================== 8 | 9 | This is a network traffic modeler written in python 3. This library allows users to define a layer 3 network topology, define a traffic matrix, and then run a simulation to determine how the traffic will traverse the topology. If you've used Cariden MATE or WANDL, this code solves for some of the same basic use cases those do. 10 | 11 | Typical Use Cases 12 | ----------------- 13 | 14 | * How will a failure on your wide area network (WAN) affect link utilizations? 15 | * What about a layer 3 node failure? 16 | * Will all of your RSVP auto-bandwidth LSPs be able to resignal after a link/node failure? 17 | * Can you WAN handle a 10% increase in traffic during steady state? What about failover state? 18 | 19 | These questions are non-trivial to answer for a medium to large size WAN that is meshy/interconnected, and these are the exact scenarios that a WAN simulation engine is designed to answer. 20 | 21 | Changes to the topology can be done to simulate new routers, circuits, circuit capacity, network failures, etc. Changes to the traffic matrix can be done to simulate increases/decreases in existing traffic or additional traffic matrix entries. 22 | 23 | This is the network traffic modeler written in python 3. The main use cases involve understanding how your layer 3 traffic will transit a given topology. You can modify the topology (add/remove layer 3 Nodes, Circuits, Shared Risk Link Groups), fail elements in the topology, or add new traffic Demands to the topology. pyNTM is a simulation engine that will converge the modeled topology to give you insight as to how traffic will transit a given topology, either steady state or with failures. 24 | 25 | This library allows users to define a layer 3 network topology, define a traffic matrix, and then run a simulation to determine how the traffic will traverse the topology, traverse a modified topology, and fail over. If you've used Cariden MATE or WANDL, this code solves for some of the same basic use cases those do. This package is in no way related to those, or any, commercial products. IGP and RSVP routing is supported. 26 | 27 | pyNTM can be used as an open source solution to answer WAN planning questions; you can also run pyNTM alongside a commercial solution as a validation/check on the commercial solution. 28 | 29 | 30 | Example Scripts and Training 31 | ---------------------------- 32 | 33 | See the [example directory](https://github.com/tim-fiola/network_traffic_modeler_py3/blob/master/examples). 34 | 35 | Examine and run the `client code examples`_, these docs, and check out the `pyNTM training repository`_ to get an understanding of how to use this code and the use cases. 36 | 37 | .. _pyNTM training repository: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM- 38 | 39 | .. _client code examples: https://github.com/tim-fiola/network_traffic_modeler_py3/tree/master/examples 40 | 41 | 42 | Full API set use cases include 43 | ------------------------------- 44 | 45 | * simulating how traffic will transit your layer 3 network, given a traffic matrix 46 | * simulating how your traffic will failover if any link(s) or node(s) fail 47 | * simulating how a given network augment will affect link utilization and traffic flow, possible augments include 48 | 49 | - adding a new link 50 | - adding a new node with links 51 | - changing a link's capacity 52 | - adding a new traffic matrix entry (demand) to the traffic matrix 53 | - increasing/decreasing the magnitude of an existing demand in the traffic matrix failover 54 | 55 | Note: interface circuit_ids are only used to match interfaces into circuits and do not have any practical bearing on the simulation results 56 | 57 | Contents 58 | ========== 59 | 60 | .. toctree:: 61 | :maxdepth: 2 62 | 63 | install 64 | examples 65 | model 66 | model_file 67 | rsvp_lsp 68 | demand 69 | interface 70 | srlg 71 | workflows 72 | visualization 73 | development 74 | api 75 | changelog 76 | 77 | Indices and tables 78 | =================== 79 | 80 | * :ref:`genindex` 81 | * :ref:`modindex` 82 | * :ref:`search` 83 | 84 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Install 2 | ======= 3 | 4 | To use the library as a module, it is recommended to install it via pip as shown below. 5 | 6 | Install via pip:: 7 | 8 | $ pip3 install pyNTM 9 | 10 | To upgrade:: 11 | 12 | $ pip3 install --upgrade pyNTM 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/interface.rst: -------------------------------------------------------------------------------- 1 | Interfaces 2 | ========== 3 | 4 | Interfaces represent uni-directional interfaces on a layer 3 node. 5 | 6 | A **circuit** is created when two *matching* interfaces (one in each direction) are paired to form a bi-directional connection between layer 3 nodes. 7 | 8 | Interfaces are matched into a circuit if: 9 | 10 | * They are on different nodes 11 | * They have the same ``capacity`` 12 | * They have a matching ``circuit_id`` 13 | 14 | .. important:: 15 | In the ``PerformanceModel`` subclass, the ``circuit_id`` is hidden because this subclass only allows a single circuit (edge) between any two nodes, so matching interfaces into circuits is deterministic. 16 | 17 | Interface ``utilization`` represents how much traffic is egressing the interface divided by the interface's ``capacity`` (``utilization`` = ``traffic`` / ``capacity``). 18 | 19 | Interface Capacity Units 20 | ************************ 21 | 22 | An interface's ``capacity`` and ``traffic`` are described in *units* of traffic, meaning they are not described in Mbps, Gbps, etc. 23 | They are written in this generic way so that the user can determine the traffic units they want to deal with in their modeling exercises. 24 | 25 | For example, if an interface has ``capacity`` = 200, it can be 200 Mpbs, 200 Gbps, etc. This example, will use Gbps. 26 | 27 | If a single, entire demand (# ECMP = 1) with ``traffic`` = 100 (Gbps) transits the interface, the interface's computed ``utilization`` will be 50% and its ``traffic`` will be 100 (Gbps). 28 | 29 | For more information on interfaces,see the `interface docstrings`_. 30 | 31 | .. _interface docstrings: ./api.html#interface -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/model.rst: -------------------------------------------------------------------------------- 1 | The Model Object 2 | ================ 3 | 4 | What is the model object? 5 | ************************* 6 | 7 | The model object has two main functions: 8 | 9 | * It holds all the objects that represent the network (interfaces, nodes, demands, RSVPs, etc) 10 | * It controls the mechanics of how each object interacts with others 11 | 12 | A network model requires these primitive objects to create a simulation and higher-level objects: 13 | 14 | * Interfaces 15 | * Nodes 16 | * Demands 17 | * RSVP LSPs (only required if network has RSVP LSPs) 18 | 19 | From these primitives, the model can construct higher-level objects such as: 20 | 21 | * Circuits 22 | * Shared Risk Link Groups (SRLGs) 23 | * Paths for demands and LSPs 24 | * etc 25 | 26 | The model produces a simulation of the network behavior by applying the traffic demand objects to the network topology, routing the traffic demands across the topology as a real network would. 27 | The model produces a simulation when its ``update_simulation()`` method is called. 28 | 29 | Model Type Subclasses 30 | ********************* 31 | 32 | There are two network model subclasses: ``FlexModel`` and ``PerformanceModel``. 33 | 34 | In general, the ``FlexModel`` can accommodate more topology variations, but at the price of a slightly longer convergence time while the ``PerformanceModel`` can only handle 35 | simpler network architectures, but with the benefit of better convergence time. 36 | 37 | All model classes support: 38 | 39 | * IGP routing 40 | * RSVP LSPs carrying traffic demands that have matching source and destination as the RSVP LSPs 41 | * RSVP auto-bandwidth or fixed bandwidth 42 | * RSVP LSP manual metrics 43 | 44 | The ``PerformanceModel`` class allows for: 45 | 46 | * Single circuits between 2 Nodes 47 | * Error messages if it detects use of IGP shortcuts or multiple Circuits between 2 Nodes 48 | 49 | The ``FlexModel`` class allows for: 50 | 51 | * Multiple Circuits between 2 Nodes 52 | * RSVP LSP IGP shortcuts, whereby LSPs can carry traffic demands downstream, even if the demand does not have matching source and destination as the LSP 53 | 54 | How do I know the simulations work correctly? 55 | ********************************************** 56 | 57 | There are many safeguards in place to ensure the simulation's mechanics are correct: 58 | 59 | * Multiple functional tests in the CI/CD pipeline check for the expected mechanics and results for each routing method (ECMP, single path, RSVP, RSVP resignaling, etc) and other features in various topology scenarios: 60 | 61 | * If any of these tests fail, the build will fail and the bad code will not make it into production 62 | 63 | * This helps ensure that functionality works as expected and that new features and fixes don't break prior functionality 64 | 65 | * There are over 300 unit and functional tests in the pyNTM CI/CD pipeline 66 | * There are dozens of topology-specific functional tests in the pyNTM CI/CD pipeline that ensure the simulation works properly for different topologies, and more are added for each new feature 67 | 68 | * The model object has internal checks that happen automatically during the ``update_simulation()`` execution: 69 | 70 | * Flagging interfaces that are not part of a circuit 71 | * Flagging if an interface's RSVP reserved bandwidth is greater than the interface's capacity 72 | * Verifying that each interface's RSVP reserved bandwidth matches the sum of the reserved bandwidth for the LSPs egressing the interface 73 | * Checks that the interface names on each node are unique 74 | * Validates that the capacities of each of the component interfaces in a circuit match 75 | * Validates that each node in an SRLG has the SRLG in the node's ``srlgs`` set 76 | * No duplicate node names are present in the topology 77 | 78 | The PerformanceModel subclass also verifies that 79 | 80 | * IGP shortcuts are not enabled for nodes 81 | * There is no more than a single circuit (edge) between any two nodes 82 | 83 | Note that there are more checks involving RSVP than IGP/ECMP routing because there are more mechanics involved when RSVP is running, whereas the straight IGP/ECMP routing is much simpler. 84 | 85 | If any of these checks fails, ``update_simulation()`` will throw an error with debug info. 86 | -------------------------------------------------------------------------------- /docs/model_file.rst: -------------------------------------------------------------------------------- 1 | The Network Model File 2 | ====================== 3 | 4 | The network model file contains basic information about the network topology: 5 | 6 | * Interfaces 7 | * Nodes (layer 3 node/router) 8 | * Demands (traffic) 9 | * RSVP LSPs 10 | 11 | Since there is a lot of information needed to create a model, using the model file to load network data is recommended. 12 | 13 | The network model file is a **tab-separated** values file. 14 | 15 | The sections below describe the model file's table headers for the FlexModel and PerformanceModel subclasses. 16 | 17 | Model File Interface Headers 18 | ---------------------------- 19 | 20 | Interfaces represent the logical interfaces on a layer 3 Node (Router). 21 | In the context of a simulation, demands (traffic) egress interfaces. 22 | A circuit is created when two *matching* interfaces (one in each direction) are paired to form a bi-directional connection between layer 3 nodes. 23 | 24 | The process of *matching* circuits depends on the model object used. 25 | 26 | Since there can only be a single connection (circuit) between any two nodes, the ``PerformanceModel`` automatically matches the interfaces into circuits. 27 | 28 | The ``FlexModel`` requires a ``circuit_id`` to appropriately match two interfaces into a circuit. The ``circuit_id`` must be included for each interface in the model file's ``INTERFACES_TABLE``. 29 | There must be exactly two instances of a given ``circuit_id`` in the ``INTERFACES_TABLE``: any more or any less will result in extra/unmatched interfaces and will cause an error when the file is loaded. 30 | 31 | PerformanceModel Interfaces 32 | *************************** 33 | 34 | INTERFACES_TABLE 35 | 36 | * ``node_object_name`` - name of node where interface resides 37 | * ``remote_node_object_name`` - name of remote node 38 | * ``name`` - interface name 39 | * ``cost`` - IGP cost/metric for interface 40 | * ``capacity`` - capacity 41 | * ``rsvp_enabled`` (optional) - is interface allowed to carry RSVP LSPs? True|False; default is True 42 | * ``percent_reservable_bandwidth`` (optional) - percent of capacity allowed to be reserved by RSVP LSPs; this value should be given as a percentage value - ie 80% would be given as 80, NOT .80. Default is 100 43 | 44 | FlexModel Interfaces 45 | ******************** 46 | 47 | INTERFACES_TABLE 48 | 49 | * ``node_object_name`` - name of node where interface resides 50 | * ``remote_node_object_name`` - name of remote node 51 | * ``name`` - interface name 52 | * ``cost`` - IGP cost/metric for interface 53 | * ``capacity`` - capacity 54 | * ``circuit_id`` - id of the circuit; used to match two Interfaces into Circuits 55 | 56 | * each ``circuit_id`` value can only appear twice in the model 57 | * ``circuit_id`` can be string or integer 58 | 59 | * ``rsvp_enabled`` (optional) - is interface allowed to carry RSVP LSPs? True|False; default is True 60 | * ``percent_reservable_bandwidth`` (optional) - percent of capacity allowed to be reserved by RSVP LSPs; this value should be given as a percentage value - ie 80% would be given as 80, NOT .80. Default is 100 61 | * ``manual_metric`` (optional) - manually assigned metric for LSP, if not using default metric from topology shortest path 62 | 63 | .. important:: 64 | Column order matters. If you wish to use an optional column to the right of an optional column you don't want to specify a value for, you must still include the optional headers to the left of the column you wish to specify a value for. 65 | 66 | For example, if you wish to specify a ``percent_reservable_bandwidth`` for an interface but not explicitly specify ``rsvp_enabled``, you must also include the ``rsvp_enabled`` columns and then leave those row values blank in each unused column. 67 | 68 | This example specifies ``percent_reservable_bandwidth`` of 30 for interface ``A-to-B_1``:: 69 | 70 | INTERFACES_TABLE 71 | node_object_name remote_node_object_name name cost capacity circuit_id rsvp_enabled percent_reservable_bandwidth 72 | A B A-to-B_1 20 120 1 30 73 | B A B-to-A_1 20 120 1 True 50 74 | 75 | Model File Node Headers 76 | ----------------------- 77 | 78 | Nodes represent layer 3 devices in the topology. Many nodes can be inferred by the presence of an interface on the ``node_object`` column in the ``INTERFACES_TABLE`` in the model file. 79 | Any node inferred by the ``node_object`` column in the ``INTERFACES`` table does not have to be explicitly declared in the ``NODES`` table. 80 | However, the ``NODES`` table does have a couple of use cases: 81 | 82 | * It can be used to add attributes to inferred nodes: ``lat`` (latitude, or y-coordinate), ``lon`` (longitude, or x-coordinate), and ``igp_shortcuts_enabled`` (whether IGP shortcuts are enabled for the node) 83 | * It can be used to declare a node that does not have any interfaces yet (aka an *orphan* node) 84 | 85 | .. note:: 86 | ``lat`` and ``lon`` can be used instead for (y, x) grid coordinates; there are no restrictions on the integer values those attributes can have. 87 | 88 | PerformanceModel Nodes 89 | ********************** 90 | 91 | NODES_TABLE 92 | 93 | * ``name`` - name of node 94 | * ``lon`` - longitude (or y-coordinate) (optional) 95 | * ``lat`` - latitude (or x-coordinate) (optional) 96 | 97 | 98 | FlexModel Nodes 99 | *************** 100 | 101 | NODES_TABLE 102 | 103 | * ``name`` - name of node 104 | * ``lon`` - longitude (or y-coordinate) (optional) 105 | * ``lat`` - latitude (or x-coordinate) (optional) 106 | * ``igp_shortcuts_enabled`` (default=``False``) - Indicates if IGP shortcuts enabled for the Node 107 | * If ``True``, network internal traffic transiting the layer 3 node can now use LSPs en route to the destination, if they are available 108 | 109 | .. important:: 110 | Column order matters. If you wish to use an optional column to the right of an optional column you don't want to specify a value for, you must still include the optional headers to the left of the column you wish to specify a value for. 111 | 112 | If you wish to include ``igp_shortcuts_enabled`` values for a given node, you must include the ``name``, ``lon`` and ``lat`` column headers and then leave the unused row values for those columns blank. 113 | 114 | For example, to enable ``igp_shortcuts_enabled`` for the ``SLC`` node, but not specify ``lon`` or ``lat``:: 115 | 116 | NODES_TABLE 117 | name lon lat igp_shortcuts_enabled 118 | SLC True 119 | 120 | Model File Demand Headers 121 | ------------------------- 122 | 123 | Demands represent traffic on the network. Each demand represents an amount of traffic ingressing the network at a specific layer 3 (source) node and egressing the network at a specific layer 3 (destination) node. 124 | 125 | PerformanceModel and FlexModel Demands 126 | ************************************** 127 | 128 | For both model classes, the ``DEMANDS_TABLE`` table has four headers, all of which are required: 129 | 130 | * ``source`` - the source node for the traffic; the node in the model where the traffic originates 131 | * ``dest`` - the destination node for the traffic; the node in the model where the traffic terminates 132 | * ``traffic`` - the amount of traffic in the demand 133 | * ``name`` - the name of the demand; there can be multiple demands with matching source and dest nodes - the name is the differentiator 134 | 135 | * there cannot be multiple demands with matching ``source``, ``dest``, and ``name`` values 136 | 137 | RSVP LSPs 138 | --------- 139 | 140 | PerformanceModel and FlexModel RSVP LSPs 141 | **************************************** 142 | 143 | The ``RSVP_LSP_TABLE`` has the following columns: 144 | 145 | * ``source`` - the source node for the LSP; the node in the model where the LSP originates 146 | * ``dest`` - the destination node for the LSP; the node in the model where the LSP terminates 147 | * ``name`` - the name of the LSP; there can be multiple LSPs with matching source and dest nodes - the name is the differentiator 148 | 149 | * There cannot be multiple LSPs with matching ``source``, ``dest``, and ``name`` values 150 | 151 | * ``configured_setup_bw`` (optional) - if LSP has a fixed, static configured setup bandwidth, place that static value here, if LSP is auto-bandwidth, then leave this blank for the LSP 152 | * ``manual_metric`` (optional) - manually assigned metric for LSP, if not using default metric from topology shortest path 153 | 154 | .. important:: 155 | Column order matters. If you wish to use an optional column to the right of an optional column you don't want to specify a value for, you must still include the optional headers to the left of the column you wish to specify a value for. 156 | 157 | If you wish to specify a ``manual_metric`` for an LSP but not explicitly specify ``configured_setup_bw``, you must also include the ``configured_setup_bw`` column and then leave those row values blank in each unused column. 158 | 159 | For example, to specify a ``manual_metric`` for the LSP with name ``lsp_a_b_2`` but not specify ``configured_setup_bw``:: 160 | 161 | RSVP_LSP_TABLE 162 | source dest name configured_setup_bw manual_metric 163 | A B lsp_a_b_1 10 19 164 | A B lsp_a_b_2 6 -------------------------------------------------------------------------------- /docs/pyNTM_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-fiola/network_traffic_modeler_py3/d2bd7287ea86fb22ec41c2522164d5761fc225dd/docs/pyNTM_logo.png -------------------------------------------------------------------------------- /docs/rsvp_lsp.rst: -------------------------------------------------------------------------------- 1 | RSVP LSP 2 | ========= 3 | 4 | A class to represent an RSVP label-switched-path in the network model 5 | 6 | **source_node_object**: Node where LSP ingresses the network (LSP starts here) 7 | 8 | **dest_node_object**: Node where LSP egresses the network (LSP ends here) 9 | 10 | **lsp_name**: name of LSP 11 | 12 | **path**:: 13 | 14 | will either be 'Unrouted' or be a dict containing the following - 15 | - interfaces: list of interfaces that LSP egresses in the order it 16 | egresses them 17 | - path_cost: sum of costs of the interfaces 18 | - baseline_path_reservable_bw: the amount of reservable bandwidth 19 | available on the LSP's path when the LSP was signaled, not inclusive 20 | of the bandwidth already reserved by this LSP on that path (if any) 21 | 22 | **reserved_bandwidth**: amount of bandwidth reserved by this LSP 23 | 24 | **setup_bandwidth**: amount of bandwidth this LSP attempts to signal for when it sets up 25 | 26 | **manual_metric**: manual metric for LSP. 27 | If set, this value will override the default (shortest path) metric for effective_metric. 28 | This value must be a positive integer. 29 | To restore the LSP's default metric (that of the shortest IGP path) in a live simulation, 30 | set this value to -1. 31 | 32 | For more information on the capabilities of RSVP LSPs, see the `RSVP LSP docstrings`_. 33 | 34 | .. _RSVP LSP docstrings: ./api.html#rsvp-lsp 35 | 36 | -------------------------------------------------------------------------------- /docs/srlg.rst: -------------------------------------------------------------------------------- 1 | Shared Risk Link Groups (SRLGs) 2 | =============================== 3 | 4 | An SRLG represents a collection of model objects with shared risk factors. 5 | 6 | An SRLG can include: 7 | 8 | - Nodes 9 | - Circuits 10 | 11 | When the SRLG is failed (``failed = True``), the members will go to a failed state (``failed = True``) as well. 12 | When the SRLG is not failed (``failed = False``), the members will also return to ``failed = False``. 13 | 14 | Nodes are added to an SRLG via the ``add_to_srlg`` node method. 15 | 16 | A circuit is added to an SRLG when a component interface is added to the SRLG via the ``add_to_srlg`` interface method. 17 | The other interface in the interface's circuit is also automatically added to the SRLG. -------------------------------------------------------------------------------- /docs/visualization.rst: -------------------------------------------------------------------------------- 1 | Visualization 2 | ============= 3 | 4 | PyNTM has a visualization feature that produces an interactive visualization of the network. 5 | 6 | More info on the visualization and how to use it can be found in the pyNTM training repository `visualization training module `_. 7 | 8 | Visualization Demo Script 9 | ------------------------- 10 | 11 | The ``examples/vis_training.py`` example script showcases the visualization module, which is current beta feature. 12 | 13 | To run this script, install the packages in ``requirements_visualization.txt`` file:: 14 | 15 | (venv) % pip3 install -r requirements_visualization.txt 16 | 17 | Once you've installed the requirements, run the script. 18 | 19 | You will see some simulation activities and info and then a visualization:: 20 | 21 | (venv) % python3 -i vis_training.py 22 | Routing the LSPs . . . 23 | Routing 1 LSPs in parallel LSP group C-E; 1/3 24 | Routing 1 LSPs in parallel LSP group D-F; 2/3 25 | Routing 2 LSPs in parallel LSP group B-D; 3/3 26 | LSPs routed (if present) in 0:00:00.001134; routing demands now . . . 27 | Demands routed in 0:00:00.003497; validating model . . . 28 | shortest path from A to F is: 29 | {'cost': 50, 30 | 'path': [[Interface(name = 'A-G', cost = 25, capacity = 100, node_object = Node('A'), remote_node_object = Node('G'), circuit_id = '6'), 31 | Interface(name = 'G-F', cost = 25, capacity = 100, node_object = Node('G'), remote_node_object = Node('F'), circuit_id = '7')], 32 | [Interface(name = 'A-G', cost = 25, capacity = 100, node_object = Node('A'), remote_node_object = Node('G'), circuit_id = '6'), 33 | Interface(name = 'G-F_2', cost = 25, capacity = 100, node_object = Node('G'), remote_node_object = Node('F'), circuit_id = '8')], 34 | [Interface(name = 'A-B', cost = 10, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1'), 35 | Interface(name = 'B-C', cost = 10, capacity = 100, node_object = Node('B'), remote_node_object = Node('C'), circuit_id = '2'), 36 | Interface(name = 'C-D', cost = 10, capacity = 100, node_object = Node('C'), remote_node_object = Node('D'), circuit_id = '3'), 37 | Interface(name = 'D-E', cost = 10, capacity = 100, node_object = Node('D'), remote_node_object = Node('E'), circuit_id = '4'), 38 | Interface(name = 'E-F', cost = 10, capacity = 100, node_object = Node('E'), remote_node_object = Node('F'), circuit_id = '5')], 39 | [Interface(name = 'A-B', cost = 10, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1'), 40 | Interface(name = 'B-C_2', cost = 10, capacity = 100, node_object = Node('B'), remote_node_object = Node('C'), circuit_id = '9'), 41 | Interface(name = 'C-D', cost = 10, capacity = 100, node_object = Node('C'), remote_node_object = Node('D'), circuit_id = '3'), 42 | Interface(name = 'D-E', cost = 10, capacity = 100, node_object = Node('D'), remote_node_object = Node('E'), circuit_id = '4'), 43 | Interface(name = 'E-F', cost = 10, capacity = 100, node_object = Node('E'), remote_node_object = Node('F'), circuit_id = '5')]]} 44 | 45 | dmd_a_f_1 path is: 46 | [[Interface(name = 'A-G', cost = 25, capacity = 100, node_object = Node('A'), remote_node_object = Node('G'), circuit_id = '6'), 47 | Interface(name = 'G-F', cost = 25, capacity = 100, node_object = Node('G'), remote_node_object = Node('F'), circuit_id = '7')], 48 | [Interface(name = 'A-G', cost = 25, capacity = 100, node_object = Node('A'), remote_node_object = Node('G'), circuit_id = '6'), 49 | Interface(name = 'G-F_2', cost = 25, capacity = 100, node_object = Node('G'), remote_node_object = Node('F'), circuit_id = '8')], 50 | [Interface(name = 'A-B', cost = 10, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1'), 51 | RSVP_LSP(source = B, dest = D, lsp_name = 'lsp_b_d_1'), 52 | RSVP_LSP(source = D, dest = F, lsp_name = 'lsp_d_f_1')], 53 | [Interface(name = 'A-B', cost = 10, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1'), 54 | RSVP_LSP(source = B, dest = D, lsp_name = 'lsp_b_d_2'), 55 | RSVP_LSP(source = D, dest = F, lsp_name = 'lsp_d_f_1')]] 56 | 57 | *** NOTE: The make_visualization_beta function is a beta feature. It may not have been as 58 | extensively tested as the pyNTM code in general. The API calls for this may also 59 | change more rapidly than the general pyNTM code base. 60 | 61 | 62 | 63 | Visualization is available at http://127.0.0.1:8050/ 64 | 65 | 66 | Dash is running on http://127.0.0.1:8050/ 67 | 68 | * Serving Flask app 'pyNTM.weathermap' (lazy loading) 69 | * Environment: production 70 | WARNING: This is a development server. Do not use it in a production deployment. 71 | Use a production WSGI server instead. 72 | * Debug mode: off 73 | * Running on http://127.0.0.1:8050/ (Press CTRL+C to quit) 74 | 127.0.0.1 - - [26/Sep/2021 18:18:19] "GET / HTTP/1.1" 200 - 75 | 127.0.0.1 - - [26/Sep/2021 18:18:20] "GET /_dash-layout HTTP/1.1" 200 - 76 | 127.0.0.1 - - [26/Sep/2021 18:18:20] "GET /_dash-dependencies HTTP/1.1" 200 - 77 | 127.0.0.1 - - [26/Sep/2021 18:18:20] "GET /_favicon.ico?v=2.0.0 HTTP/1.1" 200 - 78 | 127.0.0.1 - - [26/Sep/2021 18:18:20] "GET /_dash-component-suites/dash/dcc/async-dropdown.js HTTP/1.1" 200 - 79 | 127.0.0.1 - - [26/Sep/2021 18:18:20] "POST /_dash-update-component HTTP/1.1" 200 - 80 | 127.0.0.1 - - [26/Sep/2021 18:18:20] "POST /_dash-update-component HTTP/1.1" 200 - 81 | 82 | If you open your browser to http://127.0.0.1:8050/, you will see an interactive visualization. 83 | 84 | -------------------------------------------------------------------------------- /docs/workflows.rst: -------------------------------------------------------------------------------- 1 | Common Workflows 2 | ================ 3 | 4 | There is an existing pyNTM training repository on GitHub that extensively covers common workflows with pyNTM. 5 | 6 | `pyNTM Training Modules repository module 1`_ covers what network modeling is, the problem it solves for, and the common use cases 7 | 8 | .. _pyNTM Training Modules repository module 1: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM-/blob/master/pyNTM_training_module_1.pdf 9 | 10 | Please see the `pyNTM Training Modules repository module 2`_ for info and walk-through exercises for 11 | 12 | * Directions on how to get started using pyNTM 13 | * Setting up a practice/demo environment 14 | * Finding Shortest Path(s) 15 | * Failing/Unfailing Interfaces 16 | * Finding traffic demands egressing a given interface 17 | * Finding all ECMP paths for a specific demand 18 | * Simple visualization exercise 19 | 20 | .. _pyNTM Training Modules repository module 2: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM-/blob/master/pyNTM_training_module_2_v2.pdf 21 | 22 | Please see the `pyNTM Training Modules repository module 3`_ for info and walk-through exercises for 23 | 24 | * Adding a new Node 25 | * Adding a new link 26 | * Adding traffic to the traffic matrix 27 | * Changing Interface/Circuit capacity 28 | * Changing an Interface metric 29 | * Working with RSVP LSPs 30 | 31 | .. _pyNTM Training Modules repository module 3: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM-/blob/master/pyNTM_training_module_3.pdf 32 | 33 | Please see the `pyNTM Training Modules repository module 4`_ for info and walk-through exercises for 34 | 35 | * RSVP LSP model data files 36 | * RSVP types and behaviors 37 | * Auto bandwidth 38 | * Fixed bandwidth 39 | * LSPs and Demands 40 | * Getting an LSP path 41 | * Seeing demands on an LSP 42 | * Demand path when demand is on LSP 43 | * Shared Risk Link Groups (SRLGs) 44 | * Adding an SRLG 45 | * Failing an SRLG 46 | 47 | .. _pyNTM Training Modules repository module 4: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM-/blob/master/pyNTM_training_module_4.pdf 48 | 49 | Please see the `pyNTM Training Modules repository module 5`_ for info and walk-through exercises for 50 | 51 | * How to create a visualization using the WeatherMap 52 | * WeatherMap visual components overview 53 | 54 | .. _pyNTM Training Modules repository module 5: https://github.com/tim-fiola/TRAINING---network_traffic_modeler_py3-pyNTM-/blob/master/pyNTM_visualization_training.pdf 55 | 56 | Checking Network Health 57 | *********************** 58 | 59 | There are a some results to watch for in your simulations that will indicate a network augment or re-architecture of your existing or planned network may be helpful. 60 | 61 | IGP routing is deterministic and much simpler to interpret; one obvious warning sign is over-utilized links. 62 | 63 | It gets a bit more difficult with RSVP, especially with auto-bandwidth enabled, to determine if the network is under stress. 64 | RSVP auto-bandwidth behavior can be non-deterministic, meaning that there may be multiple different end-states the network will converge to, depending on the order in which the LSPs signal and how long each layer 3 node takes to compute the paths for its LSPs and a host of other factors. 65 | 66 | With this being the case, there are a few behavior in the model to watch for when running RSVP that may indicate a network augment or re-architecture may be helpful: 67 | 68 | * Large quantities of LSPs not on the shortest path 69 | * LSPs reserving less bandwidth than they are carrying 70 | * Some LSPs not being able to signal due to lack of available setup bandwidth in the path 71 | -------------------------------------------------------------------------------- /examples/flex_model_parallel_source_dest_lsps.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 20 100 1 4 | B A B-to-A 20 100 1 5 | B C B-to-C 20 100 2 6 | C B C-to-B 20 100 2 7 | C D C-to-D 20 100 3 8 | D C D-to-C 20 100 3 9 | D E D-to-E 20 100 4 10 | E D E-to-D 20 100 4 11 | 12 | NODES_TABLE 13 | name lon lat igp_shortcuts_enabled 14 | A 50 0 15 | B 0 -50 True 16 | C 0 50 17 | D -50 0 18 | E 25 25 19 | 20 | DEMANDS_TABLE 21 | source dest traffic name 22 | B D 15 dmd_b_d_1 23 | A D 12 dmd_a_d_1 24 | 25 | RSVP_LSP_TABLE 26 | source dest name configured_setup_bw manual_metric 27 | B D lsp_b_d_1 20 28 | B D lsp_b_d_3 20 29 | B D lsp_b_d_2 -------------------------------------------------------------------------------- /examples/igp_shortcuts_manual_metrics_prototype.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | 5 | from pyNTM import FlexModel 6 | 7 | model = FlexModel.load_model_file("flex_model_parallel_source_dest_lsps.csv") 8 | 9 | lsp_b_d_1 = model.get_rsvp_lsp("B", "D", "lsp_b_d_1") # lower metric 10 | lsp_b_d_3 = model.get_rsvp_lsp("B", "D", "lsp_b_d_3") # lower metric 11 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") # default metric (higher) 12 | 13 | dmd_a_d_1 = model.get_demand_object("A", "D", "dmd_a_d_1") 14 | dmd_b_d_1 = model.get_demand_object("B", "D", "dmd_b_d_1") 15 | 16 | print("traffic on lsp_b_d_1 = {}".format(lsp_b_d_1.traffic_on_lsp(model))) 17 | print("traffic on lsp_b_d_2 = {}".format(lsp_b_d_2.traffic_on_lsp(model))) 18 | print("traffic on lsp_b_d_3 = {}".format(lsp_b_d_3.traffic_on_lsp(model))) 19 | -------------------------------------------------------------------------------- /examples/igp_shortcuts_model_mult_lsps_in_path.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-B 10 100 1 4 | B A B-A 10 100 1 5 | B C B-C 10 100 2 6 | C B C-B 10 100 2 7 | C D C-D 10 100 3 8 | D C D-C 10 100 3 9 | D E D-E 10 100 4 10 | E D E-D 10 100 4 11 | E F E-F 10 100 5 12 | F E F-E 10 100 5 13 | A G A-G 25 100 6 14 | G A G-A 25 100 6 15 | G F G-F 25 100 7 16 | F G F-G 25 100 7 17 | G F G-F_2 25 100 8 18 | F G F-G_2 25 100 8 19 | 20 | 21 | NODES_TABLE 22 | name lon lat igp_shortcuts_enabled 23 | A 0 0 True 24 | B 60 0 True 25 | C 120 0 True 26 | D 180 0 True 27 | E 240 0 True 28 | F 300 0 True 29 | G 150 -150 True 30 | 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A F 10 dmd_a_f_1 35 | D F 8 dmd_d_f_1 36 | 37 | 38 | RSVP_LSP_TABLE 39 | source dest name 40 | B D lsp_b_d_1 41 | B D lsp_b_d_2 42 | C E lsp_c_e_1 43 | D F lsp_d_f_1 44 | -------------------------------------------------------------------------------- /examples/igp_shortcuts_model_mult_lsps_in_path_parallel_links_2.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-B 10 100 1 4 | B A B-A 10 100 1 5 | B C B-C 10 100 2 6 | C B C-B 10 100 2 7 | B C B-C_2 10 100 9 8 | C B C-B_2 10 100 9 9 | C D C-D 10 100 3 10 | D C D-C 10 100 3 11 | D E D-E 10 100 4 12 | E D E-D 10 100 4 13 | E F E-F 10 100 5 14 | F E F-E 10 100 5 15 | A G A-G 25 100 6 16 | G A G-A 25 100 6 17 | G F G-F 25 100 7 18 | F G F-G 25 100 7 19 | G F G-F_2 25 100 8 20 | F G F-G_2 25 100 8 21 | 22 | 23 | NODES_TABLE 24 | name lon lat igp_shortcuts_enabled 25 | A 0 0 True 26 | B 60 0 True 27 | C 120 0 True 28 | D 180 0 True 29 | E 240 0 True 30 | F 300 0 True 31 | G 150 -150 True 32 | 33 | 34 | DEMANDS_TABLE 35 | source dest traffic name 36 | A F 100 dmd_a_f_1 37 | D F 20 dmd_d_f_1 38 | D E 10 dmd_d_f_1 39 | A C 10 dmd_a_c_1 40 | 41 | 42 | RSVP_LSP_TABLE 43 | source dest name 44 | B D lsp_b_d_1 45 | B D lsp_b_d_2 46 | C E lsp_c_e_1 47 | D F lsp_d_f_1 -------------------------------------------------------------------------------- /examples/igp_shortcuts_prototype.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | 5 | from pprint import pprint 6 | from pyNTM import FlexModel 7 | 8 | # Make the Parallel_Link_Model 9 | model = FlexModel.load_model_file("igp_shortcuts_model_mult_lsps_in_path.csv") 10 | model.update_simulation() 11 | 12 | dmd_a_f_1 = model.get_demand_object("A", "F", "dmd_a_f_1") 13 | dmd_d_f_1 = model.get_demand_object("D", "F", "dmd_d_f_1") 14 | node_a = model.get_node_object("A") 15 | 16 | lsp_b_d_1 = model.get_rsvp_lsp("B", "D", "lsp_b_d_1") 17 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") 18 | 19 | print("Interfaces with traffic") 20 | for interface in model.interface_objects: 21 | if interface.traffic > 0: 22 | print([interface, interface.traffic, interface.utilization]) 23 | print() 24 | # for dmd in model.demand_objects: 25 | # pprint([dmd, dmd.path_detail]) 26 | 27 | print("LSPs and their traffic") 28 | for lsp in model.rsvp_lsp_objects: 29 | print([lsp, lsp.traffic_on_lsp(model)]) 30 | print() 31 | lsp_d_f = model.get_rsvp_lsp("D", "F", "lsp_d_f_1") 32 | 33 | print("demands on lsp_d_f:") 34 | pprint(lsp_d_f.demands_on_lsp(model)) 35 | 36 | # print() 37 | # print("Remove igp shortcuts from Node B:") 38 | # 39 | # node_b = model.get_node_object('B') 40 | # node_b.igp_shortcuts_enabled = False 41 | # model.update_simulation() 42 | # 43 | # lsp_c_e = model.get_rsvp_lsp('C', 'E', 'lsp_c_e_1') 44 | # print("Traffic on lsp_c_e = {}".format(lsp_c_e.traffic_on_lsp(model))) 45 | -------------------------------------------------------------------------------- /examples/lsp_manual_metric_perf_model_prototype.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | 5 | from pprint import pprint 6 | from pyNTM import PerformanceModel 7 | from pyNTM import RSVP_LSP 8 | 9 | model = PerformanceModel.load_model_file("perf_model_lsp_metric.csv") 10 | model.update_simulation() 11 | 12 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 13 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 14 | 15 | print("lsp_a_d_2 config_setup_bw = {}".format(lsp_a_d_2.configured_setup_bandwidth)) 16 | print("lsp_a_d_2 setup_bw = {}".format(lsp_a_d_2.setup_bandwidth)) 17 | print("lsp_a_d_2 manual_metric = {}".format(lsp_a_d_2.manual_metric)) 18 | print("lsp_a_d_2 path = ") 19 | pprint(lsp_a_d_2.path) 20 | 21 | print() 22 | print("lsp_a_d_1 setup_bw = {}".format(lsp_a_d_1.configured_setup_bandwidth)) 23 | print("lsp_a_d_1 manual_metric = {}".format(lsp_a_d_1.manual_metric)) 24 | print("lsp_a_d_1 effective_metric = {}".format(lsp_a_d_1.effective_metric(model))) 25 | print("lsp_a_d_1 topology_metric = {}".format(lsp_a_d_1.topology_metric(model))) 26 | 27 | new_lsp = RSVP_LSP( 28 | model.get_node_object("A"), 29 | model.get_node_object("G"), 30 | "lsp_a_f_manual_enter", 31 | configured_setup_bandwidth=float("4"), 32 | configured_manual_metric=float("10"), 33 | ) 34 | -------------------------------------------------------------------------------- /examples/lsp_model_test_file.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 -50 26 | C 0 50 27 | D -50 0 28 | E 75 0 29 | F -75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A D 80 dmd_a_d_1 35 | A D 70 dmd_a_d_2 36 | F E 400 dmd_f_e_1 37 | A F 40 dmd_a_f_1 38 | 39 | RSVP_LSP_TABLE 40 | source dest name configured_setup_bw 41 | A D lsp_a_d_1 42 | A D lsp_a_d_2 43 | F E lsp_f_e_1 -------------------------------------------------------------------------------- /examples/model_test_topology_flexmodel.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 2 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | B E B-to-E 3 200 7 16 | E B E-to-B 3 200 7 17 | B E B-to-E_2 3 200 17 18 | E B E-to-B_2 3 200 17 19 | B E B-to-E_3 3 200 27 20 | E B E-to-B_3 3 200 27 21 | D C D-to-C 9 150 8 22 | C D C-to-D 9 150 8 23 | D E D-to-E 4 100 9 24 | E D E-to-D 4 100 9 25 | D F D-to-F 3 100 10 26 | F D F-to-D 3 100 10 27 | F B F-to-B 6 100 11 28 | B F B-to-F 6 100 11 29 | G H G-to-H 4 150 12 30 | H G H-to-G 4 150 12 31 | G D G-to-D 2 50 13 32 | D G D-to-G 2 50 13 33 | H E H-to-E 4 100 14 34 | E H E-to-H 4 100 14 35 | E F E-to-F 3 100 15 36 | F E F-to-E 3 100 15 37 | H D H-to-D 4 100 16 38 | D H D-to-H 4 100 16 39 | A X A-to-X 10 100 38 40 | X A X-to-A 10 100 38 41 | Y X Y-to-X 10 100 39 42 | X Y X-to-Y 10 100 39 43 | 44 | NODES_TABLE 45 | name lon lat 46 | A 25 0 47 | B 31 -177 48 | C 60 -63 49 | D 62 37 50 | E -60 124 51 | F 2 35 52 | G 30 90 53 | H 52 124 54 | X 0 30 55 | Y 30 0 56 | 57 | DEMANDS_TABLE 58 | source dest traffic name 59 | A B 50 dmd_a_b_1 60 | A F 22 dmd_a_f_1 61 | A E 24 dmd_a_e_1 62 | F E 80 dmd_f_e_1 63 | F B 50 dmd_f_b_1 64 | D A 10 dmd_d_a_1 65 | A H 20 dmd_a_h_1 66 | C E 20 dmd_c_e_1 67 | B G 30 dmd_b_g_1 68 | E C 20 dmd_e_c_1 69 | A D 20 dmd_a_d_1 70 | A D 20 dmd_a_d_2 71 | A D 20 dmd_a_d_3 72 | A Y 20 dmd_a_y_1 73 | 74 | RSVP_LSP_TABLE 75 | source dest name 76 | A D lsp_a_d_1 77 | A D lsp_a_d_2 78 | F E lsp_f_e_1 79 | A F lsp_a_f_1 -------------------------------------------------------------------------------- /examples/network_modeling_with_visualization.py: -------------------------------------------------------------------------------- 1 | """ 2 | Client code to demonstrate how to use the pyNTM network_modeling classes. 3 | 4 | Requirements: 5 | - sample_network_model_file.csv be in same directory 6 | - access to graph_network folder in syspath 7 | 8 | """ 9 | # This is a temp hack to get this to see pyNTM and let it import 10 | import sys # noqa 11 | 12 | sys.path.append("../") # noqa 13 | 14 | from pyNTM import PerformanceModel 15 | from pyNTM import Node 16 | 17 | from pyNTM.weathermap import WeatherMap 18 | 19 | from pprint import pprint 20 | import time 21 | 22 | t1 = time.time() 23 | 24 | # Load a network Model file with info on nodes, interfaces, and demands 25 | model1 = PerformanceModel.load_model_file("sample_network_model_file.csv") 26 | model1.update_simulation() 27 | 28 | print("model1 is type", type(model1)) 29 | print() 30 | 31 | # Define source and dest nodes for testing 32 | source = "A" 33 | dest = "B" 34 | 35 | print() 36 | 37 | # find the best path from node A to B 38 | best_A_B = model1.get_shortest_path(source, dest) 39 | print("The best path from Node A to Node B is:", best_A_B) 40 | print() 41 | 42 | # display the traffic 43 | print("Interface traffic with no failures:") 44 | 45 | model1.display_interfaces_traffic() 46 | print() 47 | print() 48 | 49 | # Find the remote interface for Node F, interface F-to-D 50 | print("Find the remote interface for Node F, interface F-to-D:") 51 | f_to_d = model1.get_interface_object("F-to-D", "F") 52 | remote_int = f_to_d.get_remote_interface(model1) 53 | print("Remote interface is", remote_int) 54 | print() 55 | 56 | # Fail interface on node A, interface A-to-B 57 | print("Failing interface A-to-B on node A") 58 | int_to_fail = model1.get_interface_object("A-to-B", "A") 59 | int_to_fail.fail_interface(model1) 60 | 61 | # Calculate new traffic on interfaces and display 62 | # the new traffic after the failure 63 | print("Here is the traffic after the failure:") 64 | model1.update_simulation() 65 | model1.display_interfaces_traffic() 66 | print() 67 | 68 | # Add a node to the model 69 | print("Adding Node('Z') to model") 70 | new_node = Node("Z") 71 | model1.add_node(new_node) 72 | new_node.lat = 40 73 | new_node.lon = 50 74 | print() 75 | 76 | # Display all the nodes without interfaces 77 | print("Here are the nodes without any interfaces:", model1.get_orphan_node_objects()) 78 | print() 79 | 80 | # Add a circuit from new_node to node A 81 | print("Add a circuit between Node('A') and Node('Z')") 82 | model1.add_circuit(Node("A"), Node("Z"), "a-to-z", "z-to-a") 83 | print() 84 | 85 | 86 | # Add traffic to the model 87 | shortest_paths_B_D = model1.get_shortest_path("B", "D") 88 | print("The shortest paths between nodes B and D are:") 89 | for path in shortest_paths_B_D["path"]: 90 | pprint(path) 91 | print() 92 | print("These paths both have a cost of", shortest_paths_B_D["cost"]) 93 | print() 94 | print("Adding 100 traffic from Node B to Node D:") 95 | model1.add_demand("B", "D", 100) 96 | model1.update_simulation() 97 | print("Here is the interface traffic after adding the traffic:") 98 | print("Notice the difference in traffic - ") 99 | print("Each path has 50 additional traffic: B-to-D and (B-to-E and E-to-D)") 100 | model1.display_interfaces_traffic() 101 | print() 102 | print() 103 | 104 | 105 | # Return an interface object 106 | print("Get an interface object via interface name and node name:") 107 | interface_object = model1.get_interface_object("A-to-B", "A") 108 | print('Interface(Node("A"), "A-to-B") = ', interface_object) 109 | print() 110 | 111 | # Find the circuit for the interface 112 | print("The circuit for Interface(Node('A'), 'A-to-B') is") 113 | ckt_object = interface_object.get_circuit_object(model1) 114 | print(ckt_object) 115 | print() 116 | 117 | # Fail a node 118 | print("Fail Node('D')") 119 | model1.fail_node("D") 120 | model1.update_simulation() 121 | print() 122 | print("Here is Node('D')'s failure status:", model1.get_node_object("D").failed) 123 | print() 124 | 125 | # Get Node('D')'s interfaces 126 | print("Here are Node('D')'s interfaces:") 127 | pprint(model1.get_node_interfaces("D")) 128 | model1.update_simulation() 129 | print() 130 | print("Here is the network traffic after Node('D') failed:") 131 | print(model1.display_interfaces_traffic()) 132 | print() 133 | 134 | # Get unrouted demands 135 | print("Here are the unrouted demands:") 136 | for demand in model1.get_unrouted_demand_objects(): 137 | pprint(demand) 138 | print() 139 | 140 | # Unfail Node('D') 141 | print("Now unfail Node('D')") 142 | model1.unfail_node("D") 143 | print() 144 | model1.update_simulation() 145 | 146 | 147 | print("Here is the interface traffic after Node('D') is restored:") 148 | print(model1.display_interfaces_traffic()) 149 | print() 150 | 151 | # Unfail the B-to-A interface and display traffic 152 | print("Unfail the interface from Node B to Node A") 153 | model1.unfail_interface("B-to-A", "B", True) 154 | model1.update_simulation() 155 | model1.display_interfaces_traffic() 156 | print() 157 | 158 | # test get_circuit call from Interface object 159 | int1 = model1.get_interface_object("A-to-B", "A") 160 | ckt1 = int1.get_circuit_object(model1) 161 | print("The circuit associated with %s is %s" % (int1, ckt1)) 162 | print() 163 | 164 | # test a demand path before and after an interface it traverses fails; 165 | # should go from 2 paths to one path 166 | dmd_B_D = model1.get_demand_object("B", "D") 167 | print("dmd_B_D path before failing interface B-to-D is") 168 | for path in dmd_B_D.path: 169 | pprint(path) 170 | print() 171 | print() 172 | model1.fail_interface("B-to-D", "B") 173 | model1.update_simulation() 174 | print("dmd_B_D path after failing interface B-to-D is") 175 | pprint(dmd_B_D.path) 176 | print() 177 | 178 | 179 | # Fail a node and interface and display a utilization graph 180 | print( 181 | "Failing node E and interface B-to-D on node B and displaying " "a network graph:" 182 | ) 183 | model1.fail_node("E") 184 | model1.fail_interface("B-to-D", "B") 185 | model1.update_simulation() 186 | 187 | # Get a demand and view its path 188 | print("Get a demand and view its path.") 189 | dmd_A_D = model1.get_demand_object("A", "D", "''") 190 | print("Demand from A to D is") 191 | print(dmd_A_D) 192 | print() 193 | print("The path for dmd_A_D is:") 194 | for hop in dmd_A_D.path: 195 | pprint(hop) 196 | print() 197 | 198 | 199 | t2 = time.time() 200 | tot_time = t2 - t1 201 | print("tot_time = ", tot_time) 202 | print() 203 | 204 | 205 | print("Making interactive network graph in browser.") 206 | 207 | vis = WeatherMap(model1) 208 | vis.create_weathermap() 209 | -------------------------------------------------------------------------------- /examples/parallel_link_model_with_visualization.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | 5 | from pprint import pprint 6 | 7 | from pyNTM import FlexModel 8 | from pyNTM.weathermap import WeatherMap 9 | 10 | # Make the Parallel_Link_Model 11 | model = FlexModel.load_model_file("model_test_topology_flexmodel.csv") 12 | model.update_simulation() 13 | 14 | print() 15 | # Get shorteset path from Node A to Node D 16 | a_d = model.get_shortest_path("A", "D") 17 | 18 | # Print the shortest path(s) 19 | print("There are {} shortest path(s) from Node A to Node D.".format(len(a_d["path"]))) 20 | print("The shortest path(s) from Node A to Node D:") 21 | for path in a_d["path"]: 22 | pprint(path) 23 | print() 24 | 25 | # Find the shortest path(s) from A to D with 75 units of reservable_bandwidth 26 | a_d_75 = model.get_shortest_path("A", "D", needed_bw=75) 27 | 28 | # Print the path(s) 29 | print( 30 | "There are {} shortest path(s) from Node A to Node D with at least 75 units of" 31 | "reserable bandwidth.".format(len(a_d_75["path"])) 32 | ) 33 | print( 34 | "The shortest path(s) from Node A to Node D with 75 units of reservable bandwidth:" 35 | ) 36 | for path in a_d_75["path"]: 37 | pprint(path) 38 | print() 39 | 40 | print() 41 | 42 | # Find ALL paths (not just shortest paths) from A to D over 43 | # interfaces that are not failed, consisting of 3 hops or less, 44 | # with at least 80 units of reservable bandwidth 45 | all_paths = model.get_all_paths_reservable_bw("A", "D", False, 3, 80) 46 | 47 | print( 48 | "All {} paths from A to D over interfaces that are not failed, consisting of 3 hops or less, with" 49 | "at least 80 units of reservable bandwidth:".format(len(all_paths["path"])) 50 | ) 51 | for path in all_paths["path"]: 52 | pprint(path) 53 | print() 54 | 55 | # Look at a demand path in detail 56 | print() 57 | dmd_a_e_1 = model.get_demand_object("A", "E", "dmd_a_e_1") 58 | print("Look at path_detail for {}:".format(dmd_a_e_1)) 59 | 60 | pprint(dmd_a_e_1.path_detail) 61 | print() 62 | print() 63 | 64 | 65 | # Create visualization 66 | print("Creating visualization:") 67 | 68 | vis = WeatherMap(model) 69 | vis.create_weathermap() 70 | -------------------------------------------------------------------------------- /examples/perf_model_lsp_metric.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 -50 26 | C 0 50 27 | D -50 0 28 | E 75 0 29 | F -75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A D 80 dmd_a_d_1 35 | A D 70 dmd_a_d_2 36 | F E 400 dmd_f_e_1 37 | A F 40 dmd_a_f_1 38 | 39 | RSVP_LSP_TABLE 40 | source dest name configured_setup_bw manual_metric 41 | A D lsp_a_d_1 4 5 42 | A D lsp_a_d_2 10 43 | F E lsp_f_e_1 6 -------------------------------------------------------------------------------- /examples/sample_network_model_file.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 4 100 4 | A C A-to-C 1 200 5 | A D A-to-D 8 150 6 | B A B-to-A 4 100 7 | B D B-to-D 7 200 8 | B E B-to-E 3 200 9 | D B D-to-B 7 200 10 | D C D-to-C 9 150 11 | D A D-to-A 8 150 12 | D E D-to-E 4 100 13 | D F D-to-F 3 100 14 | C A C-to-A 1 200 15 | C D C-to-D 9 150 16 | E B E-to-B 3 200 17 | E D E-to-D 4 100 18 | F D F-to-D 3 100 19 | F B F-to-B 6 100 20 | B F B-to-F 6 100 21 | G H G-to-H 4 150 22 | H G H-to-G 4 150 23 | G D G-to-D 2 50 24 | D G D-to-G 2 50 25 | H E H-to-E 4 100 26 | E H E-to-H 4 100 27 | E F E-to-F 3 100 28 | F E F-to-E 3 100 29 | H D H-to-D 4 100 30 | D H D-to-H 4 100 31 | 32 | NODES_TABLE 33 | name lon lat 34 | A 25 0 35 | B 31 -177 36 | C 60 -63 37 | D 62 37 38 | E -60 124 39 | F 2 35 40 | G 30 90 41 | H 52 124 42 | 43 | DEMANDS_TABLE 44 | source dest traffic name 45 | A B 50 '' 46 | A F 22 '' 47 | A E 24 '' 48 | F E 80 '' 49 | F B 50 '' 50 | A D 120 '' 51 | D A 10 '' 52 | A H 20 '' 53 | C E 20 '' 54 | B G 30 '' 55 | E C 20 '' -------------------------------------------------------------------------------- /examples/traffic_eng_example_model.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity rsvp_enabled percent_reservable_bandwidth 3 | A B A-to-B 20 120 True 50 4 | B A B-to-A 20 120 True 50 5 | A C A-to-C 20 150 6 | C A C-to-A 20 150 7 | A D A-to-D 10 200 False 8 | D A D-to-A 10 200 False 9 | B E B-to-E 20 125 10 | E B E-to-B 20 125 11 | D E D-to-E 20 225 12 | E D E-to-D 20 225 13 | C E C-to-E 20 100 14 | E C E-to-C 20 100 15 | 16 | NODES_TABLE 17 | name lon lat 18 | A 50 0 19 | B 0 50 20 | C 0 50 21 | D 50 0 22 | E 75 0 23 | 24 | DEMANDS_TABLE 25 | source dest traffic name 26 | A E 80 dmd_a_e_1 27 | A E 70 dmd_a_e_2 28 | 29 | RSVP_LSP_TABLE 30 | source dest name 31 | A E lsp_a_e_1 32 | A E lsp_a_e_2 33 | A E lsp_a_e_3 -------------------------------------------------------------------------------- /examples/traffic_eng_feature_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | 5 | 6 | # Test cases 7 | # - LSPs won't transit over non-rsvp_enabled interface on both models 8 | # - percent_reserved_bandwidth limits amount of reservable_bandwidth on Interface 9 | # - test return self._reservable_bandwidth on Interface 10 | # - try manually setting reservable_bandwidth on an interface 11 | 12 | # TODO - make model file that has 3 identical circuit_id values - should error out 13 | # TODO - test parallel links but one w higher metric 14 | 15 | from pprint import pprint 16 | 17 | from pyNTM import PerformanceModel 18 | 19 | model = PerformanceModel.load_model_file("traffic_eng_example_model.csv") 20 | model.update_simulation() 21 | 22 | int_a_b = model.get_interface_object("A-to-B", "A") 23 | int_a_c = model.get_interface_object("A-to-C", "A") 24 | int_a_d = model.get_interface_object("A-to-D", "A") 25 | 26 | lsp_a_e_1 = model.get_rsvp_lsp("A", "E", "lsp_a_e_1") 27 | lsp_a_e_2 = model.get_rsvp_lsp("A", "E", "lsp_a_e_2") 28 | lsp_a_e_3 = model.get_rsvp_lsp("A", "E", "lsp_a_e_3") 29 | 30 | dmd_a_e_1 = model.get_demand_object("A", "E", "dmd_a_e_1") 31 | dmd_a_e_2 = model.get_demand_object("A", "E", "dmd_a_e_2") 32 | 33 | # int_a_d is not rsvp_enabled, so it should have no RSVP LSPs 34 | print("int_a_d is rsvp_enabled: {}".format(int_a_d.rsvp_enabled)) 35 | print("int_a_d has {} RSVP LSPs".format(len(int_a_d.lsps(model)))) 36 | print() 37 | # int_a_b has 50% of capacity reservable 38 | print( 39 | "int_a_b percent_reservable_bandwidth = {}".format( 40 | int_a_b.percent_reservable_bandwidth 41 | ) 42 | ) 43 | print("int_a_b capacity = {}".format(int_a_b.capacity)) 44 | print("int_a_b reserved_bandwidth is {}".format(int_a_b.reserved_bandwidth)) 45 | print("int_a_b reservable_bandwidth is {}".format(int_a_b.reservable_bandwidth)) 46 | print( 47 | "int_a_b reserved_bandwidth + reservable_bandwidth = {}".format( 48 | int_a_b.reserved_bandwidth + int_a_b.reservable_bandwidth 49 | ) 50 | ) 51 | -------------------------------------------------------------------------------- /examples/vis_training.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append("../") 4 | 5 | from pprint import pprint 6 | from pyNTM import FlexModel 7 | from pyNTM.weathermap import WeatherMap 8 | 9 | model = FlexModel.load_model_file( 10 | "igp_shortcuts_model_mult_lsps_in_path_parallel_links_2.csv" 11 | ) 12 | 13 | model.update_simulation() 14 | 15 | sp_a_f = model.get_shortest_path("A", "F") 16 | print("shortest path from A to F is:") 17 | pprint(sp_a_f) 18 | print() 19 | dmd_a_f = model.get_demand_object("A", "F", "dmd_a_f_1") 20 | print("dmd_a_f_1 path is:") 21 | pprint(dmd_a_f.path) 22 | 23 | 24 | wm = WeatherMap(model) 25 | 26 | wm.spacing_factor = 5 27 | 28 | wm.create_weathermap() 29 | -------------------------------------------------------------------------------- /prototypes/pypy3_pytest_notes.txt: -------------------------------------------------------------------------------- 1 | getting pytest to work in pypy3 - 2 | 3 | sphinx 4 | sphinx_rtd_theme 5 | pytest>=4.5.0 6 | pytest-cov 7 | pytest-flake8 8 | pytest-mccabe 9 | coveralls 10 | 11 | - for every requirement above, pypy3 -m pip install 12 | 13 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ pypy3 -m pip install coveralls 14 | 15 | 16 | Now when I run it, all the tests pass minus the flake8 tests - I get this for each flake8 test on each file 17 | FAILED pyNTM/rsvp.py::FLAKE8 - AttributeError: 'Application' object has no attribute 'parse_preliminary_options_and_args' 18 | FAILED pyNTM/srlg.py::FLAKE8 - AttributeError: 'Application' object has no attribute 'parse_preliminary_options_and_args' 19 | FAILED pyNTM/utilities.py::FLAKE8 - AttributeError: 'Application' object has no attribute 'parse_preliminary_options_and_args' 20 | ========================================================================== 34 failed, 254 passed in 51.52s =========================================================================== 21 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ 22 | 23 | 24 | 25 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ pypy3 -m pytest --version 26 | This is pytest version 5.4.1, imported from /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest/__init__.py 27 | setuptools registered plugins: 28 | pytest-mccabe-1.0 at /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_mccabe.py 29 | pytest-flake8-1.0.5 at /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_flake8.py 30 | pytest-cov-2.8.1 at /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_cov/plugin.py 31 | 32 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ pytest --version 33 | This is pytest version 5.3.5, imported from /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest/__init__.py 34 | setuptools registered plugins: 35 | pytest-mccabe-1.0 at /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest_mccabe.py 36 | pytest-cov-2.7.1 at /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest_cov/plugin.py 37 | pytest-flake8-1.0.4 at /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest_flake8.py 38 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ 39 | 40 | 41 | 42 | 43 | Next step - the pytest versions are different in python3 and pypy3 - check flake8/main/application.py in python3; in the pypy /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/flake8/main/application.py, application has def parse_preliminary_options, but no parse_preliminary_options_and_args 44 | - check the python3 application.py file for parse_preliminary_options_and_args def 45 | 46 | /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_flake8.py:191: in check_file 47 | app.parse_preliminary_options_and_args(args) 48 | E AttributeError: 'Application' object has no attribute 'parse_preliminary_options_and_args' 49 | 50 | 51 | 52 | This is pytest version 5.3.5, imported from /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest/__init__.py 53 | setuptools registered plugins: 54 | pytest-mccabe-1.0 at /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest_mccabe.py 55 | pytest-cov-2.7.1 at /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest_cov/plugin.py 56 | pytest-flake8-1.0.4 at /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3/venv/lib/python3.7/site-packages/pytest_flake8.py 57 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ 58 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ pypy3 -m pytest --version 59 | This is pytest version 5.4.1, imported from /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest/__init__.py 60 | setuptools registered plugins: 61 | pytest-mccabe-1.0 at /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_mccabe.py 62 | pytest-flake8-1.0.5 at /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_flake8.py 63 | pytest-cov-2.8.1 at /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages/pytest_cov/plugin.py 64 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ 65 | 66 | 5-13-20 - it LOOKS like (https://flake8.pycqa.org/en/latest/_modules/flake8/main/application.html) parse_preliminary_options is the most recent implementation in flake8; in flake8 3.7.9 https://flake8.pycqa.org/en/3.7.9/_modules/flake8/main/application.html it has parse_preliminary_options_and_args - it changed in 3.8.0 to parse_preliminary_options 67 | 68 | Installed flake8 3.7.9 - ALL TESTS PASSED!!! 69 | 70 | (venv) Timothys-MacBook-Air:venv timothyfiola$ pypy3 -m pip install flake8==3.7.9 71 | Collecting flake8==3.7.9 72 | Using cached flake8-3.7.9-py2.py3-none-any.whl (69 kB) 73 | Collecting entrypoints<0.4.0,>=0.3.0 74 | Using cached entrypoints-0.3-py2.py3-none-any.whl (11 kB) 75 | Collecting pyflakes<2.2.0,>=2.1.0 76 | Using cached pyflakes-2.1.1-py2.py3-none-any.whl (59 kB) 77 | Collecting pycodestyle<2.6.0,>=2.5.0 78 | Using cached pycodestyle-2.5.0-py2.py3-none-any.whl (51 kB) 79 | Requirement already satisfied: mccabe<0.7.0,>=0.6.0 in /usr/local/Cellar/pypy3/7.3.1_1/libexec/site-packages (from flake8==3.7.9) (0.6.1) 80 | Installing collected packages: entrypoints, pyflakes, pycodestyle, flake8 81 | Attempting uninstall: pyflakes 82 | Found existing installation: pyflakes 2.2.0 83 | Uninstalling pyflakes-2.2.0: 84 | Successfully uninstalled pyflakes-2.2.0 85 | WARNING: The script pyflakes is installed in '/usr/local/share/pypy3' which is not on PATH. 86 | Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. 87 | Attempting uninstall: pycodestyle 88 | Found existing installation: pycodestyle 2.6.0 89 | Uninstalling pycodestyle-2.6.0: 90 | Successfully uninstalled pycodestyle-2.6.0 91 | WARNING: The script pycodestyle is installed in '/usr/local/share/pypy3' which is not on PATH. 92 | Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. 93 | Attempting uninstall: flake8 94 | Found existing installation: flake8 3.8.0 95 | Uninstalling flake8-3.8.0: 96 | Successfully uninstalled flake8-3.8.0 97 | WARNING: The script flake8 is installed in '/usr/local/share/pypy3' which is not on PATH. 98 | Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. 99 | Successfully installed entrypoints-0.3 flake8-3.7.9 pycodestyle-2.5.0 pyflakes-2.1.1 100 | 101 | 102 | 103 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ pypy3 -m pytest 104 | ================================================================================ test session starts ================================================================================= 105 | platform darwin -- Python 3.6.9[pypy-7.3.1-final], pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /usr/local/bin/pypy3 106 | cachedir: .pytest_cache 107 | rootdir: /Users/timothyfiola/PycharmProjects/network_traffic_modeler_py3, inifile: setup.cfg 108 | plugins: mccabe-1.0, flake8-1.0.5, cov-2.8.1 109 | collected 288 items 110 | 111 | test/__init__.py::FLAKE8 PASSED 112 | ------------------ 113 | pyNTM/master_model.py::FLAKE8 PASSED [ 98%] 114 | pyNTM/node.py::FLAKE8 PASSED [ 98%] 115 | pyNTM/performance_model.py::FLAKE8 PASSED [ 98%] 116 | pyNTM/rsvp.py::FLAKE8 PASSED [ 99%] 117 | pyNTM/srlg.py::FLAKE8 PASSED [ 99%] 118 | pyNTM/utilities.py::FLAKE8 PASSED [100%] 119 | 120 | ---------- coverage: platform darwin, python 3.6.9-final-0 ----------- 121 | Name Stmts Miss Cover Missing 122 | ---------------------------------------------------------- 123 | pyNTM/__init__.py 13 0 100% 124 | pyNTM/circuit.py 15 0 100% 125 | pyNTM/demand.py 24 2 92% 50, 58 126 | pyNTM/exceptions.py 2 0 100% 127 | pyNTM/flex_model.py 402 37 91% 105-108, 177, 492-495, 523, 560-563, 586, 605, 622-640, 697-698, 735-736, 874, 900-901, 936-938, 1210, 1220, 1234 128 | pyNTM/interface.py 149 1 99% 92 129 | pyNTM/master_model.py 458 18 96% 168, 651-652, 681-683, 694, 717-719, 724, 731, 854, 877-878, 885, 902-904 130 | pyNTM/node.py 85 0 100% 131 | pyNTM/performance_model.py 371 26 93% 171-174, 179, 368-370, 515-522, 583, 623, 637, 723-724, 760-761, 1057, 1067, 1080, 1109-1113 132 | pyNTM/rsvp.py 72 3 96% 87, 161-162 133 | pyNTM/srlg.py 23 0 100% 134 | pyNTM/utilities.py 7 0 100% 135 | ---------------------------------------------------------- 136 | TOTAL 1621 87 95% 137 | Coverage XML written to file coverage.xml 138 | 139 | 140 | =========================================================================== 288 passed in 78.56s (0:01:18) =========================================================================== 141 | (venv) Timothys-MacBook-Air:network_traffic_modeler_py3 timothyfiola$ 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /pyNTM/__init__.py: -------------------------------------------------------------------------------- 1 | """A set of native python APIs to create a network model and run 2 | network simulations.""" 3 | 4 | from .circuit import Circuit # noqa: F401 5 | from .demand import Demand # noqa: F401 6 | 7 | from .exceptions import ModelException # noqa: F401 8 | from .node import Node # noqa: F401 9 | from .srlg import SRLG # noqa: F401 10 | from .utilities import * # noqa: F401,F403 11 | from .flex_model import FlexModel # noqa: F401 12 | from .flex_model import Parallel_Link_Model # noqa: F401 13 | from .master_model import _MasterModel # noqa: F401 14 | from .interface import Interface # noqa: F401 15 | from .rsvp import RSVP_LSP # noqa: F401 16 | from .performance_model import PerformanceModel # noqa: F401 17 | from .performance_model import Model # noqa: F401 18 | 19 | # from .weathermap import WeatherMap # noqa: F401 20 | -------------------------------------------------------------------------------- /pyNTM/circuit.py: -------------------------------------------------------------------------------- 1 | """A Circuit object in a Model. Circuit is comprised of two Interface objects""" 2 | 3 | 4 | class Circuit(object): 5 | """A circuit is an object consisting of 2 connected interfaces""" 6 | 7 | def __init__(self, interface_a, interface_b): 8 | self.interface_a = interface_a 9 | self.interface_b = interface_b 10 | 11 | def __repr__(self): 12 | return "Circuit(%r, %r)" % ( 13 | self.interface_a, 14 | self.interface_b, 15 | ) 16 | 17 | def _key(self): 18 | return (self.interface_a._key, self.interface_b._key) 19 | 20 | def get_circuit_interfaces(self, model): 21 | """ 22 | Return the Circuit's component Interface objects in model object 23 | 24 | :param model: model object containing Circuit 25 | :return: Component Interfaces in Circuit 26 | """ 27 | return (self.interface_a, self.interface_b) 28 | 29 | def failed(self, model): 30 | """ 31 | Is Circuit failed? 32 | 33 | :param model: Model containing circuit 34 | :return: Boolean 35 | """ 36 | int_a, int_b = self.get_circuit_interfaces(model) 37 | if int_a.failed or int_b.failed: 38 | return True 39 | else: 40 | return False 41 | 42 | def circuit_id(self): # TODO - unit test this 43 | """ 44 | Returns the circuit_id, which bonds the two component Interfaces to the Circuit 45 | """ 46 | 47 | return self.interface_a.circuit_id 48 | -------------------------------------------------------------------------------- /pyNTM/demand.py: -------------------------------------------------------------------------------- 1 | """A Demand is a traffic load that traverses the network from a source Node 2 | to a destination Node""" 3 | 4 | 5 | class Demand(object): 6 | """ 7 | A representation of traffic load on the modeled network 8 | """ 9 | 10 | def __init__(self, source_node_object, dest_node_object, traffic=0, name="none"): 11 | self.source_node_object = source_node_object 12 | self.dest_node_object = dest_node_object 13 | self.traffic = traffic 14 | self.name = name 15 | self.path = "Unrouted" 16 | self._path_detail = "Unrouted_detail" 17 | 18 | # Validate traffic value 19 | if not (isinstance(traffic, (int, float))) or traffic < 0: 20 | raise ValueError("Must be a positive int or float") 21 | 22 | @property 23 | def _key(self): 24 | """Unique identifier for the demand: (Node('source').name, Node('dest').name, name)""" 25 | return (self.source_node_object.name, self.dest_node_object.name, self.name) 26 | 27 | def __repr__(self): 28 | return "Demand(source = %s, dest = %s, traffic = %s, name = %r)" % ( 29 | self.source_node_object.name, 30 | self.dest_node_object.name, 31 | self.traffic, 32 | self.name, 33 | ) 34 | 35 | @property 36 | def path_detail(self): 37 | """ 38 | Returns a detailed breakdown of the Demand path. 39 | Each path will have the following information: 40 | 41 | items: The combination of Interfaces and/or LSPs that the Demand takes 42 | from source to destination 43 | 44 | splits: each item on the path (Interface and/or LSP) and the number of cumulative 45 | ECMP path splits that the Demand has transited as it egresses the source node for 46 | that element. 47 | 48 | Splits can be used to calculate how much of the Demand's traffic is on a certain path 49 | (see path_traffic below) or how much of the Demand's traffic is on a certain element. 50 | 51 | The demand object's ``path_detail`` property can be very useful to determine how much of the demand's 52 | traffic egresses each object (interface, LSP) in the path. 53 | 54 | For example, sample demand ``Demand(source = A, dest = E, traffic = 24, name = 'dmd_a_e_1')`` 55 | has 24 units of traffic. 56 | 57 | Here is the ``path_0`` entry for the sample demand:: 58 | 59 | 'path_0': { 60 | 'items': [Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), 61 | remote_node_object = Node('B'), circuit_id = '1'), 62 | Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), 63 | remote_node_object = Node('E'), circuit_id = '27') 64 | ], 65 | 'path_traffic': 4.0, 66 | 'splits': {Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), 67 | remote_node_object = Node('B'), circuit_id = '1'): 2, 68 | Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), 69 | remote_node_object = Node('E'), circuit_id = '27'): 6} 70 | } 71 | 72 | The ``path_0`` component of the ``path_detail`` property in this example shows the following: 73 | 74 | * ``Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1')`` has **2** splits 75 | * ``Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), remote_node_object = Node('E'), circuit_id = '27')`` has **6** splits 76 | 77 | To get the amount of traffic load from the specific demand that transits each interface, divide the amount of traffic that the demand has by the number of splits for the object: 78 | 79 | * ``Interface(name = 'A-to-B', cost = 4, capacity = 100, node_object = Node('A'), remote_node_object = Node('B'), circuit_id = '1')`` carries **24 / 2 = 12** units of traffic from the sample demand. 80 | * ``Interface(name = 'B-to-E_3', cost = 3, capacity = 200, node_object = Node('B'), remote_node_object = Node('E'), circuit_id = '27')`` carries **24 / 6 = 4** units of traffic from the sample demand. 81 | 82 | Since the minimum amount of traffic found on any object in ``path_0`` is 4 units of traffic, ``path_traffic`` for ``path_0`` = 4. 83 | 84 | :return: Dict of path entries (keys). The value for each key is another dict with 3 keys: 'items', 'splits', and 'path_traffic'. Each is described above. # noqa E501 85 | """ 86 | 87 | return self._path_detail 88 | -------------------------------------------------------------------------------- /pyNTM/exceptions.py: -------------------------------------------------------------------------------- 1 | """Exceptions""" 2 | 3 | 4 | class ModelException(Exception): 5 | pass 6 | -------------------------------------------------------------------------------- /pyNTM/node.py: -------------------------------------------------------------------------------- 1 | """A class to represent a layer 3 device in the Model""" 2 | 3 | from .exceptions import ModelException 4 | from .srlg import SRLG 5 | 6 | 7 | class Node(object): 8 | """ 9 | A class to represent a layer 3 device in the model. 10 | 11 | Attribute lat, lon can be used as y, x values, respectively for 12 | graphing purposes. 13 | 14 | """ 15 | 16 | def __init__(self, name, lat=0, lon=0): 17 | self.name = name 18 | self._failed = False 19 | self._lat = lat 20 | self._lon = lon 21 | self._srlgs = set() 22 | self._igp_shortcuts_enabled = False 23 | 24 | # Validate lat, lon values 25 | if not (isinstance(lat, float)) and not (isinstance(lat, int)): 26 | raise ValueError("lat must be a float value") 27 | if not (isinstance(lon, float)) and not (isinstance(lon, int)): 28 | raise ValueError("lon must be a float value") 29 | 30 | def __repr__(self): 31 | return "Node(%r)" % self.name 32 | 33 | # Modify __eq__ and __hash__ default behavior for Node class 34 | # to allow us to easily determine if a Node instance is equivalent to another. 35 | # By default, equivalency test is 1) id, 2) hash, 3) equality. The id test will 36 | # fail and so it will move to hash and then equality. Modifying the __eq__ to 37 | # focus on the Node.name equivalency and and __hash__ to focus on the 38 | # hash of the Node.name will make equivalency testing possible 39 | def __eq__(self, other_node): 40 | return self.__dict__ == other_node.__dict__ 41 | 42 | def __hash__(self): 43 | # return hash(tuple(sorted(self.__dict__.items()))) 44 | return hash(self.name) 45 | 46 | def _key(self): 47 | return self.name 48 | 49 | @property 50 | def failed(self): 51 | """ 52 | Is node failed? Boolean. It is NOT recommended to directly 53 | modify this property. Rather, Model methods fail_node(node_name) and 54 | unfail_node(node_name) 55 | 56 | :return: Boolean - is node failed? 57 | """ 58 | return self._failed 59 | 60 | @failed.setter 61 | def failed(self, status): 62 | if not isinstance(status, bool): 63 | raise ModelException("must be boolean") 64 | 65 | if status is False: # False means Node would not be failed 66 | # Check for any SRLGs with self as a member and get status 67 | # of each SRLG 68 | failed_srlgs = [srlg for srlg in self.srlgs if srlg.failed is True] 69 | if len(failed_srlgs) > 0: 70 | self._failed = True 71 | raise ModelException( 72 | "Node must be failed since it is a member of one or more SRLGs that are failed" 73 | ) 74 | else: 75 | self._failed = False 76 | 77 | else: 78 | self._failed = True 79 | 80 | @property 81 | def lat(self): 82 | """Latitude or y-coordinate of Node on a plot""" 83 | return self._lat 84 | 85 | @lat.setter 86 | def lat(self, status): 87 | if isinstance(status, float) or isinstance(status, int): 88 | self._lat = status 89 | else: 90 | raise ValueError("lat attribute must be integer or float.") 91 | 92 | @property 93 | def lon(self): 94 | """Longitude or x-coordinate of Node on a plot""" 95 | return self._lon 96 | 97 | @lon.setter 98 | def lon(self, status): 99 | if isinstance(status, float) or isinstance(status, int): 100 | self._lon = status 101 | else: 102 | raise ValueError("lon attribute must be integer or float.") 103 | 104 | @property 105 | def igp_shortcuts_enabled(self): 106 | """Are IGP shortcuts enabled for RSVP LSPs on this Node? 107 | This is only applicable in the FlexModel; PerformanceModel 108 | subclass ignores this attribute 109 | """ 110 | return self._igp_shortcuts_enabled 111 | 112 | @igp_shortcuts_enabled.setter 113 | def igp_shortcuts_enabled(self, status): 114 | if isinstance(status, bool): 115 | self._igp_shortcuts_enabled = status 116 | elif status == "True": 117 | self.igp_shortcuts_enabled = True 118 | elif status == "False": 119 | self.igp_shortcuts_enabled = False 120 | else: 121 | raise ValueError("igp_shortcuts must be boolean") 122 | 123 | def interfaces(self, model): 124 | """ 125 | Returns interfaces for a given node 126 | 127 | :param model: model structure 128 | :return adjacency_list: (list) list of interfaces on the given node 129 | """ 130 | adjacency_list = [] 131 | 132 | interface_iterator = (interface for interface in model.interface_objects) 133 | 134 | for interface in interface_iterator: 135 | if interface.node_object.name == self.name: 136 | adjacency_list.append(interface) 137 | 138 | return adjacency_list 139 | 140 | def adjacent_nodes(self, model): 141 | """ 142 | Returns a list of adjacent nodes 143 | 144 | :param model: model Object 145 | :return: List of adjacent Nodes in model 146 | """ 147 | 148 | adjacencies = self.interfaces(model) 149 | 150 | adjacent_nodes = set() 151 | 152 | for adjacency in adjacencies: 153 | adjacent_nodes.add(adjacency.remote_node_object) 154 | 155 | return adjacent_nodes 156 | 157 | def add_to_srlg(self, srlg_name, model, create_if_not_present=False): 158 | """ 159 | Adds self to an SRLG with name=srlg_name in model. 160 | 161 | :param srlg_name: name of srlg 162 | :param model: Model object 163 | :param create_if_not_present: Boolean. Create the SRLG if it does not exist in model already. True will create SRLG in model; False will raise ModelException # noqa E501 164 | 165 | :return: None 166 | """ 167 | 168 | # See if model has existing SRLG with name='srlg_name' 169 | # get_srlg will be the SRLG object with name=srlg_name in model 170 | # or it will be False if the SRLG with name=srlg_name does not 171 | # exist in model 172 | try: 173 | get_srlg = model.get_srlg_object(srlg_name) 174 | except ModelException: 175 | get_srlg = False 176 | 177 | if get_srlg is False: 178 | # SRLG does not exist 179 | if create_if_not_present is True: 180 | new_srlg = SRLG(srlg_name, model) 181 | model.srlg_objects.add(new_srlg) 182 | self._srlgs.add(new_srlg) 183 | else: 184 | msg = "An SRLG with name {} does not exist in the Model".format( 185 | srlg_name 186 | ) 187 | raise ModelException(msg) 188 | else: 189 | # SRLG does exist in model; add self to that SRLG 190 | get_srlg.node_objects.add(self) 191 | self._srlgs.add(get_srlg) 192 | 193 | def remove_from_srlg(self, srlg_name, model): 194 | """ 195 | Removes self from SRLG with srlg_name in model 196 | 197 | :param srlg_name: name of SRLG 198 | :param model: Model object 199 | :return: none 200 | """ 201 | # See if model has existing SRLG with name='srlg_name' 202 | # get_srlg will be the SRLG object with name=srlg_name in model 203 | # or it will be False if the SRLG with name=srlg_name does not 204 | # exist in model 205 | try: 206 | get_srlg = model.get_srlg_object(srlg_name) 207 | except ModelException: 208 | get_srlg = False 209 | 210 | if get_srlg is False: 211 | msg = "An SRLG with name {} does not exist in the Model".format(srlg_name) 212 | raise ModelException(msg) 213 | else: 214 | # Remove self from SRLG 215 | get_srlg.node_objects.remove(self) 216 | self._srlgs.remove(get_srlg) 217 | 218 | # If SRLG was failed, change self.failed = False when removed. If 219 | # setting self.failed = False generates a ModelException, pass. The 220 | # ModelException would happen if the Node was part of a different SRLG 221 | # that was still failed 222 | # if get_srlg.failed: 223 | # try: 224 | # self.failed = False 225 | # except ModelException: 226 | # pass 227 | 228 | self.failed = False 229 | 230 | @property 231 | def srlgs(self): 232 | return self._srlgs 233 | 234 | # TODO add node.fail and node.unfail - low priority - not really 235 | # necessary since Model has fail_/unfail_node 236 | -------------------------------------------------------------------------------- /pyNTM/srlg.py: -------------------------------------------------------------------------------- 1 | """A Class to represent Shared Risk Link Groups (SRLGs) in a Model""" 2 | from .exceptions import ModelException 3 | 4 | 5 | class SRLG(object): 6 | """ 7 | Represents a collection of Model objects with shared risk factors. 8 | Can include: 9 | 10 | - Nodes 11 | - Interfaces 12 | 13 | When self.failed = True, the members will go to a failed state as well. 14 | When self.failed returns to False, the members will also return to 15 | failed = False. 16 | 17 | When an interface is added to an SRLG, the other interface in the 18 | interface's circuit is also automatically added to the SRLG. 19 | 20 | """ 21 | 22 | def __init__(self, name, model, circuit_objects=set(), node_objects=set()): 23 | # self.circuit_objects = circuit_objects 24 | # self.node_objects = node_objects 25 | if name in set([srlg.name for srlg in model.srlg_objects]): 26 | raise ModelException( 27 | "SRLG with name {} already exists in Model".format(name) 28 | ) 29 | else: 30 | self.name = name 31 | self.model = model 32 | self._failed = False 33 | model.srlg_objects.add(self) 34 | 35 | def __repr__(self): 36 | return "SRLG(Name: {})".format(self.name) 37 | 38 | @property 39 | def failed(self): 40 | return self._failed 41 | 42 | @failed.setter 43 | def failed(self, status): 44 | if isinstance(status, bool): 45 | self._failed = status 46 | else: 47 | raise ModelException("must be boolean") 48 | 49 | @property 50 | def node_objects(self): 51 | nodes = set([node for node in self.model.node_objects if self in node.srlgs]) 52 | return nodes 53 | 54 | @property 55 | def interface_objects(self): 56 | interfaces = set( 57 | [ 58 | interface 59 | for interface in self.model.interface_objects 60 | if self in interface.srlgs 61 | ] 62 | ) 63 | return interfaces 64 | -------------------------------------------------------------------------------- /pyNTM/utilities.py: -------------------------------------------------------------------------------- 1 | def find_end_index(start_index, lines): 2 | """ 3 | Given a start index and lines of data, finds the first line that 4 | contains only '' and returns the index for that line. 5 | """ 6 | end_index = None 7 | for line in lines[start_index:]: 8 | if line == "": 9 | end_index = lines.index(line, start_index) 10 | break 11 | return end_index 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx==2.4 2 | jinja2 3 | dash 4 | dash-cytoscape 5 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | pre-commit 4 | black 5 | pytest>=4.5.0 6 | pytest-cov 7 | pytest-flake8 8 | pytest-mccabe 9 | coveralls 10 | -------------------------------------------------------------------------------- /requirements_dev_pypy.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | pytest>=4.5.0 4 | pytest-cov 5 | pytest-flake8 6 | pytest-mccabe 7 | coveralls 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [tool:pytest] 5 | addopts = -p no:warnings --flake8 --cache-clear --cov-report term-missing --cov-report xml --cov=pyNTM/ test/ pyNTM/ -v 6 | 7 | [flake8] 8 | ignore = E203,E226,E302,E41,W503,W504 9 | max-line-length = 120 10 | max-complexity = 15 11 | exclude = 12 | .git, 13 | __pycache__, 14 | docs/conf.py, 15 | build, 16 | dist, 17 | examples 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("requirements.txt", "r") as fs: 4 | reqs = [r for r in fs.read().splitlines() if (len(r) > 0 and not r.startswith("#"))] 5 | 6 | version = "3.4.1" 7 | 8 | with open("README.md", "r") as fh: 9 | long_description = fh.read() 10 | 11 | setup( 12 | name="pyNTM", 13 | version=version, 14 | py_modules=["pyNTM"], 15 | packages=find_packages(), 16 | install_requires=reqs, 17 | include_package_data=True, 18 | description="Network traffic modeler API written in Python 3", 19 | long_description=long_description, 20 | long_description_content_type="text/markdown", 21 | author="Tim Fiola", 22 | author_email="timothy.fiola@gmail.com", 23 | url="https://github.com/tim-fiola/network_traffic_modeler_py3", 24 | download_url="https://github.com/tim-fiola/network_traffic_modeler_py3/tarball/%s" 25 | % version, 26 | keywords=["networking", "layer3", "failover", "modeling", "model", "pyNTM"], 27 | classifiers=[], 28 | ) 29 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-fiola/network_traffic_modeler_py3/d2bd7287ea86fb22ec41c2522164d5761fc225dd/test/__init__.py -------------------------------------------------------------------------------- /test/flex_model_parallel_source_dest_lsps.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 20 100 1 4 | B A B-to-A 20 100 1 5 | B C B-to-C 20 100 2 6 | C B C-to-B 20 100 2 7 | C D C-to-D 20 100 3 8 | D C D-to-C 20 100 3 9 | D E D-to-E 20 100 4 10 | E D E-to-D 20 100 4 11 | 12 | NODES_TABLE 13 | name lon lat igp_shortcuts_enabled 14 | A 50 0 15 | B 0 -50 True 16 | C 0 50 17 | D -50 0 18 | E 25 25 19 | 20 | DEMANDS_TABLE 21 | source dest traffic name 22 | A D 12 dmd_a_d_1 23 | B D 10 dmd_b_d_1 24 | 25 | 26 | RSVP_LSP_TABLE 27 | source dest name configured_setup_bw manual_metric 28 | B D lsp_b_d_1 20 29 | B D lsp_b_d_2 -------------------------------------------------------------------------------- /test/igp_routing_topology.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A F 40 dmd_a_f_1 -------------------------------------------------------------------------------- /test/igp_shortcuts_model_mult_lsps_in_path.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-B 10 100 1 4 | B A B-A 10 100 1 5 | B C B-C 10 100 2 6 | C B C-B 10 100 2 7 | C D C-D 10 100 3 8 | D C D-C 10 100 3 9 | D E D-E 10 100 4 10 | E D E-D 10 100 4 11 | E F E-F 10 100 5 12 | F E F-E 10 100 5 13 | A G A-G 25 100 6 14 | G A G-A 25 100 6 15 | G F G-F 25 100 7 16 | F G F-G 25 100 7 17 | 18 | NODES_TABLE 19 | name lon lat igp_shortcuts_enabled 20 | A 10 0 True 21 | B 0 5 True 22 | C 0 0 True 23 | D 0 0 True 24 | E 0 0 True 25 | F 0 0 True 26 | 27 | DEMANDS_TABLE 28 | source dest traffic name 29 | A F 10 dmd_a_f_1 30 | D F 8 dmd_d_f_1 31 | 32 | RSVP_LSP_TABLE 33 | source dest name 34 | B D lsp_b_d_1 35 | B D lsp_b_d_2 36 | C E lsp_c_e_1 37 | D F lsp_d_f_1 -------------------------------------------------------------------------------- /test/igp_shortcuts_model_mult_lsps_in_path_parallel_links.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-B 10 100 1 4 | B A B-A 10 100 1 5 | B C B-C 10 100 2 6 | C B C-B 10 100 2 7 | C D C-D 10 100 3 8 | D C D-C 10 100 3 9 | D E D-E 10 100 4 10 | E D E-D 10 100 4 11 | E F E-F 10 100 5 12 | F E F-E 10 100 5 13 | A G A-G 25 100 6 14 | G A G-A 25 100 6 15 | G F G-F 25 100 7 16 | F G F-G 25 100 7 17 | G F G-F_2 25 100 8 18 | F G F-G_2 25 100 8 19 | 20 | 21 | NODES_TABLE 22 | name lon lat igp_shortcuts_enabled 23 | A 0 0 True 24 | B 60 0 True 25 | C 120 0 True 26 | D 180 0 True 27 | E 240 0 True 28 | F 300 0 True 29 | G 150 -150 True 30 | 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A F 100 dmd_a_f_1 35 | D F 20 dmd_d_f_1 36 | D E 10 dmd_d_f_1 37 | A C 10 dmd_a_c_1 38 | 39 | 40 | RSVP_LSP_TABLE 41 | source dest name 42 | B D lsp_b_d_1 43 | B D lsp_b_d_2 44 | C E lsp_c_e_1 45 | D F lsp_d_f_1 -------------------------------------------------------------------------------- /test/igp_shortcuts_model_mult_lsps_in_path_parallel_links_2.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-B 10 100 1 4 | B A B-A 10 100 1 5 | B C B-C 10 100 2 6 | C B C-B 10 100 2 7 | B C B-C_2 10 100 9 8 | C B C-B_2 10 100 9 9 | C D C-D 10 100 3 10 | D C D-C 10 100 3 11 | D E D-E 10 100 4 12 | E D E-D 10 100 4 13 | E F E-F 10 100 5 14 | F E F-E 10 100 5 15 | A G A-G 25 100 6 16 | G A G-A 25 100 6 17 | G F G-F 25 100 7 18 | F G F-G 25 100 7 19 | G F G-F_2 25 100 8 20 | F G F-G_2 25 100 8 21 | 22 | 23 | NODES_TABLE 24 | name lon lat igp_shortcuts_enabled 25 | A 0 0 True 26 | B 60 0 True 27 | C 120 0 True 28 | D 180 0 True 29 | E 240 0 True 30 | F 300 0 True 31 | G 150 -150 True 32 | 33 | 34 | DEMANDS_TABLE 35 | source dest traffic name 36 | A F 100 dmd_a_f_1 37 | D F 20 dmd_d_f_1 38 | D E 10 dmd_d_f_1 39 | A C 10 dmd_a_c_1 40 | 41 | 42 | RSVP_LSP_TABLE 43 | source dest name 44 | B D lsp_b_d_1 45 | B D lsp_b_d_2 46 | C E lsp_c_e_1 47 | D F lsp_d_f_1 -------------------------------------------------------------------------------- /test/interface_field_info_missing_routing_topology.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A F 40 dmd_a_f_1 35 | -------------------------------------------------------------------------------- /test/interface_field_info_missing_routing_topology_multidigraph.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 -------------------------------------------------------------------------------- /test/lsp_configured_setup_bw_model.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 35 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A D 80 dmd_a_d_1 35 | A D 70 dmd_a_d_2 36 | F E 400 dmd_f_e_1 37 | A F 40 dmd_a_f_1 38 | 39 | RSVP_LSP_TABLE 40 | source dest name configured_setup_bw 41 | A D lsp_a_d_1 42 | A D lsp_a_d_2 43 | A D lsp_a_d_3 1 44 | F E lsp_f_e_1 -------------------------------------------------------------------------------- /test/lsp_manual_metric_test_model.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 -50 26 | C 0 50 27 | D -50 0 28 | E 75 0 29 | F -75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A D 80 dmd_a_d_1 35 | A D 70 dmd_a_d_2 36 | F E 400 dmd_f_e_1 37 | A F 40 dmd_a_f_1 38 | 39 | RSVP_LSP_TABLE 40 | source dest name configured_setup_bw manual_metric 41 | A D lsp_a_d_1 4 5 42 | A D lsp_a_d_2 10 43 | A D lsp_a_d_3 44 | A D lsp_a_d_4 4 45 | F E lsp_f_e_1 6 -------------------------------------------------------------------------------- /test/manual_lsp_metric_test_flex_model.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-B 10 100 1 4 | B A B-A 10 100 1 5 | B C B-C 10 100 2 6 | C B C-B 10 100 2 7 | C D C-D 10 100 3 8 | D C D-C 10 100 3 9 | D E D-E 10 100 4 10 | E D E-D 10 100 4 11 | E F E-F 10 100 5 12 | F E F-E 10 100 5 13 | A G A-G 25 100 6 14 | G A G-A 25 100 6 15 | G F G-F 25 100 7 16 | F G F-G 25 100 7 17 | 18 | NODES_TABLE 19 | name lon lat igp_shortcuts_enabled 20 | A 10 0 True 21 | B 0 5 True 22 | C 0 0 True 23 | D 0 0 True 24 | E 0 0 True 25 | F 0 0 True 26 | 27 | DEMANDS_TABLE 28 | source dest traffic name 29 | A F 10 dmd_a_f_1 30 | D F 8 dmd_d_f_1 31 | 32 | RSVP_LSP_TABLE 33 | source dest name configured_setup_bw manual_metric 34 | B D lsp_b_d_1 2 10 35 | B D lsp_b_d_2 9 36 | B D lsp_b_d_3 3 37 | B D lsp_b_d_4 38 | C E lsp_c_e_1 39 | D F lsp_d_f_1 -------------------------------------------------------------------------------- /test/mismatched_ckt_int_capacity_topology_file.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 120 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A F 40 dmd_a_f_1 -------------------------------------------------------------------------------- /test/mismatched_ckt_int_capacity_topology_parallel_links.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 20 125 1 4 | B A B-to-A 20 120 1 5 | B D B-to-D 20 125 2 6 | D B D-to-B 20 125 2 7 | A C A-to-C 30 150 3 8 | C A C-to-A 30 150 3 9 | D C D-to-C 30 150 4 10 | C D C-to-D 30 150 4 11 | A E A-to-E 10 300 5 12 | E A E-to-A 10 300 5 13 | D F D-to-F 10 300 6 14 | F D F-to-D 10 300 6 15 | A D A-to-D 40 20 7 16 | D A D-to-A 40 20 7 17 | B G B-to-G 10 100 8 18 | G B G-to-B 10 100 8 19 | G D G-to-D 10 100 9 20 | D G D-to-G 10 100 9 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A F 40 dmd_a_f_1 -------------------------------------------------------------------------------- /test/model_bad_node_in_demand.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 4 100 4 | B A B-to-A 4 100 5 | A B A-to-B_2 4 50 6 | B A B-to-A_2 4 50 7 | A C A-to-C 1 200 8 | C A C-to-A 1 200 9 | A D A-to-D 8 150 10 | D A D-to-A 8 150 11 | B D B-to-D 4 200 12 | D B D-to-B 4 200 13 | B D B-to-D_2 4 200 14 | D B D-to-B_2 4 200 15 | B E B-to-E 3 200 16 | E B E-to-B 3 200 17 | B E B-to-E_2 3 200 18 | E B E-to-B_2 3 200 19 | B E B-to-E_3 3 200 20 | E B E-to-B_3 3 200 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 25 0 25 | B 31 -177 26 | C 60 -63 27 | D 62 37 28 | E -60 124 29 | F 2 35 30 | G 30 90 31 | H 52 124 32 | 33 | DEMANDS_TABLE 34 | source dest traffic name 35 | A B 75 dmd_a_b_1 36 | A F 22 dmd_a_f_1 37 | A E 24 dmd_a_e_1 38 | F E 80 dmd_f_e_1 39 | F B 50 dmd_f_b_1 40 | D A 10 dmd_d_a_1 41 | A H 20 dmd_a_h_1 42 | C E 20 dmd_c_e_1 43 | B G 30 dmd_b_g_1 44 | E C 20 dmd_e_c_1 45 | A D 20 dmd_a_d_1 46 | A D 20 dmd_a_d_2 47 | A D 20 dmd_a_d_3 48 | B E 30 dmd_b_e_1 49 | Y E 10 dmd_y_e_1 50 | 51 | RSVP_LSP_TABLE 52 | source dest name 53 | A D lsp_a_d_1 54 | A D lsp_a_d_2 55 | F E lsp_f_e_1 56 | A F lsp_a_f_1 57 | A B lsp_a_b_1 -------------------------------------------------------------------------------- /test/model_bad_node_in_lsp.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 4 100 4 | B A B-to-A 4 100 5 | A B A-to-B_2 4 50 6 | B A B-to-A_2 4 50 7 | A C A-to-C 1 200 8 | C A C-to-A 1 200 9 | A D A-to-D 8 150 10 | D A D-to-A 8 150 11 | B D B-to-D 4 200 12 | D B D-to-B 4 200 13 | B D B-to-D_2 4 200 14 | D B D-to-B_2 4 200 15 | B E B-to-E 3 200 16 | E B E-to-B 3 200 17 | B E B-to-E_2 3 200 18 | E B E-to-B_2 3 200 19 | B E B-to-E_3 3 200 20 | E B E-to-B_3 3 200 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 25 0 25 | B 31 -177 26 | C 60 -63 27 | D 62 37 28 | E -60 124 29 | F 2 35 30 | G 30 90 31 | H 52 124 32 | 33 | DEMANDS_TABLE 34 | source dest traffic name 35 | A B 75 dmd_a_b_1 36 | A F 22 dmd_a_f_1 37 | A E 24 dmd_a_e_1 38 | F E 80 dmd_f_e_1 39 | F B 50 dmd_f_b_1 40 | D A 10 dmd_d_a_1 41 | A H 20 dmd_a_h_1 42 | C E 20 dmd_c_e_1 43 | B G 30 dmd_b_g_1 44 | E C 20 dmd_e_c_1 45 | A D 20 dmd_a_d_1 46 | A D 20 dmd_a_d_2 47 | A D 20 dmd_a_d_3 48 | B E 30 dmd_b_e_1 49 | 50 | RSVP_LSP_TABLE 51 | source dest name 52 | A D lsp_a_d_1 53 | A D lsp_a_d_2 54 | F E lsp_f_e_1 55 | A F lsp_a_f_1 56 | A B lsp_a_b_1 57 | Y A lsp_y_a_1 -------------------------------------------------------------------------------- /test/model_test_topology.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | A E A-to-E 10 300 12 | E A E-to-A 10 300 13 | D F D-to-F 10 300 14 | F D F-to-D 10 300 15 | A D A-to-D 40 20 16 | D A D-to-A 40 20 17 | B G B-to-G 10 100 18 | G D G-to-D 10 100 19 | G B G-to-B 10 100 20 | D G D-to-G 10 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 35 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A D 80 dmd_a_d_1 35 | A D 70 dmd_a_d_2 36 | F E 400 dmd_f_e_1 37 | A F 40 dmd_a_f_1 38 | 39 | RSVP_LSP_TABLE 40 | source dest name 41 | A D lsp_a_d_1 42 | A D lsp_a_d_2 43 | F E lsp_f_e_1 -------------------------------------------------------------------------------- /test/model_test_topology_2.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 30 150 8 | C A C-to-A 30 150 9 | D C D-to-C 30 150 10 | C D C-to-D 30 150 11 | D F D-to-F 10 300 12 | F D F-to-D 10 300 13 | A D A-to-D 40 10 14 | D A D-to-A 40 10 15 | B G B-to-G 10 100 16 | G D G-to-D 10 100 17 | G B G-to-B 10 100 18 | D G D-to-G 10 100 19 | A F A-to-F 1000 100 20 | F A F-to-A 1000 100 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 50 0 25 | B 0 50 26 | C 0 50 27 | D 50 0 28 | E 75 0 29 | F 75 0 30 | G 30 30 31 | 32 | DEMANDS_TABLE 33 | source dest traffic name 34 | A D 40 dmd_a_d_1 35 | 36 | RSVP_LSP_TABLE 37 | source dest name 38 | A D lsp_a_d_1 39 | E A lsp_e_a_1 -------------------------------------------------------------------------------- /test/multiple_rsvp_paths.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | B D B-to-D 20 125 6 | D B D-to-B 20 125 7 | A C A-to-C 20 125 8 | C A C-to-A 20 125 9 | D C D-to-C 20 125 10 | C D C-to-D 20 125 11 | 12 | NODES_TABLE 13 | name lon lat 14 | A 50 0 15 | B 0 50 16 | C 0 50 17 | D 50 0 18 | 19 | DEMANDS_TABLE 20 | source dest traffic name 21 | A D 80 dmd_a_d_1 22 | 23 | RSVP_LSP_TABLE 24 | source dest name 25 | A D lsp_a_d_1 26 | -------------------------------------------------------------------------------- /test/parallel_link_model_bad_circuit_id.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 1 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | 16 | NODES_TABLE 17 | name lon lat 18 | A 25 0 19 | B 31 -177 20 | C 60 -63 21 | D 62 37 22 | E -60 124 23 | F 2 35 24 | G 30 90 25 | H 52 124 26 | 27 | DEMANDS_TABLE 28 | source dest traffic name 29 | A B 50 dmd_a_b_1 30 | A F 22 dmd_a_f_1 -------------------------------------------------------------------------------- /test/parallel_link_model_bad_node_in_demand.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 2 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | B E B-to-E 3 200 7 16 | E B E-to-B 3 200 7 17 | B E B-to-E_2 3 200 17 18 | E B E-to-B_2 3 200 17 19 | B E B-to-E_3 3 200 27 20 | E B E-to-B_3 3 200 27 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 25 0 25 | B 31 -177 26 | C 60 -63 27 | D 62 37 28 | E -60 124 29 | F 2 35 30 | G 30 90 31 | H 52 124 32 | 33 | DEMANDS_TABLE 34 | source dest traffic name 35 | A B 75 dmd_a_b_1 36 | A F 22 dmd_a_f_1 37 | A E 24 dmd_a_e_1 38 | F E 80 dmd_f_e_1 39 | F B 50 dmd_f_b_1 40 | D A 10 dmd_d_a_1 41 | A H 20 dmd_a_h_1 42 | C E 20 dmd_c_e_1 43 | B G 30 dmd_b_g_1 44 | E C 20 dmd_e_c_1 45 | A D 20 dmd_a_d_1 46 | A D 20 dmd_a_d_2 47 | A D 20 dmd_a_d_3 48 | B E 30 dmd_b_e_1 49 | Y E 10 dmd_y_e_1 50 | 51 | RSVP_LSP_TABLE 52 | source dest name 53 | A D lsp_a_d_1 54 | A D lsp_a_d_2 55 | F E lsp_f_e_1 56 | A F lsp_a_f_1 57 | A B lsp_a_b_1 -------------------------------------------------------------------------------- /test/parallel_link_model_bad_node_in_lsp.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 2 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | B E B-to-E 3 200 7 16 | E B E-to-B 3 200 7 17 | B E B-to-E_2 3 200 17 18 | E B E-to-B_2 3 200 17 19 | B E B-to-E_3 3 200 27 20 | E B E-to-B_3 3 200 27 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 25 0 25 | B 31 -177 26 | C 60 -63 27 | D 62 37 28 | E -60 124 29 | F 2 35 30 | G 30 90 31 | H 52 124 32 | 33 | DEMANDS_TABLE 34 | source dest traffic name 35 | A B 75 dmd_a_b_1 36 | A F 22 dmd_a_f_1 37 | A E 24 dmd_a_e_1 38 | F E 80 dmd_f_e_1 39 | F B 50 dmd_f_b_1 40 | D A 10 dmd_d_a_1 41 | A H 20 dmd_a_h_1 42 | C E 20 dmd_c_e_1 43 | B G 30 dmd_b_g_1 44 | E C 20 dmd_e_c_1 45 | A D 20 dmd_a_d_1 46 | A D 20 dmd_a_d_2 47 | A D 20 dmd_a_d_3 48 | B E 30 dmd_b_e_1 49 | 50 | RSVP_LSP_TABLE 51 | source dest name 52 | A D lsp_a_d_1 53 | A D lsp_a_d_2 54 | F E lsp_f_e_1 55 | A F lsp_a_f_1 56 | A B lsp_a_b_1 57 | Y A lsp_y_a_1 -------------------------------------------------------------------------------- /test/parallel_link_model_test_topology.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 2 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | B E B-to-E 3 200 7 16 | E B E-to-B 3 200 7 17 | B E B-to-E_2 3 200 17 18 | E B E-to-B_2 3 200 17 19 | B E B-to-E_3 3 200 27 20 | E B E-to-B_3 3 200 27 21 | D C D-to-C 9 150 8 22 | C D C-to-D 9 150 8 23 | D E D-to-E 4 100 9 24 | E D E-to-D 4 100 9 25 | D F D-to-F 3 100 10 26 | F D F-to-D 3 100 10 27 | F B F-to-B 6 100 11 28 | B F B-to-F 6 100 11 29 | G H G-to-H 4 150 12 30 | H G H-to-G 4 150 12 31 | G D G-to-D 2 50 13 32 | D G D-to-G 2 50 13 33 | H E H-to-E 4 100 14 34 | E H E-to-H 4 100 14 35 | E F E-to-F 3 100 15 36 | F E F-to-E 3 100 15 37 | H D H-to-D 4 100 16 38 | D H D-to-H 4 100 16 39 | A X A-to-X 10 100 38 40 | X A X-to-A 10 100 38 41 | Y X Y-to-X 10 100 39 42 | X Y X-to-Y 10 100 39 43 | 44 | NODES_TABLE 45 | name lon lat 46 | A 25 0 47 | B 31 -177 48 | C 60 -63 49 | D 62 37 50 | E -60 124 51 | F 2 35 52 | G 30 90 53 | H 52 124 54 | 55 | DEMANDS_TABLE 56 | source dest traffic name 57 | A B 50 dmd_a_b_1 58 | A F 22 dmd_a_f_1 59 | A E 24 dmd_a_e_1 60 | F E 80 dmd_f_e_1 61 | F B 50 dmd_f_b_1 62 | D A 10 dmd_d_a_1 63 | A H 20 dmd_a_h_1 64 | C E 20 dmd_c_e_1 65 | B G 30 dmd_b_g_1 66 | E C 20 dmd_e_c_1 67 | A D 20 dmd_a_d_1 68 | A D 20 dmd_a_d_2 69 | A D 20 dmd_a_d_3 70 | A Y 20 dmd_a_y_1 71 | 72 | RSVP_LSP_TABLE 73 | source dest name 74 | A D lsp_a_d_1 75 | A D lsp_a_d_2 76 | F E lsp_f_e_1 77 | A F lsp_a_f_1 -------------------------------------------------------------------------------- /test/parallel_link_model_test_topology_igp_only.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 2 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | B E B-to-E 3 200 7 16 | E B E-to-B 3 200 7 17 | B E B-to-E_2 3 200 17 18 | E B E-to-B_2 3 200 17 19 | B E B-to-E_3 3 200 27 20 | E B E-to-B_3 3 200 27 21 | D C D-to-C 9 150 8 22 | C D C-to-D 9 150 8 23 | 24 | NODES_TABLE 25 | name lon lat 26 | A 25 0 27 | B 31 -177 28 | C 60 -63 29 | D 62 37 30 | E -60 124 31 | F 2 35 32 | G 30 90 33 | H 52 124 34 | 35 | DEMANDS_TABLE 36 | source dest traffic name 37 | A E 30 dmd_a_e_1 38 | D A 10 dmd_d_a_1 39 | B G 30 dmd_b_g_1 40 | E C 20 dmd_e_c_1 41 | A D 20 dmd_a_d_1 42 | A D 20 dmd_a_d_2 43 | A D 20 dmd_a_d_3 -------------------------------------------------------------------------------- /test/parallel_link_model_w_lsps.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B 4 100 1 4 | B A B-to-A 4 100 1 5 | A B A-to-B_2 4 50 2 6 | B A B-to-A_2 4 50 2 7 | A C A-to-C 1 200 3 8 | C A C-to-A 1 200 3 9 | A D A-to-D 8 150 4 10 | D A D-to-A 8 150 4 11 | B D B-to-D 4 200 5 12 | D B D-to-B 4 200 5 13 | B D B-to-D_2 4 200 6 14 | D B D-to-B_2 4 200 6 15 | B E B-to-E 3 200 7 16 | E B E-to-B 3 200 7 17 | B E B-to-E_2 3 200 17 18 | E B E-to-B_2 3 200 17 19 | B E B-to-E_3 3 200 27 20 | E B E-to-B_3 3 200 27 21 | 22 | NODES_TABLE 23 | name lon lat 24 | A 25 0 25 | B 31 -177 26 | C 60 -63 27 | D 62 37 28 | E -60 124 29 | F 2 35 30 | G 30 90 31 | H 52 124 32 | 33 | DEMANDS_TABLE 34 | source dest traffic name 35 | A B 75 dmd_a_b_1 36 | A F 22 dmd_a_f_1 37 | A E 24 dmd_a_e_1 38 | F E 80 dmd_f_e_1 39 | F B 50 dmd_f_b_1 40 | D A 10 dmd_d_a_1 41 | A H 20 dmd_a_h_1 42 | C E 20 dmd_c_e_1 43 | B G 30 dmd_b_g_1 44 | E C 20 dmd_e_c_1 45 | A D 20 dmd_a_d_1 46 | A D 20 dmd_a_d_2 47 | A D 20 dmd_a_d_3 48 | B E 30 dmd_b_e_1 49 | 50 | RSVP_LSP_TABLE 51 | source dest name 52 | A D lsp_a_d_1 53 | A D lsp_a_d_2 54 | F E lsp_f_e_1 55 | A F lsp_a_f_1 56 | A B lsp_a_b_1 57 | -------------------------------------------------------------------------------- /test/rsvp_lsp_effective_metric_update.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity 3 | A B A-to-B 20 125 4 | B A B-to-A 20 125 5 | A C A-to-C 15 100 6 | C A C-to-A 15 100 7 | B C B-to-C 15 100 8 | C B C-to-B 15 100 9 | 10 | NODES_TABLE 11 | name lon lat 12 | A 50 0 13 | B 0 -50 14 | C 0 50 15 | 16 | DEMANDS_TABLE 17 | source dest traffic name 18 | A C 80 dmd_a_c_1 19 | 20 | RSVP_LSP_TABLE 21 | source dest name configured_setup_bw manual_metric 22 | A B lsp_a_b_1 23 | -------------------------------------------------------------------------------- /test/test_circuit.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import Node 4 | from pyNTM import PerformanceModel 5 | from pyNTM import Circuit 6 | from pyNTM import Interface 7 | 8 | 9 | class TestCircuit(unittest.TestCase): 10 | @classmethod 11 | def setUpClass(self): 12 | self.maxDiff = None 13 | self.node_a = Node(name="nodeA", lat=0, lon=0) 14 | self.node_b = Node(name="nodeB", lat=0, lon=0) 15 | self.interface_a = Interface( 16 | name="inerfaceA-to-B", 17 | cost=4, 18 | capacity=100, 19 | node_object=self.node_a, 20 | remote_node_object=self.node_b, 21 | circuit_id=1, 22 | ) 23 | self.interface_b = Interface( 24 | name="inerfaceB-to-A", 25 | cost=4, 26 | capacity=100, 27 | node_object=self.node_b, 28 | remote_node_object=self.node_a, 29 | circuit_id=1, 30 | ) 31 | self.model = PerformanceModel( 32 | interface_objects=set([self.interface_a, self.interface_b]), 33 | node_objects=set([self.node_a, self.node_b]), 34 | demand_objects=set([]), 35 | rsvp_lsp_objects=set([]), 36 | ) 37 | self.circuit = Circuit(self.interface_a, self.interface_b) 38 | 39 | def test_repr(self): 40 | self.assertEqual( 41 | repr(self.circuit), 42 | "Circuit(Interface(name = 'inerfaceA-to-B', cost = 4, capacity = 100, node_object = Node('nodeA'), " 43 | "remote_node_object = Node('nodeB'), circuit_id = 1), Interface(name = 'inerfaceB-to-A', cost = 4, " 44 | "capacity = 100, node_object = Node('nodeB'), remote_node_object = Node('nodeA'), circuit_id = 1))", 45 | ) # noqa E501 46 | 47 | def test_key(self): 48 | self.assertEqual( 49 | self.circuit._key(), 50 | (("inerfaceA-to-B", "nodeA"), ("inerfaceB-to-A", "nodeB")), 51 | ) 52 | 53 | def test_get_circuit_interfaces(self): 54 | (interface_a, interface_b) = self.circuit.get_circuit_interfaces(self.model) 55 | self.assertEqual(interface_a, self.interface_a) 56 | self.assertEqual(interface_b, self.interface_b) 57 | -------------------------------------------------------------------------------- /test/test_demand_parallel_link_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | Confirming behavior of Demand in Parallel_Link_Model 3 | """ 4 | 5 | import unittest 6 | 7 | from pyNTM import Node 8 | from pyNTM import Demand 9 | from pyNTM import FlexModel 10 | from pyNTM import Interface 11 | from pyNTM import RSVP_LSP 12 | 13 | 14 | class TestDemand(unittest.TestCase): 15 | @classmethod 16 | def setUpClass(self): 17 | self.maxDiff = None 18 | self.node_a = Node(name="nodeA", lat=0, lon=0) 19 | self.node_b = Node(name="nodeB", lat=0, lon=0) 20 | self.interface_a = Interface( 21 | name="inerfaceA-to-B", 22 | cost=4, 23 | capacity=100, 24 | node_object=self.node_a, 25 | remote_node_object=self.node_b, 26 | circuit_id=1, 27 | ) 28 | self.interface_b = Interface( 29 | name="inerfaceB-to-A", 30 | cost=4, 31 | capacity=100, 32 | node_object=self.node_b, 33 | remote_node_object=self.node_a, 34 | circuit_id=1, 35 | ) 36 | self.rsvp_lsp_a = RSVP_LSP( 37 | source_node_object=self.node_a, 38 | dest_node_object=self.node_b, 39 | lsp_name="A-to-B", 40 | ) 41 | self.model = FlexModel( 42 | interface_objects=set([self.interface_a, self.interface_b]), 43 | node_objects=set([self.node_a, self.node_b]), 44 | demand_objects=set([]), 45 | rsvp_lsp_objects=set([self.rsvp_lsp_a]), 46 | ) 47 | self.demand = Demand( 48 | source_node_object=self.node_a, 49 | dest_node_object=self.node_b, 50 | traffic=10, 51 | name="A-to-B", 52 | ) 53 | 54 | def test_init_fail_neg_traffic(self): 55 | with self.assertRaises(ValueError): 56 | Demand( 57 | source_node_object=self.node_a, 58 | dest_node_object=self.node_b, 59 | traffic=-1, 60 | name="A-to-B", 61 | ) 62 | 63 | def test_repr(self): 64 | self.assertEqual( 65 | repr(self.demand), 66 | "Demand(source = nodeA, dest = nodeB, traffic = 10, name = 'A-to-B')", 67 | ) 68 | 69 | def test_key(self): 70 | self.assertEqual( 71 | self.demand._key, (Node("nodeA").name, Node("nodeB").name, "A-to-B") 72 | ) 73 | 74 | def test_demand_behavior(self): 75 | model = FlexModel.load_model_file("test/parallel_link_model_test_topology.csv") 76 | 77 | model.update_simulation() 78 | 79 | dmd_a_f = model.get_demand_object("A", "F", "dmd_a_f_1") 80 | # int_a_b = model.get_interface_object('A-to-B', 'A') 81 | # int_b_d = model.get_interface_object('B-to-D', 'B') 82 | # int_b_g = model.get_interface_object('B-to-G', 'B') 83 | # int_g_d = model.get_interface_object('G-to-D', 'G') 84 | # int_d_f = model.get_interface_object('D-to-F', 'D') 85 | # int_a_c = model.get_interface_object('A-to-C', 'A') 86 | # int_c_d = model.get_interface_object('C-to-D', 'C') 87 | # int_a_d = model.get_interface_object('A-to-D', 'A') 88 | 89 | # Demand routes initially 90 | self.assertNotEqual(dmd_a_f.path, "Unrouted") 91 | 92 | # Demand should not route if source node is down 93 | model.fail_node("A") 94 | model.update_simulation() 95 | self.assertEqual(dmd_a_f.path, "Unrouted") 96 | 97 | # Demand should route when source node unfails 98 | model.unfail_node("A") 99 | model.update_simulation() 100 | self.assertNotEqual(dmd_a_f.path, "Unrouted") 101 | 102 | # Demand should not route when dest node fails 103 | model.fail_node("F") 104 | model.update_simulation() 105 | self.assertEqual(dmd_a_f.path, "Unrouted") 106 | 107 | # Demand should route when dest node unfails 108 | model.unfail_node("F") 109 | model.update_simulation() 110 | self.assertNotEqual(dmd_a_f.path, "Unrouted") 111 | 112 | def test_unroutable_demand(self): 113 | node_a = Node(name="nodeA", lat=0, lon=0) 114 | node_b = Node(name="nodeB", lat=0, lon=0) 115 | node_d = Node(name="nodeD") 116 | interface_a = Interface( 117 | name="inerfaceA-to-B", 118 | cost=4, 119 | capacity=100, 120 | node_object=node_a, 121 | remote_node_object=node_b, 122 | circuit_id=1, 123 | ) 124 | interface_b = Interface( 125 | name="inerfaceB-to-A", 126 | cost=4, 127 | capacity=100, 128 | node_object=node_b, 129 | remote_node_object=node_a, 130 | circuit_id=1, 131 | ) 132 | dmd_a_d = Demand(node_a, node_d, traffic=10) 133 | model = FlexModel( 134 | interface_objects=set([interface_a, interface_b]), 135 | node_objects=set([node_a, node_b, node_d]), 136 | demand_objects=set([dmd_a_d]), 137 | rsvp_lsp_objects=set([]), 138 | ) 139 | model.update_simulation() 140 | 141 | self.assertEqual(dmd_a_d.path, "Unrouted") 142 | 143 | def test_demand_on_lsp(self): 144 | """ 145 | Ensure the demand takes an available LSP 146 | :return: 147 | """ 148 | node_a = Node(name="nodeA", lat=0, lon=0) 149 | node_b = Node(name="nodeB", lat=0, lon=0) 150 | node_d = Node(name="nodeD") 151 | interface_a = Interface( 152 | name="inerfaceA-to-B", 153 | cost=4, 154 | capacity=100, 155 | node_object=node_a, 156 | remote_node_object=node_b, 157 | circuit_id=1, 158 | ) 159 | interface_b = Interface( 160 | name="inerfaceB-to-A", 161 | cost=4, 162 | capacity=100, 163 | node_object=node_b, 164 | remote_node_object=node_a, 165 | circuit_id=1, 166 | ) 167 | dmd_a_b = Demand(node_a, node_b, traffic=10) 168 | 169 | lsp_a_b = RSVP_LSP( 170 | source_node_object=node_a, dest_node_object=node_b, lsp_name="lsp_a_b" 171 | ) 172 | 173 | model = FlexModel( 174 | interface_objects=set([interface_a, interface_b]), 175 | node_objects=set([node_a, node_b, node_d]), 176 | demand_objects=set([dmd_a_b]), 177 | rsvp_lsp_objects=set([lsp_a_b]), 178 | ) 179 | 180 | model.update_simulation() 181 | 182 | self.assertEqual( 183 | str(dmd_a_b.path), 184 | "[[RSVP_LSP(source = nodeA, dest = nodeB, lsp_name = 'lsp_a_b')]]", 185 | ) 186 | -------------------------------------------------------------------------------- /test/test_igp_ecmp_routing.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import PerformanceModel 4 | 5 | 6 | class TestIGPRouting(unittest.TestCase): 7 | def test_ecmp(self): 8 | 9 | model8 = PerformanceModel() 10 | model8.rsvp_lsp_objects = set([]) 11 | model8.demand_objects = set([]) 12 | 13 | int_list = [ 14 | { 15 | "name": "E-to-A", 16 | "cost": 10, 17 | "capacity": 300, 18 | "node": "E", 19 | "remote_node": "A", 20 | "circuit_id": 1, 21 | "failed": False, 22 | }, 23 | { 24 | "name": "C-to-D", 25 | "cost": 30, 26 | "capacity": 150, 27 | "node": "C", 28 | "remote_node": "D", 29 | "circuit_id": 5, 30 | "failed": False, 31 | }, 32 | { 33 | "name": "D-to-C", 34 | "cost": 30, 35 | "capacity": 150, 36 | "node": "D", 37 | "remote_node": "C", 38 | "circuit_id": 5, 39 | "failed": False, 40 | }, 41 | { 42 | "name": "A-to-E", 43 | "cost": 10, 44 | "capacity": 300, 45 | "node": "A", 46 | "remote_node": "E", 47 | "circuit_id": 1, 48 | "failed": False, 49 | }, 50 | { 51 | "name": "A-to-D", 52 | "cost": 40, 53 | "capacity": 20, 54 | "node": "A", 55 | "remote_node": "D", 56 | "circuit_id": 2, 57 | "failed": False, 58 | }, 59 | { 60 | "name": "D-to-A", 61 | "cost": 40, 62 | "capacity": 20, 63 | "node": "D", 64 | "remote_node": "A", 65 | "circuit_id": 2, 66 | "failed": False, 67 | }, 68 | { 69 | "name": "G-to-D", 70 | "cost": 10, 71 | "capacity": 100, 72 | "node": "G", 73 | "remote_node": "D", 74 | "circuit_id": 7, 75 | "failed": False, 76 | }, 77 | { 78 | "name": "C-to-A", 79 | "cost": 30, 80 | "capacity": 150, 81 | "node": "C", 82 | "remote_node": "A", 83 | "circuit_id": 3, 84 | "failed": False, 85 | }, 86 | { 87 | "name": "D-to-F", 88 | "cost": 10, 89 | "capacity": 300, 90 | "node": "D", 91 | "remote_node": "F", 92 | "circuit_id": 6, 93 | "failed": False, 94 | }, 95 | { 96 | "name": "F-to-D", 97 | "cost": 10, 98 | "capacity": 300, 99 | "node": "F", 100 | "remote_node": "D", 101 | "circuit_id": 6, 102 | "failed": False, 103 | }, 104 | { 105 | "name": "D-to-G", 106 | "cost": 10, 107 | "capacity": 100, 108 | "node": "D", 109 | "remote_node": "G", 110 | "circuit_id": 7, 111 | "failed": False, 112 | }, 113 | { 114 | "name": "B-to-A", 115 | "cost": 20, 116 | "capacity": 125, 117 | "node": "B", 118 | "remote_node": "A", 119 | "circuit_id": 4, 120 | "failed": False, 121 | }, 122 | { 123 | "name": "D-to-B", 124 | "cost": 20, 125 | "capacity": 125, 126 | "node": "D", 127 | "remote_node": "B", 128 | "circuit_id": 8, 129 | "failed": False, 130 | }, 131 | { 132 | "name": "B-to-G", 133 | "cost": 10, 134 | "capacity": 100, 135 | "node": "B", 136 | "remote_node": "G", 137 | "circuit_id": 9, 138 | "failed": False, 139 | }, 140 | { 141 | "name": "A-to-C", 142 | "cost": 30, 143 | "capacity": 150, 144 | "node": "A", 145 | "remote_node": "C", 146 | "circuit_id": 3, 147 | "failed": False, 148 | }, 149 | { 150 | "name": "B-to-D", 151 | "cost": 20, 152 | "capacity": 125, 153 | "node": "B", 154 | "remote_node": "D", 155 | "circuit_id": 8, 156 | "failed": False, 157 | }, 158 | { 159 | "name": "G-to-B", 160 | "cost": 10, 161 | "capacity": 100, 162 | "node": "G", 163 | "remote_node": "B", 164 | "circuit_id": 9, 165 | "failed": False, 166 | }, 167 | { 168 | "name": "A-to-B", 169 | "cost": 20, 170 | "capacity": 125, 171 | "node": "A", 172 | "remote_node": "B", 173 | "circuit_id": 4, 174 | "failed": False, 175 | }, 176 | ] 177 | 178 | model8.add_network_interfaces_from_list(int_list) 179 | 180 | demands = [ 181 | {"source": "A", "dest": "F", "traffic": 40, "name": "dmd_a_f_1"}, 182 | ] 183 | 184 | for demand in demands: 185 | model8.add_demand( 186 | demand["source"], demand["dest"], demand["traffic"], demand["name"] 187 | ) 188 | 189 | int_a_b = model8.get_interface_object("A-to-B", "A") 190 | int_b_d = model8.get_interface_object("B-to-D", "B") 191 | int_b_g = model8.get_interface_object("B-to-G", "B") 192 | int_g_d = model8.get_interface_object("G-to-D", "G") 193 | int_d_f = model8.get_interface_object("D-to-F", "D") 194 | int_a_c = model8.get_interface_object("A-to-C", "A") 195 | int_c_d = model8.get_interface_object("C-to-D", "C") 196 | int_a_d = model8.get_interface_object("A-to-D", "A") 197 | 198 | model8.update_simulation() 199 | 200 | self.assertEqual(int_a_b.traffic, 20) 201 | self.assertEqual(int_b_d.traffic, 10) 202 | self.assertEqual(int_d_f.traffic, 40) 203 | self.assertEqual(int_b_g.traffic, 10) 204 | self.assertEqual(int_g_d.traffic, 10) 205 | self.assertEqual(int_a_c.traffic, 0) 206 | self.assertEqual(int_c_d.traffic, 0) 207 | self.assertEqual(int_a_d.traffic, 20) 208 | -------------------------------------------------------------------------------- /test/test_interface_parallel_link_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | Confirming behavior of Interface object in Parallel_Link_Model 3 | """ 4 | 5 | import unittest 6 | 7 | from pyNTM import FlexModel 8 | from pyNTM import ModelException 9 | 10 | 11 | class TestInterface(unittest.TestCase): 12 | def test_demands_non_failed_int(self): 13 | model = FlexModel.load_model_file( 14 | "test/parallel_link_model_test_topology_igp_only.csv" 15 | ) 16 | model.update_simulation() 17 | 18 | int_b_e = model.get_interface_object("B-to-E", "B") 19 | 20 | self.assertTrue(int_b_e.demands(model) != []) 21 | 22 | def test_traffic_non_failed_int(self): 23 | model = FlexModel.load_model_file( 24 | "test/parallel_link_model_test_topology_igp_only.csv" 25 | ) 26 | model.update_simulation() 27 | 28 | int_b_e = model.get_interface_object("B-to-E", "B") 29 | 30 | self.assertTrue(int_b_e.traffic, 10) 31 | 32 | def test_demands_non_failed(self): 33 | model = FlexModel.load_model_file( 34 | "test/parallel_link_model_test_topology_igp_only.csv" 35 | ) 36 | model.update_simulation() 37 | 38 | int_b_e = model.get_interface_object("B-to-E", "B") 39 | dmd_a_e_1 = model.get_demand_object("A", "E", "dmd_a_e_1") 40 | 41 | self.assertEqual(int_b_e.demands(model), [dmd_a_e_1]) 42 | 43 | def test_traffic_failed_int(self): 44 | model = FlexModel.load_model_file( 45 | "test/parallel_link_model_test_topology_igp_only.csv" 46 | ) 47 | model.update_simulation() 48 | model.fail_interface("A-to-B", "A") 49 | model.update_simulation() 50 | 51 | int_a_b = model.get_interface_object("A-to-B", "A") 52 | 53 | self.assertEqual(int_a_b.traffic, "Down") 54 | 55 | def test_dmd_failed_int(self): 56 | model = FlexModel.load_model_file( 57 | "test/parallel_link_model_test_topology_igp_only.csv" 58 | ) 59 | model.update_simulation() 60 | 61 | int_a_b = model.get_interface_object("A-to-B", "A") 62 | 63 | self.assertTrue(len(int_a_b.demands(model)) != 0) 64 | 65 | model.fail_interface("A-to-B", "A") 66 | model.update_simulation() 67 | 68 | self.assertEqual(int_a_b.demands(model), []) 69 | 70 | def test_failed_node(self): 71 | model = FlexModel.load_model_file( 72 | "test/parallel_link_model_test_topology_igp_only.csv" 73 | ) 74 | model.update_simulation() 75 | 76 | int_a_b = model.get_interface_object("A-to-B", "A") 77 | 78 | model.fail_node("A") 79 | model.update_simulation() 80 | 81 | self.assertTrue(int_a_b.failed) 82 | 83 | def test_remote_int_failed(self): 84 | model = FlexModel.load_model_file( 85 | "test/parallel_link_model_test_topology_igp_only.csv" 86 | ) 87 | model.update_simulation() 88 | 89 | int_b_a = model.get_interface_object("B-to-A", "B") 90 | 91 | model.fail_interface("A-to-B", "A") 92 | model.update_simulation() 93 | 94 | self.assertTrue(int_b_a.failed) 95 | 96 | def test_unfail_int_failed_node(self): 97 | model = FlexModel.load_model_file( 98 | "test/parallel_link_model_test_topology_igp_only.csv" 99 | ) 100 | model.update_simulation() 101 | 102 | int_a_b = model.get_interface_object("A-to-B", "A") 103 | 104 | model.fail_node("A") 105 | model.update_simulation() 106 | 107 | err_msg = "Local and/or remote node are failed; cannot have unfailed interface on failed node" 108 | 109 | with self.assertRaises(ModelException) as context: 110 | int_a_b.unfail_interface(model) 111 | 112 | self.assertTrue(err_msg in context.exception.args[0]) 113 | 114 | def test_get_ckt(self): 115 | model = FlexModel.load_model_file( 116 | "test/parallel_link_model_test_topology_igp_only.csv" 117 | ) 118 | model.update_simulation() 119 | int_a_b = model.get_interface_object("A-to-B", "A") 120 | 121 | ckt1 = model.get_circuit_object_from_interface("A-to-B", "A") 122 | ckt2 = int_a_b.get_circuit_object(model) 123 | 124 | self.assertEqual(ckt1, ckt2) 125 | 126 | def test_utilization(self): 127 | model = FlexModel.load_model_file( 128 | "test/parallel_link_model_test_topology_igp_only.csv" 129 | ) 130 | model.update_simulation() 131 | 132 | int_b_e = model.get_interface_object("B-to-E", "B") 133 | 134 | util = (30 / 3 / 200) * 100 135 | 136 | self.assertEqual(int_b_e.utilization, util) 137 | 138 | model.fail_interface("B-to-E", "B") 139 | model.update_simulation() 140 | self.assertEqual(int_b_e.utilization, "Int is down") 141 | 142 | # Test failed interface makes circuit.failed=True 143 | def test_ckt_failure(self): 144 | model = FlexModel.load_model_file("test/parallel_link_model_w_lsps.csv") 145 | model.update_simulation() 146 | 147 | model.fail_interface("A-to-B", "A") 148 | model.update_simulation() 149 | 150 | ckt_1 = model.get_circuit_object_from_interface("A-to-B", "A") 151 | 152 | self.assertTrue(ckt_1.failed(model)) 153 | 154 | def test_ckt_non_failure(self): 155 | model = FlexModel.load_model_file( 156 | "test/parallel_link_model_test_topology_igp_only.csv" 157 | ) 158 | model.update_simulation() 159 | 160 | ckt_1 = model.get_circuit_object_from_interface("A-to-B", "A") 161 | 162 | self.assertFalse(ckt_1.failed(model)) 163 | 164 | def test_equality(self): 165 | model = FlexModel.load_model_file( 166 | "test/parallel_link_model_test_topology_igp_only.csv" 167 | ) 168 | model.update_simulation() 169 | ckt_1 = model.get_circuit_object_from_interface("A-to-B", "A") 170 | int_a, int_b = ckt_1.get_circuit_interfaces(model) 171 | 172 | self.assertNotEqual(int_a, int_b) 173 | 174 | def test_reserved_bw_failed(self): 175 | model = FlexModel.load_model_file("test/parallel_link_model_w_lsps.csv") 176 | model.update_simulation() 177 | int_a_b = model.get_interface_object("A-to-B", "A") 178 | 179 | self.assertNotEqual(int_a_b.reserved_bandwidth, 0) 180 | 181 | model.fail_node("A") 182 | model.update_simulation() 183 | 184 | int_a_b.failed = False 185 | self.assertTrue(int_a_b.failed) 186 | self.assertEqual(int_a_b.reserved_bandwidth, 0) 187 | 188 | def test_unfail_interface(self): 189 | model = FlexModel.load_model_file( 190 | "test/parallel_link_model_test_topology_igp_only.csv" 191 | ) 192 | model.update_simulation() 193 | int_a_b = model.get_interface_object("A-to-B", "A") 194 | int_b_a = model.get_interface_object("B-to-A", "B") 195 | 196 | model.fail_interface("A-to-B", "A") 197 | model.update_simulation() 198 | 199 | self.assertTrue(int_b_a.failed) 200 | self.assertTrue(int_a_b.failed) 201 | 202 | int_a_b.unfail_interface(model) 203 | model.update_simulation() 204 | self.assertFalse(int_a_b.failed) 205 | 206 | def test_demands_on_interface_via_lsps(self): 207 | model = FlexModel.load_model_file("test/parallel_link_model_w_lsps.csv") 208 | model.update_simulation() 209 | int_a_b = model.get_interface_object("A-to-B", "A") 210 | dmd_a_b_1 = model.get_demand_object("A", "B", "dmd_a_b_1") # Rides an LSP 211 | dmd_c_e_1 = model.get_demand_object("C", "E", "dmd_c_e_1") # IGP routed 212 | dmd_a_e_1 = model.get_demand_object("A", "E", "dmd_a_e_1") # IGP routed 213 | 214 | self.assertEqual(len(int_a_b.demands(model)), 3) 215 | self.assertTrue(dmd_a_b_1 in int_a_b.demands(model)) 216 | self.assertTrue(dmd_c_e_1 in int_a_b.demands(model)) 217 | self.assertTrue(dmd_a_e_1 in int_a_b.demands(model)) 218 | -------------------------------------------------------------------------------- /test/test_node.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import Node 4 | from pyNTM import PerformanceModel 5 | from pyNTM import Interface 6 | from pyNTM import ModelException 7 | 8 | 9 | class TestNode(unittest.TestCase): 10 | @classmethod 11 | def setUpClass(self): 12 | self.maxDiff = None 13 | self.node_a = Node(name="nodeA", lat=0, lon=0) 14 | self.node_b = Node(name="nodeB", lat=0, lon=0) 15 | self.interface_a = Interface( 16 | name="inerfaceA-to-B", 17 | cost=4, 18 | capacity=100, 19 | node_object=self.node_a, 20 | remote_node_object=self.node_b, 21 | circuit_id=1, 22 | ) 23 | self.interface_b = Interface( 24 | name="inerfaceB-to-A", 25 | cost=4, 26 | capacity=100, 27 | node_object=self.node_b, 28 | remote_node_object=self.node_a, 29 | circuit_id=1, 30 | ) 31 | self.model = PerformanceModel( 32 | interface_objects=set([self.interface_a, self.interface_b]), 33 | node_objects=set([self.node_a, self.node_b]), 34 | demand_objects=set([]), 35 | rsvp_lsp_objects=set([]), 36 | ) 37 | 38 | def test_eq(self): 39 | if self.node_a == self.node_a: 40 | self.assertTrue(True) 41 | 42 | def test_repr(self): 43 | self.assertEqual(repr(self.node_a), "Node('nodeA')") 44 | 45 | def test_key(self): 46 | self.assertEqual(self.node_a._key(), "nodeA") 47 | 48 | def test_lat_error(self): 49 | with self.assertRaises(ValueError): 50 | Node(name="nodeC", lat="twelve", lon=0) 51 | 52 | def test_lon_error(self): 53 | with self.assertRaises(ValueError): 54 | Node(name="nodeC", lat=0, lon="the number two") 55 | 56 | def test_lat_error_2(self): 57 | with self.assertRaises(ValueError): 58 | self.node_a.lat = "twelve" 59 | 60 | def test_lon_error_2(self): 61 | with self.assertRaises(ValueError): 62 | self.node_a.lon = "twelve" 63 | 64 | def test_set_failed(self): 65 | self.node_a.failed = True 66 | self.assertEqual(True, self.node_a.failed) 67 | 68 | def test_set_failed_error(self): 69 | with self.assertRaises(ModelException): 70 | self.node_a.failed = 100 71 | 72 | def test_interfaces(self): 73 | iface_list = self.node_a.interfaces(self.model) 74 | self.assertEqual([self.interface_a], iface_list) 75 | 76 | def test_adjacent_nodes(self): 77 | node_list = self.node_a.adjacent_nodes(self.model) 78 | self.assertEqual({self.node_b}, node_list) 79 | 80 | def test_fail_node(self): 81 | model = PerformanceModel.load_model_file("test/igp_routing_topology.csv") 82 | model.update_simulation() 83 | 84 | model.fail_node("A") 85 | model.update_simulation() 86 | 87 | node_a = model.get_node_object("A") 88 | 89 | self.assertTrue(node_a.failed) 90 | -------------------------------------------------------------------------------- /test/test_rsvp_3rd_lsp_2_paths_parallel_links.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id 3 | A B A-to-B_1 10 135 1 4 | B A B-to-A_1 10 135 1 5 | A B A-to-B_2 10 140 2 6 | B A B-to-A_2 10 140 2 7 | A C A-to-C_1 10 90 3 8 | C A C-to-A_1 10 90 3 9 | A C A-to-C_2 10 90 4 10 | C A C-to-A_2 10 90 4 11 | C D C-to-D_1 10 50 5 12 | D C D-to-C_1 10 50 5 13 | C D C-to-D_2 10 70 6 14 | D C D-to-C_2 10 70 6 15 | B D B-to-D_1 10 300 7 16 | D B D-to-B_1 10 300 7 17 | D E D-to-E_1 10 300 8 18 | E D E-to-D_1 10 300 8 19 | 20 | NODES_TABLE 21 | name lon lat 22 | A 50 0 23 | B 0 50 24 | C 0 50 25 | D 50 0 26 | E 75 0 27 | 28 | DEMANDS_TABLE 29 | source dest traffic name 30 | A E 270 dmd_a_e_1 31 | 32 | RSVP_LSP_TABLE 33 | source dest name 34 | A E lsp_a_e_1 35 | A E lsp_a_e_2 36 | A E lsp_a_e_3 -------------------------------------------------------------------------------- /test/test_rsvp_3rd_lsp_two_paths.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import PerformanceModel 4 | 5 | 6 | class TestRSVPLSPAddLSP3LSPs(unittest.TestCase): 7 | @classmethod 8 | def setUpClass(self): 9 | self.model = PerformanceModel.load_model_file("test/model_test_topology.csv") 10 | 11 | self.lsp_a_d_1 = self.model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 12 | self.lsp_a_d_2 = self.model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 13 | self.lsp_f_e_1 = self.model.get_rsvp_lsp("F", "E", "lsp_f_e_1") 14 | self.int_a_b = self.model.get_interface_object("A-to-B", "A") 15 | self.int_a_c = self.model.get_interface_object("A-to-C", "A") 16 | 17 | self.model.add_rsvp_lsp("A", "D", "lsp_a_d_3") 18 | self.lsp_a_d_3 = self.model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 19 | self.model.add_demand("A", "D", 100, "dmd_a_d_3") 20 | self.model.update_simulation() 21 | 22 | # Each LSP will attempt to signal for 250 traffic/3 LSPs = 83.3 traffic/lsp. 23 | # Since any path from A to D cannot fit 83.3*2 traffic, only 2 of the LSPs 24 | # will be able to signal and reserve a setup bandwidth of 83.3 traffic 25 | def test_1_lsp_unrouted(self): 26 | # One of the 3 LSPs will not set up 27 | self.assertEqual( 28 | [ 29 | self.lsp_a_d_1.reserved_bandwidth, 30 | self.lsp_a_d_2.reserved_bandwidth, 31 | self.lsp_a_d_3.reserved_bandwidth, 32 | ].count("Unrouted"), 33 | 1, 34 | ) 35 | 36 | # Once the 2 LSPs that do initially signal for 83.3 traffic, each will have 37 | # room to signal for and reserve more traffic: 250 traffic/2 lsps = 125 traffic/lsp 38 | def test_auto_bw_adjust(self): 39 | # The 2 LSPs that do set up will have setup_bandwidth of 125 40 | self.assertEqual( 41 | [ 42 | self.lsp_a_d_1.reserved_bandwidth, 43 | self.lsp_a_d_2.reserved_bandwidth, 44 | self.lsp_a_d_3.reserved_bandwidth, 45 | ].count(125.0), 46 | 2, 47 | ) 48 | -------------------------------------------------------------------------------- /test/test_rsvp_3rd_lsp_two_paths_parallel_links.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import FlexModel 4 | 5 | 6 | class TestRSVPLSPAddLSP3LSPs(unittest.TestCase): 7 | @classmethod 8 | def setUpClass(self): 9 | self.model = FlexModel.load_model_file( 10 | "test/test_rsvp_3rd_lsp_2_paths_parallel_links.csv" 11 | ) 12 | self.lsp_a_e_1 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_1") 13 | self.lsp_a_e_2 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_2") 14 | self.lsp_a_e_3 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_3") 15 | self.model.update_simulation() 16 | 17 | # Each LSP will attempt to signal for 270 traffic/3 LSPs = 90 traffic/lsp. 18 | # Since any path from A to E cannot fit 90*2 traffic, only 2 of the LSPs 19 | # will be able to signal and reserve a setup bandwidth of 90 traffic 20 | def test_1_lsp_unrouted(self): 21 | # One of the 3 LSPs will not set up 22 | self.assertEqual( 23 | [ 24 | self.lsp_a_e_1.reserved_bandwidth, 25 | self.lsp_a_e_2.reserved_bandwidth, 26 | self.lsp_a_e_3.reserved_bandwidth, 27 | ].count("Unrouted"), 28 | 1, 29 | ) 30 | 31 | def test_auto_bw_adjust(self): 32 | # Once the 2 LSPs that do initially signal for 90 traffic, 33 | # each will have room to signal for and reserve more 34 | # traffic: 270 traffic/2 lsps = 135 traffic/lsp 35 | self.assertEqual( 36 | [ 37 | self.lsp_a_e_1.reserved_bandwidth, 38 | self.lsp_a_e_2.reserved_bandwidth, 39 | self.lsp_a_e_3.reserved_bandwidth, 40 | ].count(135.0), 41 | 2, 42 | ) 43 | int_a_b_1 = self.model.get_interface_object("A-to-B_1", "A") 44 | int_a_b_2 = self.model.get_interface_object("A-to-B_2", "A") 45 | # The 2 LSPs that do set up will have setup_bandwidth 46 | # of 135 units 47 | self.assertEqual(int_a_b_1.reserved_bandwidth, 135.0) 48 | self.assertEqual(int_a_b_2.reserved_bandwidth, 135.0) 49 | 50 | def test_auto_bw_adjust_2(self): 51 | # Add 4th LSP from A to E 52 | self.model.add_rsvp_lsp("A", "E", "lsp_a_e_4") 53 | self.model.update_simulation() 54 | 55 | lsp_a_e_4 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_4") 56 | int_a_b_1 = self.model.get_interface_object("A-to-B_1", "A") 57 | int_a_b_2 = self.model.get_interface_object("A-to-B_2", "A") 58 | 59 | # Each LSP should have 67.5 units of reserved bandwidth 60 | self.assertEqual( 61 | [ 62 | self.lsp_a_e_1.reserved_bandwidth, 63 | self.lsp_a_e_2.reserved_bandwidth, 64 | self.lsp_a_e_3.reserved_bandwidth, 65 | lsp_a_e_4.reserved_bandwidth, 66 | ].count(67.5), 67 | 4, 68 | ) 69 | 70 | # Each LSP should have 67.5 units of traffic 71 | self.assertEqual( 72 | [ 73 | self.lsp_a_e_1.traffic_on_lsp(self.model), 74 | self.lsp_a_e_2.traffic_on_lsp(self.model), 75 | self.lsp_a_e_3.traffic_on_lsp(self.model), 76 | lsp_a_e_4.traffic_on_lsp(self.model), 77 | ].count(67.5), 78 | 4, 79 | ) 80 | 81 | # At least one of the interfaces from A to B will have a 82 | # reserved_bandwidth of 135.0 units 83 | self.assertTrue( 84 | [int_a_b_1.reserved_bandwidth, int_a_b_2.reserved_bandwidth].count(135.0) 85 | >= 1 86 | ) 87 | 88 | def test_add_more_traffic(self): 89 | # Add 4th LSP from A to E (use an isolated model for this) 90 | model = FlexModel.load_model_file( 91 | "test/test_rsvp_3rd_lsp_2_paths_parallel_links.csv" 92 | ) 93 | model.add_rsvp_lsp("A", "E", "lsp_a_e_4") 94 | model.update_simulation() 95 | 96 | # Add more traffic from A to E; total now is 270 + 60 units 97 | model.add_demand("A", "E", 60, "dmd_a_e_2") 98 | model.update_simulation() 99 | 100 | lsp_a_e_1 = model.get_rsvp_lsp("A", "E", "lsp_a_e_1") 101 | lsp_a_e_2 = model.get_rsvp_lsp("A", "E", "lsp_a_e_2") 102 | lsp_a_e_3 = model.get_rsvp_lsp("A", "E", "lsp_a_e_3") 103 | lsp_a_e_4 = model.get_rsvp_lsp("A", "E", "lsp_a_e_4") 104 | 105 | # Each LSP will try to signal for 330/4 = 82.5 units of 106 | # setup bandwidth; there will only be 2 paths that can 107 | # accommodate 82.5 units of reserved_bandwidth. 108 | 109 | # All LSPs will attempt to setup at 82.5 units of traffic 110 | self.assertEqual( 111 | [ 112 | lsp_a_e_1.setup_bandwidth, 113 | lsp_a_e_2.setup_bandwidth, 114 | lsp_a_e_3.setup_bandwidth, 115 | lsp_a_e_4.setup_bandwidth, 116 | ].count(82.5), 117 | 4, 118 | ) 119 | 120 | # 2 of the 4 LSPs will successfully reserve 82.5 units of traffic 121 | reserved_bandwidth_list = [ 122 | lsp_a_e_1.reserved_bandwidth, 123 | lsp_a_e_2.reserved_bandwidth, 124 | lsp_a_e_3.reserved_bandwidth, 125 | lsp_a_e_4.reserved_bandwidth, 126 | ] 127 | 128 | self.assertEqual(reserved_bandwidth_list.count(82.5), 2) 129 | 130 | # 2 of the 4 LSPs will not route 131 | self.assertEqual(reserved_bandwidth_list.count("Unrouted"), 2) 132 | 133 | # The 330 units of traffic from A to E will load balance 134 | # over those 2 LSPs, so each LSP will carry 165 units of 135 | # traffic 136 | self.assertEqual( 137 | [ 138 | lsp_a_e_1.traffic_on_lsp(model), 139 | lsp_a_e_2.traffic_on_lsp(model), 140 | lsp_a_e_3.traffic_on_lsp(model), 141 | lsp_a_e_4.traffic_on_lsp(model), 142 | ].count(165.0), 143 | 2, 144 | ) 145 | -------------------------------------------------------------------------------- /test/test_rsvp_4th_lsp_two_paths.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import PerformanceModel 4 | 5 | 6 | class TestRSVPLSPAddLSP4LSPs(unittest.TestCase): 7 | def test_validate_lsp_routing(self): 8 | """ 9 | Test that all 4 LSPs between A and D route 10 | """ 11 | 12 | model1 = PerformanceModel() 13 | model1.rsvp_lsp_objects = set([]) 14 | model1.demand_objects = set([]) 15 | 16 | int_list = [ 17 | { 18 | "name": "E-to-A", 19 | "cost": 10, 20 | "capacity": 300, 21 | "node": "E", 22 | "remote_node": "A", 23 | "circuit_id": 1, 24 | "failed": False, 25 | }, 26 | { 27 | "name": "C-to-D", 28 | "cost": 30, 29 | "capacity": 150, 30 | "node": "C", 31 | "remote_node": "D", 32 | "circuit_id": 5, 33 | "failed": False, 34 | }, 35 | { 36 | "name": "D-to-C", 37 | "cost": 30, 38 | "capacity": 150, 39 | "node": "D", 40 | "remote_node": "C", 41 | "circuit_id": 5, 42 | "failed": False, 43 | }, 44 | { 45 | "name": "A-to-E", 46 | "cost": 10, 47 | "capacity": 300, 48 | "node": "A", 49 | "remote_node": "E", 50 | "circuit_id": 1, 51 | "failed": False, 52 | }, 53 | { 54 | "name": "A-to-D", 55 | "cost": 40, 56 | "capacity": 20, 57 | "node": "A", 58 | "remote_node": "D", 59 | "circuit_id": 2, 60 | "failed": False, 61 | }, 62 | { 63 | "name": "D-to-A", 64 | "cost": 40, 65 | "capacity": 20, 66 | "node": "D", 67 | "remote_node": "A", 68 | "circuit_id": 2, 69 | "failed": False, 70 | }, 71 | { 72 | "name": "G-to-D", 73 | "cost": 10, 74 | "capacity": 100, 75 | "node": "G", 76 | "remote_node": "D", 77 | "circuit_id": 7, 78 | "failed": False, 79 | }, 80 | { 81 | "name": "C-to-A", 82 | "cost": 30, 83 | "capacity": 150, 84 | "node": "C", 85 | "remote_node": "A", 86 | "circuit_id": 3, 87 | "failed": False, 88 | }, 89 | { 90 | "name": "D-to-F", 91 | "cost": 10, 92 | "capacity": 300, 93 | "node": "D", 94 | "remote_node": "F", 95 | "circuit_id": 6, 96 | "failed": False, 97 | }, 98 | { 99 | "name": "F-to-D", 100 | "cost": 10, 101 | "capacity": 300, 102 | "node": "F", 103 | "remote_node": "D", 104 | "circuit_id": 6, 105 | "failed": False, 106 | }, 107 | { 108 | "name": "D-to-G", 109 | "cost": 10, 110 | "capacity": 100, 111 | "node": "D", 112 | "remote_node": "G", 113 | "circuit_id": 7, 114 | "failed": False, 115 | }, 116 | { 117 | "name": "B-to-A", 118 | "cost": 20, 119 | "capacity": 125, 120 | "node": "B", 121 | "remote_node": "A", 122 | "circuit_id": 4, 123 | "failed": False, 124 | }, 125 | { 126 | "name": "D-to-B", 127 | "cost": 20, 128 | "capacity": 125, 129 | "node": "D", 130 | "remote_node": "B", 131 | "circuit_id": 8, 132 | "failed": False, 133 | }, 134 | { 135 | "name": "B-to-G", 136 | "cost": 10, 137 | "capacity": 100, 138 | "node": "B", 139 | "remote_node": "G", 140 | "circuit_id": 9, 141 | "failed": False, 142 | }, 143 | { 144 | "name": "A-to-C", 145 | "cost": 30, 146 | "capacity": 150, 147 | "node": "A", 148 | "remote_node": "C", 149 | "circuit_id": 3, 150 | "failed": False, 151 | }, 152 | { 153 | "name": "B-to-D", 154 | "cost": 20, 155 | "capacity": 125, 156 | "node": "B", 157 | "remote_node": "D", 158 | "circuit_id": 8, 159 | "failed": False, 160 | }, 161 | { 162 | "name": "G-to-B", 163 | "cost": 10, 164 | "capacity": 100, 165 | "node": "G", 166 | "remote_node": "B", 167 | "circuit_id": 9, 168 | "failed": False, 169 | }, 170 | { 171 | "name": "A-to-B", 172 | "cost": 20, 173 | "capacity": 125, 174 | "node": "A", 175 | "remote_node": "B", 176 | "circuit_id": 4, 177 | "failed": False, 178 | }, 179 | ] 180 | 181 | model1.add_network_interfaces_from_list(int_list) 182 | model1.add_rsvp_lsp("A", "D", "lsp_a_d_1") 183 | model1.add_rsvp_lsp("A", "D", "lsp_a_d_2") 184 | model1.add_rsvp_lsp("A", "D", "lsp_a_d_3") 185 | model1.add_rsvp_lsp("A", "D", "lsp_a_d_4") 186 | 187 | demands = [ 188 | {"source": "A", "dest": "D", "traffic": 100, "name": "dmd_a_d_3"}, 189 | {"source": "A", "dest": "D", "traffic": 70, "name": "dmd_a_d_2"}, 190 | {"source": "A", "dest": "D", "traffic": 80, "name": "dmd_a_d_1"}, 191 | {"source": "F", "dest": "E", "traffic": 400, "name": "dmd_f_e_1"}, 192 | {"source": "A", "dest": "F", "traffic": 40, "name": "dmd_a_f_1"}, 193 | ] 194 | 195 | for demand in demands: 196 | model1.add_demand( 197 | demand["source"], demand["dest"], demand["traffic"], demand["name"] 198 | ) 199 | 200 | model1.update_simulation() 201 | 202 | # All the paths for each LSP from Node('A') to Node('D') 203 | paths = [ 204 | lsp.path 205 | for lsp in model1.rsvp_lsp_objects 206 | if lsp.source_node_object.name == "A" and lsp.dest_node_object.name == "D" 207 | ] 208 | 209 | # Ensure all LSPs route 210 | self.assertNotIn("Unrouted", paths) 211 | -------------------------------------------------------------------------------- /test/test_rsvp_configured_setup_bw.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import PerformanceModel 4 | 5 | 6 | class TestRSVPLSPConfigSetupBW(unittest.TestCase): 7 | def test_config_setup_bw_model_load(self): 8 | model = PerformanceModel.load_model_file( 9 | "test/lsp_configured_setup_bw_model.csv" 10 | ) 11 | model.update_simulation() 12 | self.assertEqual( 13 | model.__repr__(), 14 | "PerformanceModel(Interfaces: 18, Nodes: 7, Demands: 4, RSVP_LSPs: 4)", 15 | ) 16 | 17 | def test_lsp_setup_bw(self): 18 | model = PerformanceModel.load_model_file( 19 | "test/lsp_configured_setup_bw_model.csv" 20 | ) 21 | model.update_simulation() 22 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 23 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 24 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 25 | 26 | self.assertEqual(lsp_a_d_1.setup_bandwidth, 50) 27 | self.assertEqual(lsp_a_d_1.setup_bandwidth, lsp_a_d_2.setup_bandwidth) 28 | self.assertEqual(lsp_a_d_3.setup_bandwidth, 1) 29 | 30 | def test_lsp_traffic(self): 31 | model = PerformanceModel.load_model_file( 32 | "test/lsp_configured_setup_bw_model.csv" 33 | ) 34 | model.update_simulation() 35 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 36 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 37 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 38 | 39 | self.assertEqual(lsp_a_d_1.traffic_on_lsp(model), 50) 40 | self.assertEqual(lsp_a_d_2.traffic_on_lsp(model), 50) 41 | self.assertEqual(lsp_a_d_3.traffic_on_lsp(model), 50) 42 | 43 | def test_lsp_res_bw(self): 44 | model = PerformanceModel.load_model_file( 45 | "test/lsp_configured_setup_bw_model.csv" 46 | ) 47 | model.update_simulation() 48 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 49 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 50 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 51 | 52 | self.assertEqual(lsp_a_d_1.reserved_bandwidth, 50) 53 | self.assertEqual(lsp_a_d_2.reserved_bandwidth, 50) 54 | self.assertEqual(lsp_a_d_3.reserved_bandwidth, 1) 55 | 56 | def test_lsp_dmds(self): 57 | model = PerformanceModel.load_model_file( 58 | "test/lsp_configured_setup_bw_model.csv" 59 | ) 60 | model.update_simulation() 61 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 62 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 63 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 64 | 65 | dmd_a_d_1 = model.get_demand_object("A", "D", "dmd_a_d_1") 66 | dmd_a_d_2 = model.get_demand_object("A", "D", "dmd_a_d_2") 67 | 68 | self.assertEqual( 69 | set(lsp_a_d_1.demands_on_lsp(model)), set([dmd_a_d_1, dmd_a_d_2]) 70 | ) 71 | self.assertEqual( 72 | set(lsp_a_d_2.demands_on_lsp(model)), set([dmd_a_d_1, dmd_a_d_2]) 73 | ) 74 | self.assertEqual( 75 | set(lsp_a_d_3.demands_on_lsp(model)), set([dmd_a_d_1, dmd_a_d_2]) 76 | ) 77 | 78 | # lsp_a_d_3 should always take the single hop path of Interface('A-to-D', 'A') since 79 | # it only has a setup_bw of 1. That interface has a capacity of 40 so lsp_a_d_1/2 80 | # can't take it since they have a setup_bw of 50 81 | def test_shortest_path(self): 82 | model = PerformanceModel.load_model_file( 83 | "test/lsp_configured_setup_bw_model.csv" 84 | ) 85 | model.update_simulation() 86 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 87 | 88 | self.assertEqual(len(lsp_a_d_3.path["interfaces"]), 1) 89 | self.assertEqual(lsp_a_d_3.path["interfaces"][0]._key, ("A-to-D", "A")) 90 | -------------------------------------------------------------------------------- /test/test_rsvp_manual_metric.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyNTM import FlexModel 3 | from pyNTM import ModelException 4 | from pyNTM import PerformanceModel 5 | 6 | 7 | class TestIGPShortcutsFlexModel(unittest.TestCase): 8 | # Load FlexModel, verify LSP metrics 9 | def test_model_load_flex_model(self): 10 | model = FlexModel.load_model_file("test/manual_lsp_metric_test_flex_model.csv") 11 | model.update_simulation() 12 | lsp_b_d_1 = model.get_rsvp_lsp("B", "D", "lsp_b_d_1") 13 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") 14 | lsp_b_d_3 = model.get_rsvp_lsp("B", "D", "lsp_b_d_3") 15 | lsp_b_d_4 = model.get_rsvp_lsp("B", "D", "lsp_b_d_4") 16 | 17 | self.assertEqual(lsp_b_d_1.manual_metric, 10) 18 | self.assertEqual(lsp_b_d_1.effective_metric(model), 10) 19 | self.assertEqual(lsp_b_d_1.topology_metric(model), 20) 20 | self.assertNotEqual(lsp_b_d_1.path, "Unrouted") 21 | self.assertEqual(lsp_b_d_1.configured_setup_bandwidth, 2) 22 | self.assertEqual(lsp_b_d_1.setup_bandwidth, 2) 23 | 24 | self.assertEqual(lsp_b_d_2.manual_metric, 9) 25 | self.assertEqual(lsp_b_d_2.effective_metric(model), 9) 26 | self.assertEqual(lsp_b_d_2.topology_metric(model), 20) 27 | self.assertNotEqual(lsp_b_d_2.path, "Unrouted") 28 | self.assertIsNone(lsp_b_d_2.configured_setup_bandwidth) 29 | 30 | self.assertEqual(lsp_b_d_3.manual_metric, "not set") 31 | self.assertEqual(lsp_b_d_3.topology_metric(model), 20) 32 | self.assertEqual(lsp_b_d_3.effective_metric(model), 20) 33 | self.assertEqual(lsp_b_d_3.configured_setup_bandwidth, 3) 34 | self.assertEqual(lsp_b_d_3.setup_bandwidth, 3) 35 | self.assertNotEqual(lsp_b_d_3.path, "Unrouted") 36 | 37 | self.assertEqual(lsp_b_d_4.manual_metric, "not set") 38 | self.assertEqual(lsp_b_d_4.topology_metric(model), 20) 39 | self.assertEqual(lsp_b_d_4.effective_metric(model), 20) 40 | self.assertNotEqual(lsp_b_d_4.path, "Unrouted") 41 | 42 | # Parallel LSPs in IGP shortcuts, but one with a lower metric; 43 | # traffic should only take lower metric LSP 44 | def test_manual_lsp_metric_efficacy(self): 45 | model = FlexModel.load_model_file("test/manual_lsp_metric_test_flex_model.csv") 46 | model.update_simulation() 47 | lsp_b_d_1 = model.get_rsvp_lsp("B", "D", "lsp_b_d_1") 48 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") 49 | lsp_b_d_3 = model.get_rsvp_lsp("B", "D", "lsp_b_d_3") 50 | lsp_b_d_4 = model.get_rsvp_lsp("B", "D", "lsp_b_d_4") 51 | 52 | self.assertEqual(lsp_b_d_1.traffic_on_lsp(model), 0) 53 | self.assertEqual(lsp_b_d_2.traffic_on_lsp(model), 10) # lowest metric LSP 54 | self.assertEqual(lsp_b_d_3.traffic_on_lsp(model), 0) 55 | self.assertEqual(lsp_b_d_4.traffic_on_lsp(model), 0) 56 | 57 | # Parallel LSPs source-dest, but one with a lower metric; 58 | # traffic should only take lower metric LSP 59 | def test_manual_lsp_metric_efficacy_2(self): 60 | model = FlexModel.load_model_file( 61 | "test/flex_model_parallel_source_dest_lsps.csv" 62 | ) 63 | model.update_simulation() 64 | 65 | lsp_b_d_1 = model.get_rsvp_lsp("B", "D", "lsp_b_d_1") # lower metric 66 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") # default metric (higher) 67 | 68 | self.assertEqual(lsp_b_d_1.traffic_on_lsp(model), 22) 69 | self.assertEqual(lsp_b_d_2.traffic_on_lsp(model), 0) 70 | 71 | # 2 LSP source-dest, but with higher than default metric; 72 | # traffic should take LSPs due to better protocol preference; 73 | def test_manual_lsp_metric_higher_than_igp_metric(self): 74 | model = FlexModel.load_model_file( 75 | "test/flex_model_parallel_source_dest_lsps.csv" 76 | ) 77 | model.update_simulation() 78 | 79 | lsp_b_d_1 = model.get_rsvp_lsp("B", "D", "lsp_b_d_1") 80 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") 81 | 82 | lsp_b_d_1.manual_metric = 1000 83 | lsp_b_d_2.manual_metric = 1000 84 | 85 | model.update_simulation() 86 | 87 | self.assertEqual(lsp_b_d_1.traffic_on_lsp(model), 11) 88 | self.assertEqual(lsp_b_d_2.traffic_on_lsp(model), 11) 89 | 90 | # Reset a manual metric to default using -1 91 | def test_lsp_reset_manual_lsp_metric(self): 92 | model = FlexModel.load_model_file( 93 | "test/flex_model_parallel_source_dest_lsps.csv" 94 | ) 95 | model.update_simulation() 96 | 97 | lsp_b_d_1 = model.get_rsvp_lsp( 98 | "B", "D", "lsp_b_d_1" 99 | ) # this has manual_metric set to 20 from model file 100 | lsp_b_d_2 = model.get_rsvp_lsp("B", "D", "lsp_b_d_2") 101 | 102 | lsp_b_d_1.manual_metric = -1 103 | 104 | model.update_simulation() 105 | 106 | self.assertEqual(lsp_b_d_1.topology_metric(model), 40) 107 | self.assertEqual(lsp_b_d_1.traffic_on_lsp(model), 11) 108 | self.assertEqual(lsp_b_d_2.traffic_on_lsp(model), 11) 109 | 110 | # Assign a bad LSP metric (float, string); make sure it fails 111 | def test_lsp_bad_manual_lsp_metric(self): 112 | model = FlexModel.load_model_file( 113 | "test/flex_model_parallel_source_dest_lsps.csv" 114 | ) 115 | model.update_simulation() 116 | lsp_b_d_1 = model.get_rsvp_lsp( 117 | "B", "D", "lsp_b_d_1" 118 | ) # this has manual_metric set to 20 from model file 119 | 120 | err_msg = "RSVP LSP metric must be positive integer value. Or, set manual_metric to -1 " 121 | 122 | with self.assertRaises(ModelException) as context: 123 | lsp_b_d_1.manual_metric = 20.1 124 | self.assertTrue(err_msg in context.exception.args[0]) 125 | 126 | 127 | class TestIGPShortcutsPerfModel(unittest.TestCase): 128 | # Load PerformanceModel, verify LSP metrics and LSP routing 129 | def test_model_load_perf_model(self): 130 | model = PerformanceModel.load_model_file( 131 | "test/lsp_manual_metric_test_model.csv" 132 | ) 133 | model.update_simulation() 134 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 135 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 136 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 137 | lsp_a_d_4 = model.get_rsvp_lsp("A", "D", "lsp_a_d_4") 138 | 139 | self.assertEqual(lsp_a_d_1.effective_metric(model), 5) 140 | self.assertEqual(lsp_a_d_1.manual_metric, 5) 141 | self.assertEqual(lsp_a_d_1.topology_metric(model), 40) 142 | self.assertEqual(lsp_a_d_1.configured_setup_bandwidth, 4) 143 | self.assertEqual(lsp_a_d_1.setup_bandwidth, 4) 144 | self.assertNotEqual(lsp_a_d_1.path, "Unrouted") 145 | 146 | self.assertEqual(lsp_a_d_2.effective_metric(model), 10) 147 | self.assertEqual(lsp_a_d_2.manual_metric, 10) 148 | self.assertEqual(lsp_a_d_2.topology_metric(model), 40) 149 | self.assertNotEqual(lsp_a_d_2.path, "Unrouted") 150 | 151 | self.assertEqual(lsp_a_d_3.effective_metric(model), 40) 152 | self.assertEqual(lsp_a_d_3.topology_metric(model), 40) 153 | self.assertEqual(lsp_a_d_3.manual_metric, "not set") 154 | self.assertNotEqual(lsp_a_d_3.path, "Unrouted") 155 | 156 | self.assertEqual(lsp_a_d_4.effective_metric(model), 40) 157 | self.assertEqual(lsp_a_d_4.manual_metric, "not set") 158 | self.assertEqual(lsp_a_d_4.topology_metric(model), 40) 159 | self.assertEqual(lsp_a_d_4.configured_setup_bandwidth, 4) 160 | self.assertEqual(lsp_a_d_4.setup_bandwidth, 4) 161 | self.assertNotEqual(lsp_a_d_4.path, "Unrouted") 162 | 163 | # Parallel LSPs source-dest, but one with a lower metric; 164 | # traffic should only take lower metric LSP 165 | def test_perf_model_metric_efficacy(self): 166 | model = PerformanceModel.load_model_file( 167 | "test/lsp_manual_metric_test_model.csv" 168 | ) 169 | model.update_simulation() 170 | 171 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 172 | lsp_a_d_2 = model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 173 | lsp_a_d_3 = model.get_rsvp_lsp("A", "D", "lsp_a_d_3") 174 | lsp_a_d_4 = model.get_rsvp_lsp("A", "D", "lsp_a_d_4") 175 | 176 | self.assertEqual(lsp_a_d_1.traffic_on_lsp(model), 150) 177 | self.assertEqual(lsp_a_d_2.traffic_on_lsp(model), 0) 178 | self.assertEqual(lsp_a_d_3.traffic_on_lsp(model), 0) 179 | self.assertEqual(lsp_a_d_4.traffic_on_lsp(model), 0) 180 | -------------------------------------------------------------------------------- /test/test_rsvplsp_int_failure.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyNTM import PerformanceModel 3 | 4 | 5 | class TestRSVPLSPIntFailure(unittest.TestCase): 6 | 7 | # This next section will see if the LSPs behave as expected 8 | # to the failed interface int_a_c 9 | # def test_fail_interface(self): 10 | # self.assertTrue(self.int_a_b.failed) 11 | 12 | # Update the simulation and make sure both LSPs are on 13 | # interface int_a_c 14 | def test_lsp_failover(self): 15 | model1 = PerformanceModel() 16 | model1.rsvp_lsp_objects = set() 17 | model1.demand_objects = set() 18 | 19 | int_list = [ 20 | { 21 | "name": "E-to-A", 22 | "cost": 10, 23 | "capacity": 300, 24 | "node": "E", 25 | "remote_node": "A", 26 | "circuit_id": 1, 27 | "failed": False, 28 | }, 29 | { 30 | "name": "C-to-D", 31 | "cost": 30, 32 | "capacity": 150, 33 | "node": "C", 34 | "remote_node": "D", 35 | "circuit_id": 5, 36 | "failed": False, 37 | }, 38 | { 39 | "name": "D-to-C", 40 | "cost": 30, 41 | "capacity": 150, 42 | "node": "D", 43 | "remote_node": "C", 44 | "circuit_id": 5, 45 | "failed": False, 46 | }, 47 | { 48 | "name": "A-to-E", 49 | "cost": 10, 50 | "capacity": 300, 51 | "node": "A", 52 | "remote_node": "E", 53 | "circuit_id": 1, 54 | "failed": False, 55 | }, 56 | { 57 | "name": "A-to-D", 58 | "cost": 40, 59 | "capacity": 20, 60 | "node": "A", 61 | "remote_node": "D", 62 | "circuit_id": 2, 63 | "failed": False, 64 | }, 65 | { 66 | "name": "D-to-A", 67 | "cost": 40, 68 | "capacity": 20, 69 | "node": "D", 70 | "remote_node": "A", 71 | "circuit_id": 2, 72 | "failed": False, 73 | }, 74 | { 75 | "name": "G-to-D", 76 | "cost": 10, 77 | "capacity": 100, 78 | "node": "G", 79 | "remote_node": "D", 80 | "circuit_id": 7, 81 | "failed": False, 82 | }, 83 | { 84 | "name": "C-to-A", 85 | "cost": 30, 86 | "capacity": 150, 87 | "node": "C", 88 | "remote_node": "A", 89 | "circuit_id": 3, 90 | "failed": False, 91 | }, 92 | { 93 | "name": "D-to-F", 94 | "cost": 10, 95 | "capacity": 300, 96 | "node": "D", 97 | "remote_node": "F", 98 | "circuit_id": 6, 99 | "failed": False, 100 | }, 101 | { 102 | "name": "F-to-D", 103 | "cost": 10, 104 | "capacity": 300, 105 | "node": "F", 106 | "remote_node": "D", 107 | "circuit_id": 6, 108 | "failed": False, 109 | }, 110 | { 111 | "name": "D-to-G", 112 | "cost": 10, 113 | "capacity": 100, 114 | "node": "D", 115 | "remote_node": "G", 116 | "circuit_id": 7, 117 | "failed": False, 118 | }, 119 | { 120 | "name": "B-to-A", 121 | "cost": 20, 122 | "capacity": 125, 123 | "node": "B", 124 | "remote_node": "A", 125 | "circuit_id": 4, 126 | "failed": False, 127 | }, 128 | { 129 | "name": "D-to-B", 130 | "cost": 20, 131 | "capacity": 125, 132 | "node": "D", 133 | "remote_node": "B", 134 | "circuit_id": 8, 135 | "failed": False, 136 | }, 137 | { 138 | "name": "B-to-G", 139 | "cost": 10, 140 | "capacity": 100, 141 | "node": "B", 142 | "remote_node": "G", 143 | "circuit_id": 9, 144 | "failed": False, 145 | }, 146 | { 147 | "name": "A-to-C", 148 | "cost": 30, 149 | "capacity": 150, 150 | "node": "A", 151 | "remote_node": "C", 152 | "circuit_id": 3, 153 | "failed": False, 154 | }, 155 | { 156 | "name": "B-to-D", 157 | "cost": 20, 158 | "capacity": 125, 159 | "node": "B", 160 | "remote_node": "D", 161 | "circuit_id": 8, 162 | "failed": False, 163 | }, 164 | { 165 | "name": "G-to-B", 166 | "cost": 10, 167 | "capacity": 100, 168 | "node": "G", 169 | "remote_node": "B", 170 | "circuit_id": 9, 171 | "failed": False, 172 | }, 173 | { 174 | "name": "A-to-B", 175 | "cost": 20, 176 | "capacity": 125, 177 | "node": "A", 178 | "remote_node": "B", 179 | "circuit_id": 4, 180 | "failed": False, 181 | }, 182 | ] 183 | 184 | model1.add_network_interfaces_from_list(int_list) 185 | model1.add_rsvp_lsp("A", "D", "lsp_a_d_1") 186 | model1.add_rsvp_lsp("A", "D", "lsp_a_d_2") 187 | model1.fail_interface("A-to-B", "A") 188 | int_a_b = model1.get_interface_object("A-to-B", "A") 189 | int_a_c = model1.get_interface_object("A-to-C", "A") 190 | 191 | demands = [ 192 | {"source": "A", "dest": "D", "traffic": 70, "name": "dmd_a_d_2"}, 193 | {"source": "A", "dest": "D", "traffic": 80, "name": "dmd_a_d_1"}, 194 | {"source": "F", "dest": "E", "traffic": 400, "name": "dmd_f_e_1"}, 195 | {"source": "A", "dest": "F", "traffic": 40, "name": "dmd_a_f_1"}, 196 | ] 197 | 198 | for demand in demands: 199 | model1.add_demand( 200 | demand["source"], demand["dest"], demand["traffic"], demand["name"] 201 | ) 202 | 203 | model1.update_simulation() 204 | 205 | self.assertTrue(int_a_b.failed) 206 | 207 | # int_a_b should not have any LSPs 208 | lsps_on_int_a_b = [lsp for lsp in int_a_b.lsps(model1)] 209 | self.assertTrue(len(lsps_on_int_a_b) == 0) 210 | 211 | # int_a_c should have lsp_a_d_1 and lsp_a_d_2 212 | lsp_names_on_int_a_c = [lsp.lsp_name for lsp in int_a_c.lsps(model1)] 213 | self.assertIn("lsp_a_d_1", lsp_names_on_int_a_c) 214 | self.assertIn("lsp_a_d_2", lsp_names_on_int_a_c) 215 | 216 | # reservable_bandwidth on int_a_c 217 | self.assertEqual(int_a_c.reserved_bandwidth, 150.0) 218 | self.assertEqual(int_a_c.reservable_bandwidth, 0.0) 219 | 220 | def test_effective_metric_update(self): 221 | model = PerformanceModel.load_model_file( 222 | "test/rsvp_lsp_effective_metric_update.csv" 223 | ) 224 | model.update_simulation() 225 | 226 | lsp_a_b_1 = model.get_rsvp_lsp("A", "B", "lsp_a_b_1") 227 | 228 | # Default effective_metric will be shortest path on topology 229 | self.assertEqual(lsp_a_b_1.effective_metric(model), 20) 230 | 231 | model.fail_interface("A-to-B", "A") 232 | model.update_simulation() 233 | 234 | # Default effective_metric will update to 30, reflecting the metric 235 | # for the shortest possible path on the new topology 236 | self.assertEqual(lsp_a_b_1.effective_metric(model), 30) 237 | -------------------------------------------------------------------------------- /test/test_rsvplsp_path_selection.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import PerformanceModel 4 | 5 | 6 | class TestRSVPLSPPathSelection(unittest.TestCase): 7 | @classmethod 8 | def setUpClass(self): 9 | self.model = PerformanceModel.load_model_file("test/model_test_topology_2.csv") 10 | 11 | self.lsp_a_d_1 = self.model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 12 | self.lsp_e_a_1 = self.model.get_rsvp_lsp("E", "A", "lsp_e_a_1") 13 | self.int_a_d = self.model.get_interface_object("A-to-D", "A") 14 | self.dmd_a_d_1 = self.model.get_demand_object("A", "D", "dmd_a_d_1") 15 | 16 | self.model.update_simulation() 17 | 18 | def test_lsp_no_route(self): 19 | self.assertEqual(self.lsp_e_a_1.path, "Unrouted") 20 | 21 | def test_unrouted_lsp_res_bw(self): 22 | self.assertEqual(self.lsp_e_a_1.reserved_bandwidth, "Unrouted") 23 | 24 | def test_single_demand_on_lsp(self): 25 | self.assertEqual(self.lsp_a_d_1.traffic_on_lsp(self.model), 40) 26 | 27 | def test_random_path_selection(self): 28 | model = PerformanceModel.load_model_file("test/multiple_rsvp_paths.csv") 29 | model.update_simulation() 30 | 31 | lsp_a_d_1_path_info = [ 32 | (interface.name, interface.node_object.name) 33 | for interface in self.lsp_a_d_1.path["interfaces"] 34 | ] 35 | 36 | path_list = [ 37 | [("A-to-B", "A"), ("B-to-D", "B")], 38 | [("A-to-C", "A"), ("C-to-D", "C")], 39 | ] 40 | 41 | self.assertIn(lsp_a_d_1_path_info, path_list) 42 | -------------------------------------------------------------------------------- /test/test_rsvplsp_setup.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import RSVP_LSP 4 | from pyNTM import PerformanceModel 5 | from pyNTM import ModelException 6 | 7 | 8 | class TestRSVPLSPInitial(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(self): 11 | self.model = PerformanceModel.load_model_file("test/model_test_topology.csv") 12 | 13 | self.lsp_a_d_1 = self.model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 14 | self.lsp_a_d_2 = self.model.get_rsvp_lsp("A", "D", "lsp_a_d_2") 15 | self.lsp_f_e_1 = self.model.get_rsvp_lsp("F", "E", "lsp_f_e_1") 16 | self.int_a_b = self.model.get_interface_object("A-to-B", "A") 17 | self.int_a_c = self.model.get_interface_object("A-to-C", "A") 18 | self.dmd_a_d_1 = self.model.get_demand_object("A", "D", "dmd_a_d_1") 19 | self.dmd_a_d_2 = self.model.get_demand_object("A", "D", "dmd_a_d_2") 20 | self.model.update_simulation() 21 | 22 | def test_lsp_instance(self): 23 | self.assertIsInstance(self.lsp_a_d_1, RSVP_LSP) 24 | 25 | def test_lsp_path_instance(self): 26 | self.assertIsInstance(self.lsp_a_d_1.path, dict) 27 | 28 | def test_lsp_effective_metric_value(self): 29 | self.assertEqual(40.0, self.lsp_a_d_1.effective_metric(self.model)) 30 | 31 | def test_lsp_repr(self): 32 | self.assertEqual( 33 | repr(self.lsp_a_d_1), 34 | "RSVP_LSP(source = A, dest = D, lsp_name = 'lsp_a_d_1')", 35 | ) 36 | 37 | def test_lsp_reserved_bw(self): 38 | self.assertEqual(self.lsp_a_d_1.reserved_bandwidth, 75.0) 39 | 40 | # lsp_a_d_1 and lsp_a_d_2 each carry part of dmd_a_d_1 and dmd_a_d_2 but 41 | # do not carry any other demands 42 | def test_demands_on_lsp(self): 43 | self.assertIn(self.dmd_a_d_1, self.lsp_a_d_1.demands_on_lsp(self.model)) 44 | self.assertIn(self.dmd_a_d_2, self.lsp_a_d_2.demands_on_lsp(self.model)) 45 | self.assertEqual(len(self.lsp_a_d_1.demands_on_lsp(self.model)), 2) 46 | self.assertEqual(len(self.lsp_a_d_2.demands_on_lsp(self.model)), 2) 47 | 48 | def test_traffic_on_lsp(self): 49 | self.assertEqual(self.lsp_a_d_1.traffic_on_lsp(self.model), 75) 50 | 51 | # lsp_a_d_1 and lsp_a_d_2 take different paths, so topology_metric values 52 | # should not be equal; one path topology_metric is 40, the other path's 53 | # actual metric is 60 54 | def test_lsp_actual_metrics(self): 55 | self.assertNotEqual( 56 | self.lsp_a_d_1.topology_metric(self.model), 57 | self.lsp_a_d_2.topology_metric(self.model), 58 | ) 59 | self.assertIn(self.lsp_a_d_1.topology_metric(self.model), [40, 60]) 60 | self.assertIn(self.lsp_a_d_2.topology_metric(self.model), [40, 60]) 61 | 62 | # lsp_f_e_1 should not be routed because 63 | # 1. It is trying to initially signal 64 | # 2. It has a setup_bandwidth of 400 65 | # 3. There are no available paths to support that setup_bandwidth 66 | def test_lsp_setup_bandwidth_failure(self): 67 | self.assertEqual(self.lsp_f_e_1.path, "Unrouted") 68 | self.assertEqual(self.lsp_f_e_1.setup_bandwidth, 400.0) 69 | 70 | def test_unrouted_lsp_actual_metric(self): 71 | self.assertEqual(self.lsp_f_e_1.topology_metric(self.model), "Unrouted") 72 | 73 | # Validate reserved and reservable bandwidth on int_a_b, int_a_c 74 | def test_reserved_bandwidth(self): 75 | self.assertEqual(self.int_a_b.reserved_bandwidth, 75.0) 76 | self.assertEqual(self.int_a_b.reservable_bandwidth, 50.0) 77 | 78 | self.assertEqual(self.int_a_c.reserved_bandwidth, 75.0) 79 | self.assertEqual(self.int_a_c.reservable_bandwidth, 75.0) 80 | 81 | # Test for setup bandwidth must be >= 0 82 | def test_bad_setup_bw(self): 83 | model = PerformanceModel.load_model_file("test/model_test_topology.csv") 84 | model.update_simulation() 85 | 86 | lsp_a_d_1 = model.get_rsvp_lsp("A", "D", "lsp_a_d_1") 87 | 88 | msg = "setup_bandwidth must be 0 or greater" 89 | 90 | with self.assertRaises(ModelException) as context: 91 | lsp_a_d_1.setup_bandwidth = -1 92 | 93 | self.assertTrue(msg in context.exception.args[0]) 94 | -------------------------------------------------------------------------------- /test/test_traffic_eng_features.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test suite for traffic engineering features: rsvp_enabled and 3 | percent_reservable_bandwidth Interface attributes 4 | """ 5 | 6 | import unittest 7 | 8 | from pyNTM import PerformanceModel 9 | from pyNTM import FlexModel 10 | 11 | 12 | class TestModel(unittest.TestCase): 13 | @classmethod 14 | def setUpClass(self): 15 | self.model = PerformanceModel.load_model_file("test/traffic_eng_test_model.csv") 16 | self.model.update_simulation() 17 | 18 | self.int_a_b = self.model.get_interface_object("A-to-B", "A") 19 | self.int_a_c = self.model.get_interface_object("A-to-C", "A") 20 | self.int_a_d = self.model.get_interface_object("A-to-D", "A") 21 | 22 | self.lsp_a_e_1 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_1") 23 | self.lsp_a_e_2 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_2") 24 | self.lsp_a_e_3 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_3") 25 | 26 | self.dmd_a_e_1 = self.model.get_demand_object("A", "E", "dmd_a_e_1") 27 | self.dmd_a_e_2 = self.model.get_demand_object("A", "E", "dmd_a_e_2") 28 | 29 | def test_rsvp_enabled(self): 30 | """ 31 | Interface that is not rsvp_enabled will have no LSPs 32 | """ 33 | self.assertFalse(self.int_a_d.rsvp_enabled) 34 | self.assertEqual(len(self.int_a_d.lsps(self.model)), 0) 35 | 36 | def test_percent_reservable_bandwidth(self): 37 | """ 38 | Interface with percent_reservable_bandwidth only allows 39 | for 50% of capacity to be used by LSPs 40 | """ 41 | 42 | self.assertEqual(self.int_a_b.percent_reservable_bandwidth, 50) 43 | 44 | reserved_bw = self.int_a_b.reserved_bandwidth 45 | reservable_bw = self.int_a_b.reservable_bandwidth 46 | capacity = self.int_a_b.capacity 47 | 48 | self.assertEqual((reservable_bw + reserved_bw), capacity / 2) 49 | 50 | 51 | class TestParallelLinkModel(unittest.TestCase): 52 | @classmethod 53 | def setUpClass(self): 54 | self.model = FlexModel.load_model_file( 55 | "test/traffic_eng_test_parallel_link_model.csv" 56 | ) 57 | self.model.update_simulation() 58 | 59 | self.int_a_b_1 = self.model.get_interface_object("A-to-B_1", "A") 60 | self.int_a_b_2 = self.model.get_interface_object("A-to-B_2", "A") 61 | self.int_a_b_3 = self.model.get_interface_object("A-to-B_3", "A") 62 | 63 | self.lsp_a_e_1 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_1") 64 | self.lsp_a_e_2 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_2") 65 | self.lsp_a_e_3 = self.model.get_rsvp_lsp("A", "E", "lsp_a_e_3") 66 | 67 | self.dmd_a_e_1 = self.model.get_demand_object("A", "E", "dmd_a_e_1") 68 | self.dmd_a_e_2 = self.model.get_demand_object("A", "E", "dmd_a_e_2") 69 | 70 | def test_rsvp_enabled(self): 71 | """ 72 | Interface that is not rsvp_enabled will have no LSPs 73 | """ 74 | self.assertFalse(self.int_a_b_3.rsvp_enabled) 75 | self.assertEqual(len(self.int_a_b_3.lsps(self.model)), 0) 76 | 77 | def test_percent_reservable_bandwidth(self): 78 | """ 79 | Interface with percent_reservable_bandwidth only allows 80 | for 50% of capacity to be used by LSPs 81 | """ 82 | 83 | self.assertEqual(self.int_a_b_1.percent_reservable_bandwidth, 50) 84 | 85 | reserved_bw = self.int_a_b_1.reserved_bandwidth 86 | reservable_bw = self.int_a_b_1.reservable_bandwidth 87 | capacity = self.int_a_b_1.capacity 88 | 89 | self.assertEqual((reservable_bw + reserved_bw), capacity / 2) 90 | -------------------------------------------------------------------------------- /test/test_utilities.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyNTM import find_end_index 4 | 5 | 6 | class TestUtilities(unittest.TestCase): 7 | @classmethod 8 | def setUpClass(self): 9 | self.lines = ["INTERFACES_TABLE", "", "foobar"] 10 | 11 | def test_find_end_index(self): 12 | end_index = find_end_index(0, self.lines) 13 | self.assertEqual(1, end_index) 14 | -------------------------------------------------------------------------------- /test/traffic_eng_test_model.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity rsvp_enabled percent_reservable_bandwidth 3 | A B A-to-B 20 120 True 50 4 | B A B-to-A 20 120 True 50 5 | A C A-to-C 20 150 6 | C A C-to-A 20 150 7 | A D A-to-D 10 200 False 8 | D A D-to-A 10 200 False 9 | B E B-to-E 20 125 10 | E B E-to-B 20 125 11 | D E D-to-E 20 225 12 | E D E-to-D 20 225 13 | C E C-to-E 20 100 14 | E C E-to-C 20 100 15 | 16 | NODES_TABLE 17 | name lon lat 18 | A 50 0 19 | B 0 50 20 | C 0 50 21 | D 50 0 22 | E 75 0 23 | 24 | DEMANDS_TABLE 25 | source dest traffic name 26 | A E 80 dmd_a_e_1 27 | A E 70 dmd_a_e_2 28 | 29 | RSVP_LSP_TABLE 30 | source dest name 31 | A E lsp_a_e_1 32 | A E lsp_a_e_2 33 | A E lsp_a_e_3 -------------------------------------------------------------------------------- /test/traffic_eng_test_parallel_link_model.csv: -------------------------------------------------------------------------------- 1 | INTERFACES_TABLE 2 | node_object_name remote_node_object_name name cost capacity circuit_id rsvp_enabled percent_reservable_bandwidth 3 | A B A-to-B_1 20 120 1 True 50 4 | B A B-to-A_1 20 120 1 True 50 5 | A B A-to-B_2 20 150 2 6 | B A B-to-A_2 20 150 2 7 | A B A-to-B_3 10 200 3 False 8 | B A B-to-A_3 10 200 3 False 9 | B E B-to-E 20 500 4 10 | E B E-to-B 20 500 4 11 | 12 | NODES_TABLE 13 | name lon lat 14 | A 50 0 15 | B 0 50 16 | C 0 50 17 | D 50 0 18 | E 75 0 19 | 20 | DEMANDS_TABLE 21 | source dest traffic name 22 | A E 80 dmd_a_e_1 23 | A E 70 dmd_a_e_2 24 | 25 | RSVP_LSP_TABLE 26 | source dest name 27 | A E lsp_a_e_1 28 | A E lsp_a_e_2 29 | A E lsp_a_e_3 --------------------------------------------------------------------------------