├── .coveragerc
├── .flake8
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── github-actions.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── docs
└── sample_responses.txt
├── examples
└── plugins
│ ├── README.md
│ ├── csv_inv.py
│ ├── labs_tsv.py
│ └── retitle_cmd.py
├── pyproject.toml
├── requirements.txt
├── requirements_dev.txt
├── setup.cfg
├── setup.py
├── test-requirements.txt
├── tests
├── __init__.py
└── v2
│ ├── __init__.py
│ ├── bad_plugins.py
│ ├── cluster.py
│ ├── console.py
│ ├── definitions.py
│ ├── down.py
│ ├── extract.py
│ ├── generate_ansible.py
│ ├── generate_nso.py
│ ├── generate_pyats.py
│ ├── good_plugins.py
│ ├── groups.py
│ ├── id.py
│ ├── ls.py
│ ├── mocks
│ ├── __init__.py
│ ├── api.py
│ ├── github.py
│ └── nso.py
│ ├── nodes.py
│ ├── plugins_bad_cmd
│ └── cmd_plug_bad_run.py
│ ├── plugins_bad_gen
│ └── generator_plugin_bad.py
│ ├── plugins_bad_viewer
│ └── viewer_plugin_bad.py
│ ├── plugins_good
│ ├── cmd_plug.py
│ ├── generator_plugin.py
│ └── viewer_plugin.py
│ ├── pull.py
│ ├── rm.py
│ ├── save.py
│ ├── search.py
│ ├── ssh.py
│ ├── start.py
│ ├── static
│ ├── fake_image_definitions.yaml
│ ├── fake_repo_bad_topology.yaml
│ ├── fake_repo_topology.yaml
│ ├── node_defs_list_output.txt
│ ├── node_defs_list_output_cml22.txt
│ ├── response_get_node_defs.json
│ └── response_get_node_defs_cml22.json
│ ├── stop.py
│ ├── telnet.py
│ ├── test_cli.py
│ ├── tmux.py
│ ├── up.py
│ ├── use.py
│ ├── users.py
│ └── wipe.py
└── virl
├── __init__.py
├── about.py
├── api
├── __init__.py
├── api.py
├── cml.py
├── credentials.py
├── github.py
├── nso.py
└── plugin.py
├── cli
├── __init__.py
├── clear
│ ├── __init__.py
│ └── commands.py
├── cluster
│ ├── __init__.py
│ └── info
│ │ ├── __init__.py
│ │ └── commands.py
├── cockpit
│ ├── __init__.py
│ └── commands.py
├── command
│ ├── __init__.py
│ └── commands.py
├── console
│ ├── __init__.py
│ └── commands.py
├── definitions
│ ├── __init__.py
│ ├── images
│ │ ├── __init__.py
│ │ ├── export
│ │ │ ├── __init__.py
│ │ │ └── commands.py
│ │ ├── iimport
│ │ │ ├── __init__.py
│ │ │ ├── definition
│ │ │ │ ├── __init__.py
│ │ │ │ └── commands.py
│ │ │ └── image_file
│ │ │ │ ├── __init__.py
│ │ │ │ └── commands.py
│ │ └── ls
│ │ │ ├── __init__.py
│ │ │ └── commands.py
│ └── nodes
│ │ ├── __init__.py
│ │ ├── export
│ │ ├── __init__.py
│ │ └── commands.py
│ │ ├── ls
│ │ ├── __init__.py
│ │ └── commands.py
│ │ └── nimport
│ │ ├── __init__.py
│ │ └── commands.py
├── down
│ ├── __init__.py
│ └── commands.py
├── extract
│ ├── __init__.py
│ └── commands.py
├── generate
│ ├── __init__.py
│ ├── ansible
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── nso
│ │ ├── __init__.py
│ │ └── commands.py
│ └── pyats
│ │ ├── __init__.py
│ │ └── commands.py
├── groups
│ ├── __init__.py
│ ├── create
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── delete
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── ls
│ │ ├── __init__.py
│ │ └── commands.py
│ └── update
│ │ ├── __init__.py
│ │ └── commands.py
├── id
│ ├── __init__.py
│ └── commands.py
├── license
│ ├── __init__.py
│ ├── deregister
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── features
│ │ ├── __init__.py
│ │ ├── show
│ │ │ ├── __init__.py
│ │ │ └── commands.py
│ │ └── update
│ │ │ ├── __init__.py
│ │ │ └── commands.py
│ ├── register
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── renew
│ │ ├── __init__.py
│ │ ├── authorization
│ │ │ ├── __init__.py
│ │ │ └── commands.py
│ │ └── registration
│ │ │ ├── __init__.py
│ │ │ └── commands.py
│ └── show
│ │ ├── __init__.py
│ │ └── commands.py
├── ls
│ ├── __init__.py
│ └── commands.py
├── main.py
├── nodes
│ ├── __init__.py
│ └── commands.py
├── pull
│ ├── __init__.py
│ └── commands.py
├── rm
│ ├── __init__.py
│ └── commands.py
├── save
│ ├── __init__.py
│ └── commands.py
├── search
│ ├── __init__.py
│ └── commands.py
├── ssh
│ ├── __init__.py
│ └── commands.py
├── start
│ ├── __init__.py
│ └── commands.py
├── stop
│ ├── __init__.py
│ └── commands.py
├── telnet
│ ├── __init__.py
│ └── commands.py
├── tmux
│ ├── __init__.py
│ └── commands.py
├── ui
│ ├── __init__.py
│ └── commands.py
├── up
│ ├── __init__.py
│ └── commands.py
├── use
│ ├── __init__.py
│ └── commands.py
├── users
│ ├── __init__.py
│ ├── create
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── delete
│ │ ├── __init__.py
│ │ └── commands.py
│ ├── ls
│ │ ├── __init__.py
│ │ └── commands.py
│ └── update
│ │ ├── __init__.py
│ │ └── commands.py
├── version
│ ├── __init__.py
│ └── commands.py
├── views
│ ├── __init__.py
│ ├── cluster
│ │ ├── __init__.py
│ │ └── cluster_views.py
│ ├── console
│ │ ├── __init__.py
│ │ └── console_views.py
│ ├── generate
│ │ ├── __init__.py
│ │ └── nso
│ │ │ ├── __init__.py
│ │ │ └── sync_result.py
│ ├── groups
│ │ ├── __init__.py
│ │ └── group_views.py
│ ├── images
│ │ ├── __init__.py
│ │ └── image_views.py
│ ├── labs
│ │ ├── __init__.py
│ │ └── lab_views.py
│ ├── license
│ │ ├── __init__.py
│ │ └── license_views.py
│ ├── node_defs
│ │ ├── __init__.py
│ │ └── node_def_views.py
│ ├── nodes
│ │ ├── __init__.py
│ │ └── node_views.py
│ ├── search
│ │ ├── __init__.py
│ │ └── views.py
│ └── users
│ │ ├── __init__.py
│ │ └── user_views.py
└── wipe
│ ├── __init__.py
│ ├── lab
│ ├── __init__.py
│ └── commands.py
│ └── node
│ ├── __init__.py
│ └── commands.py
├── generators
├── __init__.py
├── ansible_inventory.py
├── nso_payload.py
└── pyats_testbed.py
├── helpers.py
└── templates
├── ansible
├── inventory_ini_template.j2
└── inventory_template.j2
├── nso
└── xml_payload.j2
└── pyats
└── testbed_yaml.j2
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit =
3 | # omit anything in a .local directory anywhere
4 | */.local/*
5 | */templates/*
6 | */static/*
7 | */examples/*
8 | tests/*
9 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude = cmlutils,venv*,.git,.eggs,__pycache__,docs/source/conf.py,old,build,dist
3 | ignore = E731
4 | max-complexity = 25
5 | max-line-length = 140
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/workflows/github-actions.yml:
--------------------------------------------------------------------------------
1 | name: cmlutils CI
2 | on: [push, pull_request, workflow_dispatch]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | python-version:
10 | - "3.8"
11 | - "3.9"
12 | - "3.10"
13 | - "3.11"
14 | - "3.12"
15 | env:
16 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Setup Python ${{ matrix.python-version }}
20 | uses: actions/setup-python@v2
21 | with:
22 | python-version: ${{ matrix.python-version }}
23 | - name: Upgrade pip
24 | run: |
25 | pip install -U pip
26 | - name: Install dependencies
27 | run: |
28 | pip install -r requirements.txt
29 | pip install -r test-requirements.txt
30 | - name: Execute tests
31 | run: |
32 | make lint
33 | make coverage
34 | pip freeze
35 | - name: Run coveralls
36 | run: |
37 | coveralls --service=github
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dev stuff
2 | *.code-workspace
3 | scratch*
4 | .vscode/
5 | .idea
6 |
7 | # virl stuff
8 | .virl/
9 | .virlrc
10 | topology.yaml
11 | topology.virl
12 |
13 | # Byte-compiled / optimized / DLL files
14 | __pycache__/
15 | *.py[cod]
16 | *$py.class
17 |
18 | # C extensions
19 | *.so
20 |
21 | # Distribution / packaging
22 | .Python
23 | env/
24 | build/
25 | develop-eggs/
26 | dist/
27 | downloads/
28 | eggs/
29 | .eggs/
30 | lib/
31 | lib64/
32 | parts/
33 | sdist/
34 | var/
35 | wheels/
36 | *.egg-info/
37 | .installed.cfg
38 | *.egg
39 |
40 | # PyInstaller
41 | # Usually these files are written by a python script from a template
42 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
43 | *.manifest
44 | *.spec
45 |
46 | # Installer logs
47 | pip-log.txt
48 | pip-delete-this-directory.txt
49 |
50 | # Unit test / coverage reports
51 | htmlcov/
52 | .tox/
53 | .coverage
54 | .coverage.*
55 | .cache
56 | nosetests.xml
57 | coverage.xml
58 | *.cover
59 | .hypothesis/
60 | 5f0d96.yaml
61 | default_inventory.*
62 | default_testbed.yaml
63 |
64 | # Translations
65 | *.mo
66 | *.pot
67 |
68 | # Django stuff:
69 | *.log
70 | local_settings.py
71 |
72 | # Flask stuff:
73 | instance/
74 | .webassets-cache
75 |
76 | # Scrapy stuff:
77 | .scrapy
78 |
79 | # Sphinx documentation
80 | docs/_build/
81 |
82 | # PyBuilder
83 | target/
84 |
85 | # Jupyter Notebook
86 | .ipynb_checkpoints
87 |
88 | # pyenv
89 | .python-version
90 |
91 | # celery beat schedule file
92 | celerybeat-schedule
93 |
94 | # SageMath parsed files
95 | *.sage.py
96 |
97 | # dotenv
98 | .env
99 |
100 | # virtualenv
101 | .venv
102 | venv*/
103 | ENV/
104 | cmlutils/
105 |
106 | # Spyder project settings
107 | .spyderproject
108 | .spyproject
109 |
110 | # Rope project settings
111 | .ropeproject
112 |
113 | # mkdocs documentation
114 | /site
115 |
116 | # mypy
117 | .mypy_cache/
118 |
119 | # macOS
120 | .DS_Store
121 | .AppleDouble
122 | .LSOverride
123 | Icon
124 | ._*
125 | .DocumentRevisions-V100
126 | .fseventsd
127 | .Spotlight-V100
128 | .TemporaryItems
129 | .Trashes
130 | .VolumeIcon.icns
131 | .com.apple.timemachine.donotpresent
132 | .AppleDB
133 | .AppleDesktop
134 | Network Trash Folder
135 | Temporary Items
136 | .apdisk
137 | *.icloud
138 |
139 | # virlutils test
140 | 5f0d96_inventory.ini
141 | 5f0d96_inventory.yaml
142 | 5f0d96_testbed.yaml
143 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at devnet-github-owners@cisco.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Guidance on how to contribute
2 |
3 | Contributions to this code are welcome and appreciated.
4 | Please adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md) at all times.
5 |
6 | > All contributions to this code will be released under the terms of the [LICENSE](./LICENSE) of this code. By submitting a pull request or filing a bug, issue, or feature request, you are agreeing to comply with this waiver of copyright interest. Details can be found in our [LICENSE](./LICENSE).
7 |
8 | There are two primary ways to contribute:
9 |
10 | 1. Using the issue tracker
11 | 2. Changing the codebase
12 |
13 |
14 | ## Using the issue tracker
15 |
16 | Use the issue tracker to suggest feature requests, report bugs, and ask questions. This is also a great way to connect with the developers of the project as well as others who are interested in this solution.
17 |
18 | Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in the issue that you will take on that effort, then follow the _Changing the codebase_ guidance below.
19 |
20 |
21 | ## Changing the codebase
22 |
23 | Generally speaking, you should fork this repository, make changes in your own fork, and then submit a pull request.
24 |
25 | ## Setting up the development environment
26 |
27 | Ensure that you have the minimum required version of Python installed. See the `python_requires` line in the `./setup.py` file. To set up your development environment, create a virtual environment, and then use the `pip` to install the requirements and the development requirements. For example
28 |
29 | $ python3 -mvenv ~/venvs/virlutils-dev/
30 | $ source ~/venvs/virlutils-dev/bin/activate
31 | (virlutils-dev) $ pip install -r requirements.txt
32 | (virlutils-dev) $ pip install -r requirements_dev.txt
33 |
34 | ### Unit tests
35 |
36 | All new code should have associated unit tests (if applicable) that validate implemented features and the presence or lack of defects. Before you start making changes, check that all existing tests pass. We recommend using a test-driven development (TDD) to making changes. Add one or more new unit tests that demonstrate the bug or gap you're trying to fix. Initially, the test should be failing because you haven't fixed the problem, yet. Before you submit a pull request, ensure that your new test passes and that all existing tests still pass.
37 |
38 | You can run the unit tests from the command line by running `python setup.py tests` from the root directory of the project. That's currently equivalent to running `python -m unittest discover -v -s ./tests -p '*.py'` from the root directory of the project.
39 |
40 | You can also generally configure a Python-aware IDE to run the tests. For example, if you are using [VSCode](https://code.visualstudio.com/), click **Run > Command Palette > Python: Discover Tests** and chose *unittest*, *tests* folder, and `*.py` pattern. You can verify the settings by searching your VSCode workspace settings for `Unittest`. Ensure that the checkbox under **Python > Testing: Unittest Enabled** is checked and that the **Python > Testing: Unittest Args** are
41 |
42 | -v
43 | -s
44 | ./tests
45 | -p
46 | *.py
47 |
48 | With those settings, navigate to **View > Testing** in VSCode. The view should list all of the test methods discovered under the `tests` folder. You may need to **Refresh Tests** first. Because the tests use mock responses, you can run them without access to a CML server.
49 |
50 | ### Code Style
51 |
52 | Additionally, the code should follow any stylistic and architectural guidelines prescribed by the project. The project now uses [black](https://black.readthedocs.io/) and [isort](https://pycqa.github.io/isort/) to ensure consistent code formatting. When you installed the requirements_dev.txt in your virtual environment, it installed the `black` and `isort` commands. For each file that you are modifying, run the following before you commit the file or submit a pull request:
53 |
54 | ```sh
55 | black ./virl/path/to/file.py
56 | isort ./virl/path/to/file.py
57 | ```
58 |
59 | ### Linting
60 |
61 | We use flake 8 to lint our code. Please keep the repository clean by running:
62 |
63 | ```sh
64 | flake8
65 | ```
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2020 Cisco Systems, Inc. and its affiliates
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | recursive-include virl/templates *
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Heavily borrowed from:
2 | # https://github.com/audreyr/cookiecutter-pypackage/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/Makefile
3 |
4 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
5 |
6 |
7 | clean-build: ## remove build artifacts
8 | rm -fr build/
9 | rm -fr dist/
10 | rm -fr .eggs/
11 | find . -name '*.egg-info' -and -not -name "venv*" -exec rm -fr {} +
12 | find . -name '*.egg' -and -not -name 'venv*' -exec rm -rf {} +
13 |
14 | clean-pyc: ## remove Python file artifacts
15 | find . -name '*.pyc' -exec rm -f {} +
16 | find . -name '*.pyo' -exec rm -f {} +
17 | find . -name '*~' -exec rm -f {} +
18 | find . -name '__pycache__' -exec rm -fr {} +
19 |
20 | clean-test: ## remove test and coverage artifacts
21 | rm -fr .tox/
22 | rm -f .coverage
23 | rm -fr htmlcov/
24 | rm -f 5f0d96.yaml
25 | rm -f 5f0d96_inventory.ini
26 | rm -f 5f0d96_inventory.yaml
27 | rm -f 5f0d96_testbed.yaml
28 | rm -f default_inventory.ini
29 | rm -f default_inventory.yaml
30 | rm -f default_testbed.yaml
31 | rm -f topology.yaml
32 | rm -f .virl/cached_cml_labs/*
33 | rm -f .virl/current_cml_lab
34 |
35 | lint: ## check style with flake8
36 | flake8
37 |
38 | coverage:
39 | coverage run --source=virl -m pytest tests/v2/*
40 |
41 | report: coverage
42 | coverage html
43 | coverage report
44 | open htmlcov/index.html
45 |
46 | test: ## run tests quickly with the default Python
47 | pytest tests/v2/*
48 |
49 | release: dist ## package and upload a release
50 | @echo "*** Uploading virlutils... ***"
51 | twine upload dist/virl*
52 | @echo "*** Uploading cmlutils... ***"
53 | twine upload dist/cml*
54 |
55 | dist: clean ## builds source and wheel package
56 | # Build virlutils
57 | python setup.py sdist
58 | python setup.py bdist_wheel
59 | # Flip the name
60 | sed -i .orig -e 's|NAME,|CMLNAME,|' setup.py
61 | # Build cmlutils
62 | python setup.py sdist
63 | python setup.py bdist_wheel
64 | cp -f setup.py.orig setup.py
65 | rm -f setup.py.orig
66 | ls -l dist
67 |
68 | install: clean ## install the package to the active Python's site-packages
69 | python setup.py install
70 |
--------------------------------------------------------------------------------
/docs/sample_responses.txt:
--------------------------------------------------------------------------------
1 | from virl.api import VIRLServer
2 |
3 | server = VIRLServer()
4 |
5 | print(server.list_simulations())
6 | # {u'virl_cli_default_1dNVCr': {u'status': u'ACTIVE', u'expires': None, u'launched': u'2017-12-08T23:39:07.721310'}, u'topology-fpyHFs': {u'status': u'ACTIVE', u'expires': None, u'launched': u'2017-12-08T18:48:34.174486'}}
7 |
8 | sample_sim_data = """
9 |
10 |
11 |
12 |
13 | """
14 | print(server.launch_simulation('test', sample_sim_data))
15 | # test
16 |
17 | print(server.get_nodes('test').json())
18 | # u'~mgmt-lxc': {u'vnc-console': False, u'subtype': u'mgmt-lxc', u'state': u'ABSENT', u'reachable': None, u'management-protocol': u'ssh', u'management-proxy': u'self', u'serial-ports': 0}, u'iosv-1': {u'vnc-console': False, u'subtype': u'IOSv', u'state': u'ABSENT', u'reachable': None, u'management-protocol': u'telnet', u'management-proxy': u'lxc', u'serial-ports': 2}}
19 |
20 | print(server.get_node_console('test').json())
21 | # {"iosv-1": "10.94.140.41:17016","~mgmt-lxc": null}
22 |
23 | print(server.stop_simulation('test').text)
24 | # SUCCESS
25 |
--------------------------------------------------------------------------------
/examples/plugins/csv_inv.py:
--------------------------------------------------------------------------------
1 | import csv
2 |
3 | import click
4 |
5 | from virl.api import GeneratorPlugin, VIRLServer
6 | from virl.helpers import (get_cml_client, get_current_lab, get_node_mgmt_ip,
7 | safe_join_existing_lab)
8 |
9 |
10 | class CSVInventory(GeneratorPlugin, generator="csv"):
11 | @staticmethod
12 | def write_inventory(nodes, delimiter, output):
13 | with open(output, "w") as fd:
14 | csvwriter = csv.writer(fd, delimiter=delimiter, quoting=csv.QUOTE_MINIMAL)
15 | csvwriter.writerow(["Name", "Type", "Tags", "IP Address"])
16 | for node in nodes:
17 | mgmt_ip = get_node_mgmt_ip(node)
18 | if not mgmt_ip:
19 | continue
20 |
21 | row = [node.label]
22 | row.append(node.node_definition.lower())
23 | row.append(" ".join(node.tags()))
24 | row.append(mgmt_ip)
25 | csvwriter.writerow(row)
26 |
27 | @staticmethod
28 | @click.command()
29 | @click.option(
30 | "--delimiter", "-d", default=",", show_default=False, required=False, help="Delimiter to use between fields (default: ',')"
31 | )
32 | @click.option(
33 | "--output",
34 | "-o",
35 | default="inventory.csv",
36 | show_default=False,
37 | required=False,
38 | help="File to write inventory (default: inventory.csv)",
39 | )
40 | def generate(delimiter, output):
41 | """
42 | generate generic CSV inventory
43 | """
44 | server = VIRLServer()
45 | client = get_cml_client(server)
46 |
47 | current_lab = get_current_lab()
48 | if not current_lab:
49 | click.secho("Current lab is not set", fg="red")
50 | exit(1)
51 |
52 | lab = safe_join_existing_lab(current_lab, client)
53 | if not lab:
54 | click.secho("Failed to find running lab {}".format(current_lab), fg="red")
55 | exit(1)
56 |
57 | exit(CSVInventory.write_inventory(lab.nodes(), delimiter, output))
58 |
--------------------------------------------------------------------------------
/examples/plugins/labs_tsv.py:
--------------------------------------------------------------------------------
1 | from virl.api import ViewerPlugin
2 |
3 |
4 | class LabsTSVViewer(ViewerPlugin, viewer="lab"):
5 | def visualize(self, **kwargs):
6 | """
7 | Render the labs list as a tab-delimited set of
8 | rows. Replaces the output of `cml ls`.
9 |
10 | The input will be kwargs["labs"], kwargs["cached_labs"] and
11 | kwargs["ownerids_usernames"] (a mapping UUID to username)
12 | """
13 |
14 | labs = kwargs["labs"]
15 | if kwargs["cached_labs"]:
16 | labs += kwargs["cached_labs"]
17 | ownerids_usernames = kwargs["ownerids_usernames"]
18 |
19 | print("ID\tTitle\tDescription\tOwner\tStatus\tNodes\tLinks\tInterfaces")
20 | for lab in labs:
21 | """
22 | Each lab is of type virl2_client.models.lab.Lab whereas each cached lab is of
23 | type virl.api.cml.CachedLab. The two are similar enough for these properties to
24 | be common
25 | """
26 | print(
27 | "{id}\t{title}\t{description}\t{owner}\t{status}\t{nodes}\t{links}\t{interfaces}".format(
28 | id=lab.id,
29 | title=lab.title,
30 | description=lab.description,
31 | owner=ownerids_usernames.get(lab.owner, lab.owner),
32 | status=lab.state(),
33 | nodes=lab.statistics["nodes"],
34 | links=lab.statistics["links"],
35 | interfaces=lab.statistics["interfaces"],
36 | )
37 | )
38 |
--------------------------------------------------------------------------------
/examples/plugins/retitle_cmd.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import CommandPlugin, VIRLServer
4 | from virl.helpers import (get_cml_client, get_current_lab,
5 | safe_join_existing_lab)
6 |
7 |
8 | class RetitleCommand(CommandPlugin, command="retitle"):
9 | @staticmethod
10 | @click.command()
11 | @click.option("--new-title", "-n", required=True, help="New title for the lab")
12 | def run(new_title):
13 | """
14 | re-title the current lab
15 | """
16 |
17 | server = VIRLServer()
18 | client = get_cml_client(server)
19 |
20 | clab = get_current_lab()
21 |
22 | if not clab:
23 | click.secho("Current lab is not set", fg="red")
24 | exit(1)
25 |
26 | lab = safe_join_existing_lab(clab, client)
27 | if lab:
28 | lab.title = new_title
29 | else:
30 | click.secho("Current lab {} is not present on the server".format(clab), fg="red")
31 | exit(1)
32 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.black]
2 | line-length = 140
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | PyYAML
2 | click
3 | jinja2
4 | libtmux>=0.34.0
5 | requests
6 | tabulate
7 | virl2-client>=2.7.0
8 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | Flask
2 | Sphinx
3 | black
4 | bumpversion
5 | coverage
6 | flake8
7 | isort
8 | pip
9 | requests-mock
10 | tox
11 | twine
12 | watchdog
13 | wheel
14 | white
15 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description_file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from setuptools import find_packages, setup # noqa: H301
3 |
4 | from virl import __version__
5 |
6 | NAME = "virlutils"
7 | CMLNAME = "cmlutils"
8 | VERSION = __version__
9 | # To install the library, run the following
10 | #
11 | # python setup.py install
12 | #
13 | # prerequisite: setuptools
14 | # http://pypi.python.org/pypi/setuptools
15 |
16 |
17 | def requirements(f):
18 | with open(f, "r") as fd:
19 | return fd.read()
20 |
21 |
22 | def readme():
23 | with open("README.md", "r") as f:
24 | return f.read()
25 |
26 |
27 | setup(
28 | name=NAME,
29 | version=VERSION,
30 | description="A collection of utilities for interacting with Cisco Modeling Labs",
31 | author="Joe Clarke", # With a big thanks to its original author, Kevin Corbin
32 | author_email="jclarke@cisco.com",
33 | url="https://github.com/CiscoDevNet/virlutils",
34 | entry_points={"console_scripts": ["virl=virl.cli.main:virl", "cml=virl.cli.main:virl"]},
35 | packages=find_packages(),
36 | package_data={"virl": ["templates/**/*.j2", "swagger/templates/*", "swagger/static/*", "examples/plugins/*"]},
37 | include_package_data=True,
38 | install_requires=requirements("requirements.txt"),
39 | long_description_content_type="text/markdown",
40 | long_description=readme(),
41 | test_suite="tests",
42 | tests_require=requirements("test-requirements.txt"),
43 | zip_safe=False,
44 | python_requires=">=3.8",
45 | classifiers=[
46 | "Programming Language :: Python :: 3",
47 | "License :: OSI Approved :: MIT License",
48 | "Operating System :: OS Independent",
49 | ],
50 | )
51 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | coverage
2 | coveralls
3 | flake8
4 | pytest
5 | requests_mock
6 | respx
7 | setuptools
8 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/tests/__init__.py
--------------------------------------------------------------------------------
/tests/v2/bad_plugins.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from virl.api.plugin import _test_enable_plugins
6 |
7 | from . import BaseCMLTest
8 |
9 | try:
10 | from unittest.mock import patch
11 | except ImportError:
12 | from mock import patch # noqa
13 |
14 |
15 | class CMLBadPluginTest(BaseCMLTest):
16 | def localSetUp(self, pdir):
17 | os.environ["CML_PLUGIN_PATH"] = os.path.realpath("./tests/v2/{}".format(pdir))
18 | _test_enable_plugins()
19 |
20 | def tearDown(self):
21 | super().tearDown()
22 | os.environ.pop("CML_PLUGIN_PATH", None)
23 |
24 | @classmethod
25 | def tearDownClass(cls):
26 | super().tearDownClass()
27 | os.environ.pop("CML_PLUGIN_PATH", None)
28 | _test_enable_plugins(enabled=False)
29 |
30 | @patch("virl.cli.main.click.secho", autospec=False)
31 | def test_cmd_plugin_bad(self, secho_mock):
32 | self.localSetUp("plugins_bad_cmd")
33 | virl = self.get_virl()
34 | with self.get_context() as m:
35 | runner = CliRunner()
36 | # Mock the request to return what we expect from the API.
37 | self.setup_mocks(m)
38 | result = runner.invoke(virl, ["--help"])
39 | self.assertEqual(0, result.exit_code)
40 | self.assertNotIn("test-bad-cmd", result.output)
41 | secho_mock.assert_called_once_with(
42 | "ERROR: Malformed plugin for command test-bad-cmd. The `run` method must be static and a click.command", fg="red"
43 | )
44 |
45 | @patch("virl.cli.generate.click.secho", autospec=False)
46 | def test_gen_plugin_bad(self, secho_mock):
47 | self.localSetUp("plugins_bad_gen")
48 | virl = self.get_virl()
49 | with self.get_context() as m:
50 | runner = CliRunner()
51 | # Mock the request to return what we expect from the API.
52 | self.setup_mocks(m)
53 | result = runner.invoke(virl, ["generate", "--help"], catch_exceptions=False)
54 | self.assertEqual(0, result.exit_code)
55 | self.assertNotIn("test-bad-gen", result.output)
56 | secho_mock.assert_called_once_with(
57 | "ERROR: Malformed plugin for generator test-bad-gen. The `generate` method must be static and a click.command", fg="red"
58 | )
59 |
60 | @patch("virl.api.plugin.click.secho", autospec=False)
61 | def test_view_plugin_bad(self, secho_mock):
62 | self.localSetUp("plugins_bad_viewer")
63 | virl = self.get_virl()
64 | with self.get_context() as m:
65 | runner = CliRunner()
66 | # Mock the request to return what we expect from the API.
67 | self.setup_mocks(m)
68 | result = runner.invoke(virl, ["ls"])
69 | self.assertEqual(0, result.exit_code)
70 | self.assertNotIn("VIEWER PLUGIN", result.output)
71 | secho_mock.assert_any_call("invalid plugin BadLabViewer", fg="red")
72 |
73 |
74 | """
75 |
76 | def test_gen_plugin(self):
77 | virl = self.get_virl()
78 | with requests_mock.Mocker() as m:
79 | # Mock the request to return what we expect from the API.
80 | self.setup_mocks(m)
81 | runner = CliRunner()
82 | result = runner.invoke(virl, ["generate", "test-gen"])
83 | self.assertEqual("TEST GENERATOR\n", result.output)
84 |
85 | def test_view_plugin(self):
86 | virl = self.get_virl()
87 | with requests_mock.Mocker() as m:
88 | # Mock the request to return what we expect from the API.
89 | self.setup_mocks(m)
90 | runner = CliRunner()
91 | result = runner.invoke(virl, ["ls"])
92 | self.assertEqual("TEST VIEWER\n", result.output)
93 | """
94 |
--------------------------------------------------------------------------------
/tests/v2/cluster.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 |
6 | class TestCMLCluster(BaseCMLTest):
7 | def setup_mocks(self, m):
8 | super().setup_mocks(m)
9 | self.setup_func("get", m, "system_health", json=TestCMLCluster.get_system_health)
10 |
11 | @staticmethod
12 | def get_system_health(req, ctx=None):
13 | response = {
14 | "valid": True,
15 | "computes": {
16 | "17e91b4e-865a-4627-a6bb-50e3dfa988ab": {
17 | "kvm_vmx_enabled": True,
18 | "enough_cpus": True,
19 | "refplat_images_available": True,
20 | "lld_connected": True,
21 | "valid": True,
22 | "is_controller": True,
23 | "hostname": "cml-controller",
24 | }
25 | },
26 | "is_licensed": True,
27 | "is_enterprise": True,
28 | }
29 | return response
30 |
31 | def test_cml_cluster_info(self):
32 | with self.get_context() as m:
33 | # Mock the request to return what we expect from the API.
34 | self.setup_mocks(m)
35 | virl = self.get_virl()
36 | runner = CliRunner()
37 | result = runner.invoke(virl, ["cluster", "info"])
38 | self.assertEqual(0, result.exit_code)
39 | self.assertIn("17e91b4e-865a-4627-a6bb-50e3dfa988ab", result.output)
40 |
--------------------------------------------------------------------------------
/tests/v2/console.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 | try:
6 | from unittest.mock import patch
7 | except ImportError:
8 | from mock import patch
9 |
10 |
11 | class CMLConsoleTests(BaseCMLTest):
12 | def test_cml_console_display(self):
13 | with self.get_context() as m:
14 | # Mock the request to return what we expect from the API.
15 | self.setup_mocks(m)
16 | virl = self.get_virl()
17 | runner = CliRunner()
18 | result = runner.invoke(virl, ["console", "rtr-1", "--display"])
19 | self.assertEqual(0, result.exit_code)
20 |
21 | @patch("virl.cli.console.commands.call", autospec=False)
22 | def test_cml_console_connect(self, call_mock):
23 | with self.get_context() as m:
24 | # Mock the request to return what we expect from the API.
25 | self.setup_mocks(m)
26 | virl = self.get_virl()
27 | runner = CliRunner()
28 | runner.invoke(virl, ["console", "rtr-1"])
29 | call_mock.assert_called_once_with(["ssh", "-t", "admin@localhost", "open", "/5f0d96/n1/0"])
30 |
31 | @patch("virl.cli.console.commands.call", autospec=False)
32 | def test_cml_console_connect_24(self, call_mock):
33 | with self.get_context() as m:
34 | # Mock the request to return what we expect from the API.
35 | self.setup_mocks(m)
36 | virl = self.get_virl()
37 | runner = CliRunner()
38 | runner.invoke(virl, ["use", "--id", self.get_cml24_id()])
39 | runner.invoke(virl, ["console", "rtr-1"])
40 | call_mock.assert_called_once_with(["ssh", "-t", "admin@localhost", "open", "/Mock", "Test", "2.4/rtr-1/0"])
41 |
--------------------------------------------------------------------------------
/tests/v2/down.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from . import BaseCMLTest
6 |
7 |
8 | class CMLTestDown(BaseCMLTest):
9 | def setup_mocks(self, m):
10 | super().setup_mocks(m)
11 | self.setup_func("put", m, "labs/{}/stop".format(self.get_test_id()), json="STOPPED")
12 | self.setup_func("get", m, "labs/{}/check_if_converged".format(self.get_test_id()), json=True)
13 |
14 | def test_cml_down(self):
15 | with self.get_context() as m:
16 | # Mock the request to return what we expect from the API.
17 | self.setup_mocks(m)
18 | virl = self.get_virl()
19 | runner = CliRunner()
20 | result = runner.invoke(virl, ["down"])
21 | self.assertEqual(0, result.exit_code)
22 |
23 | def test_cml_down_by_name(self):
24 |
25 | try:
26 | os.remove(".virl/current_cml_lab")
27 | except OSError:
28 | pass
29 |
30 | with self.get_context() as m:
31 | # Mock the request to return what we expect from the API.
32 | self.setup_mocks(m)
33 | virl = self.get_virl()
34 | runner = CliRunner()
35 | result = runner.invoke(virl, ["down", "--lab-name", self.get_test_title()])
36 | self.assertEqual(0, result.exit_code)
37 |
38 | def test_cml_down_by_id(self):
39 |
40 | try:
41 | os.remove(".virl/current_cml_lab")
42 | except OSError:
43 | pass
44 |
45 | with self.get_context() as m:
46 | # Mock the request to return what we expect from the API.
47 | self.setup_mocks(m)
48 | virl = self.get_virl()
49 | runner = CliRunner()
50 | result = runner.invoke(virl, ["down", "--id", self.get_test_id()])
51 | self.assertEqual(0, result.exit_code)
52 |
--------------------------------------------------------------------------------
/tests/v2/extract.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from . import BaseCMLTest
6 |
7 |
8 | class CMLExtractTests(BaseCMLTest):
9 | def test_cml_extract(self):
10 | with self.get_context() as m:
11 | # Mock the request to return what we expect from the API.
12 | self.setup_mocks(m)
13 | virl = self.get_virl()
14 | runner = CliRunner()
15 | result = runner.invoke(virl, ["extract"])
16 | self.assertEqual(0, result.exit_code)
17 |
18 | def test_cml_extract_no_lab(self):
19 | try:
20 | os.remove(".virl/current_cml_lab")
21 | except OSError:
22 | pass
23 |
24 | with self.get_context() as m:
25 | # Mock the request to return what we expect from the API.
26 | self.setup_mocks(m)
27 | virl = self.get_virl()
28 | runner = CliRunner()
29 | result = runner.invoke(virl, ["extract"])
30 | self.assertEqual(1, result.exit_code)
31 | self.assertIn("Current lab is not set", result.output)
32 |
33 | def test_cml_extract_bogus_lab(self):
34 | try:
35 | os.remove(".virl/current_cml_lab")
36 | except OSError:
37 | pass
38 |
39 | src_dir = os.path.realpath(".virl")
40 | with open(".virl/cached_cml_labs/123456", "w") as fd:
41 | fd.write("lab: bogus\n")
42 |
43 | os.symlink("{}/cached_cml_labs/123456".format(src_dir), "{}/current_cml_lab".format(src_dir))
44 |
45 | with self.get_context() as m:
46 | # Mock the request to return what we expect from the API.
47 | self.setup_mocks(m)
48 | virl = self.get_virl()
49 | runner = CliRunner()
50 | result = runner.invoke(virl, ["extract"])
51 | os.remove(".virl/cached_cml_labs/123456")
52 | os.remove(".virl/current_cml_lab")
53 | self.assertEqual(1, result.exit_code)
54 | self.assertIn("Failed to find running lab 123456", result.output)
55 |
--------------------------------------------------------------------------------
/tests/v2/generate_ansible.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 |
6 | class Tests(BaseCMLTest):
7 | data = {
8 | "n0": {"name": "Lab Net", "interfaces": {}},
9 | "n1": {
10 | "name": "rtr-1",
11 | "interfaces": {
12 | "52:54:00:1f:27:95": {"id": "i2", "ip4": ["10.1.1.1"], "ip6": ["fc00::1"], "label": "MgmtEth0/RP0/CPU0/0"},
13 | "52:54:00:06:b7:7c": {"id": "i3", "ip4": [], "ip6": [], "label": "donotuse1"},
14 | "52:54:00:16:ef:1a": {"id": "i4", "ip4": [], "ip6": [], "label": "donotuse2"},
15 | "52:54:00:00:73:28": {"id": "i5", "ip4": [], "ip6": [], "label": "GigabitEthernet0/0/0/0"},
16 | },
17 | },
18 | }
19 |
20 | def test_virl_generate_ansible_yaml(self):
21 | with self.get_context() as m:
22 | # Mock the request to return what we expect from the API.
23 | self.setup_mocks(m)
24 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=self.data)
25 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=self.data["n0"])
26 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=self.data["n1"])
27 | virl = self.get_virl()
28 | runner = CliRunner()
29 | result = runner.invoke(virl, ["generate", "ansible"])
30 | self.assertEqual(0, result.exit_code)
31 |
32 | def test_virl_generate_ansible_topology_yaml(self):
33 | with self.get_context() as m:
34 | # Mock the request to return what we expect from the API.
35 | self.setup_mocks(m)
36 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=self.data)
37 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=self.data["n0"])
38 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=self.data["n1"])
39 | virl = self.get_virl()
40 | runner = CliRunner()
41 | result = runner.invoke(virl, ["generate", "ansible", "-o", "topology.yaml"])
42 | self.assertEqual(0, result.exit_code)
43 |
44 | def test_virl_generate_ansible_ini(self):
45 | with self.get_context() as m:
46 | # Mock the request to return what we expect from the API.
47 | self.setup_mocks(m)
48 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=self.data)
49 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=self.data["n0"])
50 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=self.data["n1"])
51 | virl = self.get_virl()
52 | runner = CliRunner()
53 | result = runner.invoke(virl, ["generate", "ansible", "--style", "ini"])
54 | self.assertEqual(0, result.exit_code)
55 |
--------------------------------------------------------------------------------
/tests/v2/generate_nso.py:
--------------------------------------------------------------------------------
1 | import requests_mock
2 | from click.testing import CliRunner
3 |
4 | from . import BaseCMLTest
5 | from .mocks.nso import MockNSOServer
6 |
7 |
8 | class Tests(BaseCMLTest):
9 | def test_virl_generate_nso(self):
10 | data = {
11 | "n0": {"name": "Lab Net", "interfaces": {}},
12 | "n1": {
13 | "name": "rtr-1",
14 | "interfaces": {
15 | "52:54:00:1f:27:95": {"id": "i2", "ip4": ["10.1.1.1"], "ip6": ["fc00::1"], "label": "MgmtEth0/RP0/CPU0/0"},
16 | "52:54:00:06:b7:7c": {"id": "i3", "ip4": [], "ip6": [], "label": "donotuse1"},
17 | "52:54:00:16:ef:1a": {"id": "i4", "ip4": [], "ip6": [], "label": "donotuse2"},
18 | "52:54:00:00:73:28": {"id": "i5", "ip4": [], "ip6": [], "label": "GigabitEthernet0/0/0/0"},
19 | },
20 | },
21 | }
22 | with self.get_context() as m, requests_mock.mock() as nso_mock:
23 | # Mock the request to return what we expect from the API.
24 | self.setup_mocks(m)
25 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=data)
26 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=data["n0"])
27 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=data["n1"])
28 |
29 | # mock the responses we expect from NSO
30 | devices_url = "http://localhost:8080/api/config/devices/"
31 | nso_mock.patch(devices_url, json=MockNSOServer.update_devices())
32 | sync_url = "http://localhost:8080/api/running/devices/"
33 | sync_url += "_operations/sync-from"
34 | nso_mock.post(sync_url, json=MockNSOServer.perform_sync_from())
35 | ned_url = "http://localhost:8080/api/config/devices/ned-ids/ned-id"
36 | nso_mock.get(ned_url, json=MockNSOServer.get_ned_list())
37 | module_url = "http://localhost:8080/api/config/modules-state/module"
38 | nso_mock.get(module_url, json=MockNSOServer.get_module_list())
39 |
40 | virl = self.get_virl()
41 | runner = CliRunner()
42 | result = runner.invoke(virl, ["generate", "nso", "--syncfrom"])
43 | self.assertEqual(0, result.exit_code)
44 |
--------------------------------------------------------------------------------
/tests/v2/generate_pyats.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 |
6 | class Tests(BaseCMLTest):
7 | def test_virl_generate_pyats(self):
8 | with self.get_context() as m:
9 | # Mock the request to return what we expect from the API.
10 | self.setup_mocks(m)
11 | virl = self.get_virl()
12 | runner = CliRunner()
13 | result = runner.invoke(virl, ["generate", "pyats"])
14 | self.assertEqual(0, result.exit_code)
15 |
16 | def test_virl_generate_pyats_topology(self):
17 | with self.get_context() as m:
18 | # Mock the request to return what we expect from the API.
19 | self.setup_mocks(m)
20 | virl = self.get_virl()
21 | runner = CliRunner()
22 | result = runner.invoke(virl, ["generate", "pyats", "-o", "topology.yaml"])
23 | self.assertEqual(0, result.exit_code)
24 |
--------------------------------------------------------------------------------
/tests/v2/good_plugins.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from virl.api.plugin import _test_enable_plugins
6 |
7 | from . import BaseCMLTest
8 |
9 |
10 | class CMLGoodPluginTest(BaseCMLTest):
11 | def setUp(self):
12 | _test_enable_plugins()
13 | super().setUp()
14 | os.environ["CML_PLUGIN_PATH"] = os.path.realpath("./tests/v2/plugins_good")
15 |
16 | @classmethod
17 | def tearDownClass(cls):
18 | super().tearDownClass()
19 | os.environ.pop("CML_PLUGIN_PATH", None)
20 | _test_enable_plugins(enabled=False)
21 |
22 | def test_cmd_plugin_output(self):
23 | virl = self.get_virl()
24 | with self.get_context() as m:
25 | runner = CliRunner()
26 | # Mock the request to return what we expect from the API.
27 | self.setup_mocks(m)
28 | result = runner.invoke(virl, ["--help"])
29 | self.assertEqual(0, result.exit_code)
30 | self.assertIn("test-cmd", result.output)
31 |
32 | def test_cmd_plugin(self):
33 | virl = self.get_virl()
34 | with self.get_context() as m:
35 | # Mock the request to return what we expect from the API.
36 | self.setup_mocks(m)
37 | runner = CliRunner()
38 | result = runner.invoke(virl, ["test-cmd"])
39 | self.assertEqual("TEST COMMAND\n", result.output)
40 |
41 | def test_gen_plugin_output(self):
42 | virl = self.get_virl()
43 | with self.get_context() as m:
44 | runner = CliRunner()
45 | # Mock the request to return what we expect from the API.
46 | self.setup_mocks(m)
47 | result = runner.invoke(virl, ["generate", "--help"])
48 | self.assertEqual(0, result.exit_code)
49 | self.assertIn("test-gen", result.output)
50 |
51 | def test_gen_plugin(self):
52 | virl = self.get_virl()
53 | with self.get_context() as m:
54 | # Mock the request to return what we expect from the API.
55 | self.setup_mocks(m)
56 | runner = CliRunner()
57 | result = runner.invoke(virl, ["generate", "test-gen"])
58 | self.assertEqual("TEST GENERATOR\n", result.output)
59 |
60 | def test_view_plugin(self):
61 | virl = self.get_virl()
62 | with self.get_context() as m:
63 | # Mock the request to return what we expect from the API.
64 | self.setup_mocks(m)
65 | runner = CliRunner()
66 | result = runner.invoke(virl, ["ls"])
67 | self.assertEqual("TEST VIEWER\n", result.output)
68 |
--------------------------------------------------------------------------------
/tests/v2/id.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 |
6 | class CMLIdTest(BaseCMLTest):
7 | def test_cml_id(self):
8 | virl = self.get_virl()
9 | with self.get_context() as m:
10 | # Mock the request to return what we expect from the API.
11 | self.setup_mocks(m)
12 | runner = CliRunner()
13 | result = runner.invoke(virl, ["id"])
14 | self.assertEqual("{} (ID: {})\n".format(self.get_test_title(), self.get_test_id()), result.output)
15 |
--------------------------------------------------------------------------------
/tests/v2/ls.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 |
6 | class TestCMLLs(BaseCMLTest):
7 | def test_cml_ls_all(self):
8 | with self.get_context() as m:
9 | self.setup_mocks(m)
10 | virl = self.get_virl()
11 | runner = CliRunner()
12 | result = runner.invoke(virl, ["ls", "--all"])
13 | self.assertEqual(0, result.exit_code)
14 |
15 | def test_cml_ls(self):
16 | with self.get_context() as m:
17 | self.setup_mocks(m)
18 | virl = self.get_virl()
19 | runner = CliRunner()
20 | result = runner.invoke(virl, ["ls"])
21 | self.assertEqual(0, result.exit_code)
22 |
--------------------------------------------------------------------------------
/tests/v2/mocks/__init__.py:
--------------------------------------------------------------------------------
1 | from .api import MockCMLServer # noqa
2 |
--------------------------------------------------------------------------------
/tests/v2/mocks/github.py:
--------------------------------------------------------------------------------
1 | class MockGitHub(object):
2 | @staticmethod
3 | def get_topology(req, ctx=None):
4 | with open("tests/v2/static/fake_repo_topology.yaml", "r") as fh:
5 | response = fh.read()
6 | return response
7 |
--------------------------------------------------------------------------------
/tests/v2/mocks/nso.py:
--------------------------------------------------------------------------------
1 | class MockNSOServer:
2 | @classmethod
3 | def launch_simulation(cls):
4 | response = u"TEST_ENV"
5 | return response
6 |
7 | @classmethod
8 | def get_node_console(cls):
9 | sim_response = {u"router2": u"10.94.241.194:17002", u"router1": u"10.94.241.194:17001"}
10 | return sim_response
11 |
12 | @classmethod
13 | def update_devices(cls):
14 | return {}
15 |
16 | @classmethod
17 | def perform_sync_from(cls):
18 | response = {"tailf-ncs:output": {"sync-result": [{"device": "router1", "result": True}, {"device": "router2", "result": True}]}}
19 | return response
20 |
21 | @classmethod
22 | def get_module_list(cls):
23 | response = {
24 | "ietf-yang-library:module": [
25 | {
26 | "name": "cisco-ios-cli-6.42",
27 | "revision": "",
28 | "schema": "http://localhost:8080/restconf/tailf/modules/cisco-ios-cli-6.42",
29 | "namespace": "http://tail-f.com/ns/ned-id/cisco-ios-cli-6.42",
30 | "conformance-type": "import",
31 | },
32 | ],
33 | }
34 | return response
35 |
36 | @classmethod
37 | def get_ned_list(cls):
38 | response = {
39 | "tailf-ncs:ned-id": [
40 | {
41 | "id": "cisco-ios-cli-6.42:cisco-ios-cli-6.42",
42 | "module": [
43 | {"name": "ietf-interfaces", "revision": "2014-05-08", "namespace": "urn:ietf:params:xml:ns:yang:ietf-interfaces"},
44 | {"name": "ietf-ip", "revision": "2014-06-16", "namespace": "urn:ietf:params:xml:ns:yang:ietf-ip"},
45 | {"name": "tailf-ned-cisco-ios", "revision": "2020-01-03", "namespace": "urn:ios"},
46 | {"name": "tailf-ned-cisco-ios-id", "namespace": "urn:ios-id"},
47 | {"name": "tailf-ned-cisco-ios-stats", "namespace": "urn:ios-stats"},
48 | ],
49 | },
50 | ],
51 | }
52 | return response
53 |
--------------------------------------------------------------------------------
/tests/v2/nodes.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from . import BaseCMLTest
6 |
7 |
8 | class CMLNodesTest(BaseCMLTest):
9 | def test_cml_nodes(self):
10 | data = {
11 | "n0": {"name": "Lab Net", "interfaces": {}},
12 | "n1": {
13 | "name": "rtr-1",
14 | "interfaces": {
15 | "52:54:00:1f:27:95": {"id": "i2", "ip4": ["10.1.1.1"], "ip6": ["fc00::1"], "label": "MgmtEth0/RP0/CPU0/0"},
16 | "52:54:00:06:b7:7c": {"id": "i3", "ip4": [], "ip6": [], "label": "donotuse1"},
17 | "52:54:00:16:ef:1a": {"id": "i4", "ip4": [], "ip6": [], "label": "donotuse2"},
18 | "52:54:00:00:73:28": {"id": "i5", "ip4": [], "ip6": [], "label": "GigabitEthernet0/0/0/0"},
19 | },
20 | },
21 | }
22 | with self.get_context() as m:
23 | # Mock the request to return what we expect from the API.
24 | self.setup_mocks(m)
25 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=data)
26 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=data["n0"])
27 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=data["n1"])
28 | virl = self.get_virl()
29 | runner = CliRunner()
30 | result = runner.invoke(virl, ["nodes"])
31 | self.assertEqual(0, result.exit_code)
32 |
33 | def test_cml_nodes_no_lab(self):
34 | try:
35 | os.remove(".virl/current_cml_lab")
36 | except OSError:
37 | pass
38 |
39 | with self.get_context() as m:
40 | # Mock the request to return what we expect from the API.
41 | self.setup_mocks(m)
42 | virl = self.get_virl()
43 | runner = CliRunner()
44 | result = runner.invoke(virl, ["nodes"])
45 | self.assertEqual(1, result.exit_code)
46 | self.assertIn("No current lab selected", result.output)
47 |
48 | def test_cml_nodes_bogus_lab(self):
49 | try:
50 | os.remove(".virl/current_cml_lab")
51 | except OSError:
52 | pass
53 |
54 | src_dir = os.path.realpath(".virl")
55 | with open(".virl/cached_cml_labs/123456", "w") as fd:
56 | fd.write("lab: bogus\n")
57 |
58 | os.symlink("{}/cached_cml_labs/123456".format(src_dir), "{}/current_cml_lab".format(src_dir))
59 |
60 | with self.get_context() as m:
61 | # Mock the request to return what we expect from the API.
62 | self.setup_mocks(m)
63 | virl = self.get_virl()
64 | runner = CliRunner()
65 | result = runner.invoke(virl, ["nodes"])
66 | os.remove(".virl/cached_cml_labs/123456")
67 | self.assertEqual(1, result.exit_code)
68 | self.assertIn("Lab 123456 is not running", result.output)
69 |
--------------------------------------------------------------------------------
/tests/v2/plugins_bad_cmd/cmd_plug_bad_run.py:
--------------------------------------------------------------------------------
1 | from virl.api.plugin import CommandPlugin
2 |
3 |
4 | class TestBadCmdPlugin(CommandPlugin, command="test-bad-cmd"):
5 | def run():
6 | print("TEST COMMAND")
7 |
--------------------------------------------------------------------------------
/tests/v2/plugins_bad_gen/generator_plugin_bad.py:
--------------------------------------------------------------------------------
1 | from virl.api.plugin import GeneratorPlugin
2 |
3 |
4 | class TestBadGenPlugin(GeneratorPlugin, generator="test-bad-gen"):
5 | def generate():
6 | print("TEST GENERATOR")
7 |
--------------------------------------------------------------------------------
/tests/v2/plugins_bad_viewer/viewer_plugin_bad.py:
--------------------------------------------------------------------------------
1 | from virl.api.plugin import ViewerPlugin
2 |
3 |
4 | class BadLabViewer(ViewerPlugin):
5 | def visualize(self, **kwargs):
6 | print("VIEWER PLUGIN")
7 |
--------------------------------------------------------------------------------
/tests/v2/plugins_good/cmd_plug.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api.plugin import CommandPlugin
4 |
5 |
6 | class TestCmdPlugin(CommandPlugin, command="test-cmd"):
7 | @staticmethod
8 | @click.command()
9 | def run():
10 | print("TEST COMMAND")
11 |
--------------------------------------------------------------------------------
/tests/v2/plugins_good/generator_plugin.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api.plugin import GeneratorPlugin
4 |
5 |
6 | class TestGenPlugin(GeneratorPlugin, generator="test-gen"):
7 | @staticmethod
8 | @click.command()
9 | def generate():
10 | print("TEST GENERATOR")
11 |
--------------------------------------------------------------------------------
/tests/v2/plugins_good/viewer_plugin.py:
--------------------------------------------------------------------------------
1 | from virl.api import ViewerPlugin
2 |
3 |
4 | class LabViewer(ViewerPlugin, viewer="lab"):
5 | def visualize(self, **kwargs):
6 | print("TEST VIEWER")
7 |
--------------------------------------------------------------------------------
/tests/v2/pull.py:
--------------------------------------------------------------------------------
1 | import requests_mock
2 | from click.testing import CliRunner
3 |
4 | from . import BaseCMLTest
5 | from .mocks.github import MockGitHub
6 |
7 |
8 | class Tests(BaseCMLTest):
9 | def test_virl_pull(self):
10 | with requests_mock.mock() as m:
11 | # Mock the request to return what we expect from the API.
12 | topo_url = "https://raw.githubusercontent.com/"
13 | topo_url += "foo/bar/main/topology.yaml"
14 | m.get(topo_url, json=MockGitHub.get_topology)
15 | virl = self.get_virl()
16 | runner = CliRunner()
17 | result = runner.invoke(virl, ["pull", "foo/bar"])
18 | self.assertEqual(0, result.exit_code)
19 |
20 | def test_virl_pull_invalid_repo(self):
21 | with requests_mock.mock() as m:
22 | # Mock the request to return what we expect from the API.
23 | topo_url = "https://raw.githubusercontent.com/"
24 | topo_url += "doesnt/exist/main/topology.yaml"
25 | m.get(topo_url, status_code=400)
26 | virl = self.get_virl()
27 | runner = CliRunner()
28 | result = runner.invoke(virl, ["pull", "doesnt/exist"])
29 | expected = (
30 | "Pulling topology.yaml from doesnt/exist on branch main\nError pulling topology.yaml from doesnt/exist on branch "
31 | "main - repo, file, or branch not found\n"
32 | )
33 | self.assertEqual(result.output, expected)
34 |
--------------------------------------------------------------------------------
/tests/v2/save.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from . import BaseCMLTest
6 |
7 |
8 | class CMLSaveTests(BaseCMLTest):
9 | def test_cml_save(self):
10 | with self.get_context() as m:
11 | # Mock the request to return what we expect from the API.
12 | self.setup_mocks(m)
13 | virl = self.get_virl()
14 | runner = CliRunner()
15 | result = runner.invoke(virl, ["save"])
16 | self.assertEqual(0, result.exit_code)
17 | self.assertIn("Extracting configurations", result.output)
18 | self.assertIn("Writing topology.yaml", result.output)
19 |
20 | def test_cml_save_no_extract(self):
21 | with self.get_context() as m:
22 | # Mock the request to return what we expect from the API.
23 | self.setup_mocks(m)
24 | virl = self.get_virl()
25 | runner = CliRunner()
26 | result = runner.invoke(virl, ["save", "--no-extract"])
27 | self.assertEqual(0, result.exit_code)
28 | self.assertNotIn("Extracting configurations", result.output)
29 | self.assertIn("Writing topology.yaml", result.output)
30 |
31 | def test_cml_save_filename(self):
32 | with self.get_context() as m:
33 | # Mock the request to return what we expect from the API.
34 | self.setup_mocks(m)
35 | virl = self.get_virl()
36 | runner = CliRunner()
37 | result = runner.invoke(virl, ["save", "-f", "{}.yaml".format(self.get_test_id())])
38 | self.assertEqual(0, result.exit_code)
39 | self.assertIn("Extracting configurations", result.output)
40 | self.assertIn("Writing {}.yaml".format(self.get_test_id()), result.output)
41 |
42 | def test_cml_save_no_lab(self):
43 | try:
44 | os.remove(".virl/current_cml_lab")
45 | except OSError:
46 | pass
47 |
48 | with self.get_context() as m:
49 | # Mock the request to return what we expect from the API.
50 | self.setup_mocks(m)
51 | virl = self.get_virl()
52 | runner = CliRunner()
53 | result = runner.invoke(virl, ["save"])
54 | self.assertEqual(1, result.exit_code)
55 | self.assertIn("Current lab is not set", result.output)
56 |
57 | def test_cml_save_bogus_lab(self):
58 | try:
59 | os.remove(".virl/current_cml_lab")
60 | except OSError:
61 | pass
62 |
63 | src_dir = os.path.realpath(".virl")
64 | with open(".virl/cached_cml_labs/123456", "w") as fd:
65 | fd.write("lab: bogus\n")
66 |
67 | os.symlink("{}/cached_cml_labs/123456".format(src_dir), "{}/current_cml_lab".format(src_dir))
68 |
69 | with self.get_context() as m:
70 | # Mock the request to return what we expect from the API.
71 | self.setup_mocks(m)
72 | virl = self.get_virl()
73 | runner = CliRunner()
74 | result = runner.invoke(virl, ["save"])
75 | os.remove(".virl/cached_cml_labs/123456")
76 | os.remove(".virl/current_cml_lab")
77 | self.assertEqual(1, result.exit_code)
78 | self.assertIn("Failed to find running lab 123456", result.output)
79 |
--------------------------------------------------------------------------------
/tests/v2/search.py:
--------------------------------------------------------------------------------
1 | import requests_mock
2 | from click.testing import CliRunner
3 |
4 | from . import BaseCMLTest
5 |
6 |
7 | class SearchTests(BaseCMLTest):
8 | @requests_mock.mock()
9 | def test_virl_search(self, m):
10 | m.get("https://api.github.com/orgs/virlfiles/repos", json=self.mock_response())
11 | virl = self.get_virl()
12 | runner = CliRunner()
13 | result = runner.invoke(virl, ["search"])
14 | self.assertEqual(0, result.exit_code)
15 |
16 | @requests_mock.mock()
17 | def test_virl_search_with_query_name(self, m):
18 | m.get("https://api.github.com/orgs/virlfiles/repos", json=self.mock_response())
19 | virl = self.get_virl()
20 | runner = CliRunner()
21 | result = runner.invoke(virl, ["search", "ios"])
22 | self.assertEqual(0, result.exit_code)
23 |
24 | @requests_mock.mock()
25 | def test_virl_search_with_query_descr(self, m):
26 | # Mock the request to return what we expect from the API.
27 | m.get("https://api.github.com/orgs/virlfiles/repos", json=self.mock_response())
28 | virl = self.get_virl()
29 | runner = CliRunner()
30 | result = runner.invoke(virl, ["search", "hello"])
31 | self.assertEqual(0, result.exit_code)
32 |
33 | def mock_response(self):
34 | response = [
35 | {"name": "2-ios-router", "full_name": "virlfiles/2-ios-router", "description": "hello world virlfile", "stargazers_count": 344}
36 | ]
37 | return response
38 |
--------------------------------------------------------------------------------
/tests/v2/ssh.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 | try:
6 | from unittest.mock import patch
7 | except ImportError:
8 | from mock import patch
9 |
10 |
11 | class Tests(BaseCMLTest):
12 | data = {
13 | "n0": {"name": "Lab Net", "interfaces": {}},
14 | "n1": {
15 | "name": "rtr-1",
16 | "interfaces": {
17 | "52:54:00:1f:27:95": {"id": "i2", "ip4": ["10.1.1.1"], "ip6": ["fc00::1"], "label": "MgmtEth0/RP0/CPU0/0"},
18 | "52:54:00:06:b7:7c": {"id": "i3", "ip4": [], "ip6": [], "label": "donotuse1"},
19 | "52:54:00:16:ef:1a": {"id": "i4", "ip4": [], "ip6": [], "label": "donotuse2"},
20 | "52:54:00:00:73:28": {"id": "i5", "ip4": [], "ip6": [], "label": "GigabitEthernet0/0/0/0"},
21 | },
22 | },
23 | }
24 |
25 | @patch("virl.cli.ssh.commands.call", autospec=False)
26 | def test_virl_ssh(self, call_mock):
27 | with self.get_context() as m:
28 | # Mock the request to return what we expect from the API.
29 | self.setup_mocks(m)
30 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=self.data)
31 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=self.data["n0"])
32 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=self.data["n1"])
33 | virl = self.get_virl()
34 | runner = CliRunner()
35 | runner.invoke(virl, ["ssh", "rtr-1"])
36 | call_mock.assert_called_once_with(["ssh", "cisco@10.1.1.1"])
37 |
--------------------------------------------------------------------------------
/tests/v2/static/fake_image_definitions.yaml:
--------------------------------------------------------------------------------
1 | id: alpine-3-10-base
2 | node_definition_id: alpine
3 | description: Alpine Linux and network tools
4 | label: Alpine 3.10
5 | disk_image: alpine-3-10-base.qcow2
6 | read_only: true
7 | disk_subfolder: alpine-3-10-base
8 | schema_version: 0.0.1
9 |
--------------------------------------------------------------------------------
/tests/v2/static/fake_repo_bad_topology.yaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tests/v2/static/fake_repo_topology.yaml:
--------------------------------------------------------------------------------
1 | lab:
2 | description: ''
3 | notes: ''
4 | title: Fake Lab
5 | version: 0.1.0
6 | links:
7 | - id: l0
8 | n1: n0
9 | n2: n1
10 | i1: i0
11 | i2: i0
12 | label: test-sw-mgmt0<->ext-conn-0-port
13 | nodes:
14 | - boot_disk_size: 0
15 | configuration: hostname inserthostname_here
16 | cpu_limit: 100
17 | cpus: 0
18 | data_volume: 0
19 | hide_links: false
20 | id: n0
21 | image_definition: nxosv-7-3-0
22 | label: test-sw
23 | node_definition: nxosv
24 | ram: 0
25 | tags: []
26 | x: -350
27 | y: 0
28 | interfaces:
29 | - id: i0
30 | label: mgmt0
31 | slot: 0
32 | type: physical
33 | - id: i1
34 | label: Ethernet2/1
35 | slot: 1
36 | type: physical
37 | - id: i2
38 | label: Ethernet2/2
39 | slot: 2
40 | type: physical
41 | - id: i3
42 | label: Ethernet2/3
43 | slot: 3
44 | type: physical
45 | - boot_disk_size: 0
46 | configuration: bridge0
47 | cpu_limit: 100
48 | cpus: 0
49 | data_volume: 0
50 | hide_links: false
51 | id: n1
52 | label: ext-conn-0
53 | node_definition: external_connector
54 | ram: 0
55 | tags: []
56 | x: 0
57 | y: 0
58 | interfaces:
59 | - id: i0
60 | label: port
61 | slot: 0
62 | type: physical
63 |
--------------------------------------------------------------------------------
/tests/v2/telnet.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 | try:
6 | from unittest.mock import patch
7 | except ImportError:
8 | from mock import patch
9 |
10 |
11 | class Tests(BaseCMLTest):
12 | data = {
13 | "n0": {"name": "Lab Net", "interfaces": {}},
14 | "n1": {
15 | "name": "rtr-1",
16 | "interfaces": {
17 | "52:54:00:1f:27:95": {"id": "i2", "ip4": ["10.1.1.1"], "ip6": ["fc00::1"], "label": "MgmtEth0/RP0/CPU0/0"},
18 | "52:54:00:06:b7:7c": {"id": "i3", "ip4": [], "ip6": [], "label": "donotuse1"},
19 | "52:54:00:16:ef:1a": {"id": "i4", "ip4": [], "ip6": [], "label": "donotuse2"},
20 | "52:54:00:00:73:28": {"id": "i5", "ip4": [], "ip6": [], "label": "GigabitEthernet0/0/0/0"},
21 | },
22 | },
23 | }
24 |
25 | @patch("virl.cli.telnet.commands.call", autospec=False)
26 | def test_virl_telnet(self, call_mock):
27 | with self.get_context() as m:
28 | # Mock the request to return what we expect from the API.
29 | self.setup_mocks(m)
30 | self.setup_func("get", m, "labs/{}/layer3_addresses".format(self.get_test_id()), json=self.data)
31 | self.setup_func("get", m, "labs/{}/nodes/n0/layer3_addresses".format(self.get_test_id()), json=self.data["n0"])
32 | self.setup_func("get", m, "labs/{}/nodes/n1/layer3_addresses".format(self.get_test_id()), json=self.data["n1"])
33 | virl = self.get_virl()
34 | runner = CliRunner()
35 | runner.invoke(virl, ["telnet", "rtr-1"])
36 | call_mock.assert_called_once_with(["telnet", "10.1.1.1"])
37 |
--------------------------------------------------------------------------------
/tests/v2/test_cli.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 |
6 | class TestCMLHelp(BaseCMLTest):
7 | def test_cml_help(self):
8 | runner = CliRunner()
9 | virl = self.get_virl()
10 | result = runner.invoke(virl, ["--help"])
11 | self.assertEqual(0, result.exit_code)
12 | for command in [
13 | "clear",
14 | "cockpit",
15 | "command",
16 | "console",
17 | "definitions",
18 | "down",
19 | "extract",
20 | "generate",
21 | "groups",
22 | "id",
23 | "license",
24 | "ls",
25 | "nodes",
26 | "pull",
27 | "rm",
28 | "save",
29 | "search",
30 | "ssh",
31 | "start",
32 | "stop",
33 | "telnet",
34 | "tmux",
35 | "ui",
36 | "up",
37 | "use",
38 | "users",
39 | "version",
40 | "wipe",
41 | ]:
42 | self.assertIn(command, result.output)
43 |
--------------------------------------------------------------------------------
/tests/v2/tmux.py:
--------------------------------------------------------------------------------
1 | from click.testing import CliRunner
2 |
3 | from . import BaseCMLTest
4 |
5 | try:
6 | from unittest.mock import call, patch
7 | except ImportError:
8 | from mock import call, patch
9 |
10 |
11 | class CMLTmuxTests(BaseCMLTest):
12 | @patch("virl.cli.tmux.commands.libtmux.server.Server")
13 | def test_cml_tmux_panes(self, mock_server):
14 | with self.get_context() as m:
15 | # Mocking libtmux server
16 | mock_session = mock_server.return_value.new_session.return_value
17 | mock_window = mock_session.windows[0]
18 | mock_pane = mock_window.panes[0]
19 | mock_window.split_window.return_value = mock_pane
20 | mock_window.panes = [mock_pane]
21 | # Mock the request to return what we expect from the API.
22 | self.setup_mocks(m)
23 | virl = self.get_virl()
24 | runner = CliRunner()
25 | runner.invoke(virl, ["tmux"])
26 | mock_server.assert_called_once()
27 | mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test-5f0d", kill_session=True)
28 | expected_calls = [
29 | call("printf '\\033]2;%s\\033\\\\' 'rtr-1'", suppress_history=True),
30 | call("ssh -t admin@localhost open /5f0d96/n1/0", suppress_history=True),
31 | ]
32 | mock_pane.send_keys.assert_has_calls(expected_calls, any_order=True)
33 |
34 | @patch("virl.cli.tmux.commands.libtmux.server.Server")
35 | def test_cml_tmux_panes24(self, mock_server):
36 | with self.get_context() as m:
37 | # Mocking libtmux server
38 | mock_session = mock_server.return_value.new_session.return_value
39 | mock_window = mock_session.windows[0]
40 | mock_pane = mock_window.panes[0]
41 | mock_window.split_window.return_value = mock_pane
42 | mock_window.panes = [mock_pane]
43 | # Mock the request to return what we expect from the API.
44 | self.setup_mocks(m)
45 | virl = self.get_virl()
46 | runner = CliRunner()
47 | lab_id = self.get_cml24_id()
48 | runner.invoke(virl, ["use", "--id", lab_id])
49 | runner.invoke(virl, ["tmux"])
50 | mock_server.assert_called_once()
51 | mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test 2_4-8811", kill_session=True)
52 | expected_calls = [
53 | call("printf '\\033]2;%s\\033\\\\' 'rtr-1'", suppress_history=True),
54 | call("ssh -t admin@localhost open /Mock Test 2.4/rtr-1/0", suppress_history=True),
55 | ]
56 | mock_pane.send_keys.assert_has_calls(expected_calls, any_order=True)
57 |
58 | @patch("virl.cli.tmux.commands.libtmux.server.Server")
59 | def test_cml_tmux_windows_24(self, mock_server):
60 | with self.get_context() as m:
61 | # Mocking libtmux server
62 | mock_session = mock_server.return_value.new_session.return_value
63 | mock_window = mock_session.windows[0]
64 | mock_session.windows = [mock_window]
65 | # Mock the request to return what we expect from the API.
66 | self.setup_mocks(m)
67 | virl = self.get_virl()
68 | runner = CliRunner()
69 | lab_id = self.get_cml24_id()
70 | runner.invoke(virl, ["use", "--id", lab_id])
71 | runner.invoke(virl, ["tmux", "--group", "windows"])
72 | mock_server.assert_called_once()
73 | mock_server.return_value.new_session.assert_called_once_with(session_name="Mock Test 2_4-8811", kill_session=True)
74 | expected_calls = [
75 | call("rename-window", "rtr-1"),
76 | ]
77 | mock_window.cmd.assert_has_calls(expected_calls, any_order=True)
78 |
--------------------------------------------------------------------------------
/tests/v2/use.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from click.testing import CliRunner
4 |
5 | from . import BaseCMLTest
6 |
7 | try:
8 | from unittest.mock import patch
9 | except ImportError:
10 | from mock import patch # noqa
11 |
12 |
13 | class CMLUseTest(BaseCMLTest):
14 | @patch("virl.cli.use.commands.call", autospec=False, return_value=0)
15 | def test_cml_use(self, call_mock):
16 | with self.get_context() as m:
17 | self.setup_mocks(m)
18 | virl = self.get_virl()
19 | runner = CliRunner()
20 | runner.invoke(virl, ["use"])
21 | call_mock.assert_called_once_with(["virl", "use", "--help"])
22 |
23 | def test_cml_use_with_lab(self):
24 | with self.get_context() as m:
25 | self.setup_mocks(m)
26 | virl = self.get_virl()
27 | runner = CliRunner()
28 | result = runner.invoke(virl, ["use", self.get_test_title()])
29 | self.assertEqual(0, result.exit_code)
30 |
31 | def test_cml_use_with_id(self):
32 | with self.get_context() as m:
33 | self.setup_mocks(m)
34 | virl = self.get_virl()
35 | runner = CliRunner()
36 | result = runner.invoke(virl, ["use", "--id", self.get_test_id()])
37 | self.assertEqual(0, result.exit_code)
38 |
39 | def test_cml_use_with_lab_name(self):
40 | with self.get_context() as m:
41 | self.setup_mocks(m)
42 | virl = self.get_virl()
43 | runner = CliRunner()
44 | result = runner.invoke(virl, ["use", "--lab-name", self.get_test_title()])
45 | self.assertEqual(0, result.exit_code)
46 |
47 | def test_cml_use_with_cache(self):
48 | try:
49 | os.remove(".virl/current_cml_lab")
50 | except OSError:
51 | pass
52 |
53 | try:
54 | os.remove(".virl/cached_cml_labs/{}".format(self.get_test_id()))
55 | except OSError:
56 | pass
57 |
58 | with self.get_context() as m:
59 | self.setup_mocks(m)
60 | virl = self.get_virl()
61 | runner = CliRunner()
62 | result = runner.invoke(virl, ["use", "--id", self.get_test_id()])
63 | self.assertEqual(0, result.exit_code)
64 |
65 | def test_cml_use_with_bogus_id(self):
66 | with self.get_context() as m:
67 | self.setup_mocks(m)
68 | virl = self.get_virl()
69 | runner = CliRunner()
70 | result = runner.invoke(virl, ["use", "--id", "123456"])
71 | self.assertEqual(1, result.exit_code)
72 | self.assertIn("Unable to find unique lab in the cache or on the server", result.output)
73 |
--------------------------------------------------------------------------------
/virl/__init__.py:
--------------------------------------------------------------------------------
1 | from .about import __version__ # noqa
--------------------------------------------------------------------------------
/virl/about.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.5.0"
2 |
--------------------------------------------------------------------------------
/virl/api/__init__.py:
--------------------------------------------------------------------------------
1 | from .api import VIRLServer # noqa
2 | from .cml import CachedLab # noqa
3 | from .plugin import (CommandPlugin, GeneratorPlugin, NoPluginError, # noqa
4 | Plugin, ViewerPlugin, check_valid_plugin, load_plugins)
5 |
--------------------------------------------------------------------------------
/virl/api/api.py:
--------------------------------------------------------------------------------
1 | from .credentials import get_credentials
2 |
3 |
4 | class VIRLServer(object):
5 | def __init__(self):
6 | self._host, self._user, self._passwd, self._config = get_credentials()
7 |
8 | @property
9 | def host(self):
10 | return self._host
11 |
12 | @property
13 | def user(self):
14 | return self._user
15 |
16 | @property
17 | def passwd(self):
18 | return self._passwd
19 |
20 | @property
21 | def config(self):
22 | return self._config
23 |
--------------------------------------------------------------------------------
/virl/api/cml.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from yaml import load
4 |
5 | try:
6 | from yaml import CLoader as Loader
7 | except ImportError:
8 | from yaml import Loader
9 |
10 |
11 | class CachedLab(object):
12 | """
13 | This is a stub class to represent a pseudo-lab that is cached locally.
14 | Only enough of the lab model is implemented for the required virlutils functions.
15 | """
16 |
17 | __id = None
18 | __title = None
19 | __description = None
20 | __stats = {
21 | "nodes": 0,
22 | "links": 0,
23 | "interfaces": 0,
24 | }
25 |
26 | def __init__(self, lab_id, cache_file):
27 | if not os.path.exists(cache_file):
28 | raise FileNotFoundError("Cached lab {} not found".format(cache_file))
29 |
30 | self.__id = lab_id
31 |
32 | with open(cache_file, "rb") as fd:
33 | lab = load(fd, Loader=Loader)
34 |
35 | self.__title = lab["lab"]["title"]
36 | self.__description = lab["lab"]["description"]
37 | self.__stats["nodes"] = len(lab["nodes"])
38 | self.__stats["links"] = len(lab["links"])
39 | for n in lab["nodes"]:
40 | self.__stats["interfaces"] += len(n["interfaces"])
41 |
42 | @property
43 | def id(self):
44 | return self.__id
45 |
46 | @property
47 | def title(self):
48 | return self.__title
49 |
50 | @property
51 | def description(self):
52 | return self.__description
53 |
54 | def state(self):
55 | return "CACHED"
56 |
57 | @property
58 | def statistics(self):
59 | return self.__stats
60 |
61 | @property
62 | def owner(self):
63 | return "N/A"
64 |
65 | @property
66 | def username(self):
67 | return "N/A"
68 |
--------------------------------------------------------------------------------
/virl/api/credentials.py:
--------------------------------------------------------------------------------
1 | """
2 | Collection of utility classes to make getting credentials and
3 | configuration easier.
4 | """
5 | import getpass
6 | import os
7 |
8 | from virl.helpers import find_virl
9 |
10 |
11 | def _get_from_user(prompt): # pragma: no cover
12 | """
13 | Get the input from the user through interactive prompt.
14 | """
15 | resp = input(prompt)
16 | return resp
17 |
18 |
19 | def _get_password(prompt):
20 | """
21 | Get the password from the user through interactive prompt.
22 | Using this will ensure that the password is not displayed as
23 | it is typed.
24 | """
25 | return getpass.getpass(prompt)
26 |
27 |
28 | def _get_from_file(virlrc, prop_name):
29 | if os.path.isfile(virlrc):
30 | with open(virlrc) as fh:
31 | config = fh.readlines()
32 |
33 | for line in config:
34 | if line.startswith(prop_name):
35 | prop = line.split("=")[1].strip()
36 | if prop.startswith('"') and prop.endswith('"'):
37 | prop = prop[1:-1]
38 | return prop
39 |
40 |
41 | def get_prop(prop_name):
42 | """
43 | Gets a variable using the following order
44 |
45 | * Check for .virlrc in current directory
46 |
47 | * recurse up directory tree for .virlrc
48 |
49 | * Check environment variables
50 |
51 | * Check ~/.virlrc
52 |
53 | * Prompt user
54 |
55 | """
56 | # check for .virlrc in current directory
57 | cwd = os.getcwd()
58 | virlrc = os.path.join(cwd, ".virlrc")
59 | prop = _get_from_file(virlrc, prop_name)
60 |
61 | if prop:
62 | return prop
63 |
64 | # search up directory tree for a .virlrc
65 | virl_dir = find_virl()
66 | if virl_dir:
67 | virlrc = os.path.join(virl_dir, ".virlrc")
68 | prop = _get_from_file(virlrc, prop_name)
69 |
70 | if prop:
71 | return prop
72 |
73 | # try environment next
74 | prop = os.getenv(prop_name, None)
75 | if prop:
76 | return prop
77 |
78 | # check for .virlrc in home directory
79 | path = os.path.expanduser("~")
80 | virlrc = os.path.join(path, ".virlrc")
81 | prop = _get_from_file(virlrc, prop_name)
82 |
83 | return prop or None
84 |
85 |
86 | def get_credentials(rcfile="~/.virlrc"):
87 | """
88 | Used to get the VIRL credentials
89 |
90 | * The login credentials are taken in the following order
91 |
92 | * Check for .virlrc in current directory
93 |
94 | * Check environment variables
95 |
96 | * Check ~/.virlrc
97 |
98 | * Prompt user
99 |
100 | """
101 | # initialize vars
102 | host = None
103 | username = None
104 | password = None
105 | config = dict()
106 |
107 | host = get_prop("VIRL_HOST")
108 | username = get_prop("VIRL_USERNAME")
109 | password = get_prop("VIRL_PASSWORD")
110 |
111 | # some additional configuration that can be set / overriden
112 | configurable_props = [
113 | "VIRL_TELNET_COMMAND",
114 | "VIRL_CONSOLE_COMMAND",
115 | "VIRL_SSH_COMMAND",
116 | "VIRL_SSH_USERNAME",
117 | "CML_CONSOLE_COMMAND",
118 | "CML2_PLUS",
119 | "CML_VERIFY_CERT",
120 | "CML_DEVICE_USERNAME",
121 | "CML_DEVICE_PASSWORD",
122 | "CML_DEVICE_ENABLE_PASSWORD",
123 | "CML_PLUGIN_PATH",
124 | ]
125 |
126 | for p in configurable_props:
127 | if get_prop(p):
128 | config[p] = get_prop(p)
129 |
130 | if not host: # pragma: no cover
131 | prompt = "Please enter the IP / hostname of your virl server: "
132 | host = _get_from_user(prompt)
133 |
134 | if not username: # pragma: no cover
135 | username = _get_from_user("Please enter your VIRL username: ")
136 |
137 | if not password: # pragma: no cover
138 | password = _get_password("Please enter your password: ")
139 |
140 | if not all([host, username, password]): # pragma: no cover
141 | print("Unable to determine CML/VIRL credentials, please see docs")
142 | exit(1)
143 | else:
144 | return (host, username, password, config)
145 |
--------------------------------------------------------------------------------
/virl/api/github.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 |
4 | def get_repos(org='virlfiles', query=None):
5 | resp = requests.get('https://api.github.com/orgs/{}/repos'.format(org))
6 | ret = list()
7 |
8 | if query is None:
9 | return resp.json()
10 |
11 | for repo in resp.json():
12 | name = repo['name']
13 | descr = repo['description']
14 | if query in name:
15 | ret.append(repo)
16 | if descr and query in descr:
17 | ret.append(repo)
18 |
19 | return ret
20 |
--------------------------------------------------------------------------------
/virl/api/plugin.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from abc import ABC, abstractmethod
4 | from importlib import import_module
5 | from pkgutil import iter_modules
6 |
7 | import click
8 |
9 | _plugins_enabled = True
10 |
11 |
12 | class NoPluginError(Exception):
13 | pass
14 |
15 |
16 | class Plugin(ABC):
17 | _command_plugins = {}
18 | _generator_plugins = {}
19 | _viewer_plugins = {}
20 |
21 | _plugin_map = {
22 | "command": _command_plugins,
23 | "generator": _generator_plugins,
24 | "viewer": _viewer_plugins,
25 | }
26 |
27 | _plugin_types = [
28 | "CommandPlugin",
29 | "GeneratorPlugin",
30 | "ViewerPlugin",
31 | ]
32 |
33 | def __new__(cls, **kwargs):
34 | # only provide a plugin if global plugin support is enabled.
35 | if not _plugins_enabled:
36 | raise NoPluginError("plugin support is disabled")
37 |
38 | for t, d in cls._plugin_map.items():
39 | if t in kwargs:
40 | val = kwargs.pop(t)
41 | if val in d:
42 | return object.__new__(d[val])
43 | else:
44 | raise NoPluginError("no {} plugin for {}".format(t, val))
45 |
46 | raise ValueError("unsupported plugin")
47 |
48 | def __init_subclass__(cls, **kwargs):
49 | ptype = None
50 | pdict = None
51 | good_plugin = False
52 | for t, d in cls._plugin_map.items():
53 | nptype = kwargs.pop(t, None)
54 | if nptype and ptype:
55 | raise ValueError("plugin may only contain one type: {}".format(", ".join(cls._plugin_map.keys())))
56 |
57 | if nptype:
58 | ptype = nptype
59 | pdict = d
60 |
61 | if ptype:
62 | good_plugin = True
63 |
64 | if ptype not in pdict:
65 | pdict[ptype] = cls
66 |
67 | if cls.__name__ not in cls._plugin_types and not good_plugin:
68 | raise ValueError("invalid plugin {}".format(cls.__name__))
69 |
70 | @classmethod
71 | def get_plugins(cls, t):
72 | return list(cls._plugin_map[t].keys())
73 |
74 | @classmethod
75 | def remove_plugin(cls, t, name):
76 | cls._plugin_map[t].pop(name, None)
77 |
78 |
79 | class CommandPlugin(Plugin, ABC):
80 | def __init__(self, **kwargs):
81 | self._command = kwargs.pop("command")
82 |
83 | @property
84 | def command(self):
85 | return self._command
86 |
87 | @staticmethod
88 | @abstractmethod
89 | @click.command()
90 | def run():
91 | """
92 | This must be a "click" command.
93 | """
94 | raise NotImplementedError
95 |
96 |
97 | class GeneratorPlugin(Plugin, ABC):
98 | def __init__(self, **kwargs):
99 | self._generator = kwargs.pop("generator")
100 |
101 | @property
102 | def generator(self):
103 | return self._generator
104 |
105 | @staticmethod
106 | @abstractmethod
107 | @click.command()
108 | def generate():
109 | """
110 | This must be a "click" command.
111 | """
112 | raise NotImplementedError
113 |
114 |
115 | class ViewerPlugin(Plugin, ABC):
116 | def __init__(self, **kwargs):
117 | self._viewer = kwargs.pop("viewer")
118 |
119 | @property
120 | def viewer(self):
121 | return self._viewer
122 |
123 | @abstractmethod
124 | def visualize(self, **kwargs):
125 | raise NotImplementedError
126 |
127 |
128 | def load_plugins(basedirs):
129 | if isinstance(basedirs, str):
130 | basedirs = basedirs.split(os.pathsep)
131 |
132 | modules = iter_modules(path=basedirs)
133 | for d in basedirs:
134 | if os.path.isdir:
135 | sys.path.append(d)
136 |
137 | for mod in modules:
138 | try:
139 | import_module(name=mod.name)
140 | except (AttributeError, ImportError, ValueError, TypeError) as e:
141 | # This is not a valid plugin
142 | click.secho(str(e), fg="red")
143 |
144 |
145 | def check_valid_plugin(pl, mtd, mtd_name, is_click=True):
146 | if is_click:
147 | if not hasattr(mtd, "hidden") or not isinstance(pl.__class__.__dict__[mtd_name], staticmethod):
148 | return False
149 |
150 | return True
151 |
152 |
153 | def _test_enable_plugins(enabled=True):
154 | """
155 | This function allows the unit tests to toggle
156 | plugin support on and off. Without it, once
157 | pugins are loaded, they remain loaded for the whole
158 | test suite. This can break subsequent tests.
159 | """
160 | global _plugins_enabled
161 | _plugins_enabled = enabled
162 |
--------------------------------------------------------------------------------
/virl/cli/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/__init__.py
--------------------------------------------------------------------------------
/virl/cli/clear/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/clear/__init__.py
--------------------------------------------------------------------------------
/virl/cli/clear/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.helpers import clear_current_lab
4 |
5 |
6 | @click.command()
7 | def clear():
8 | """
9 | clear the current lab ID
10 | """
11 |
12 | clear_current_lab()
13 |
--------------------------------------------------------------------------------
/virl/cli/cluster/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.cluster.info.commands import info
4 |
5 |
6 | @click.group()
7 | def cluster():
8 | """
9 | display and manage CML cluster details
10 | """
11 | pass
12 |
13 |
14 | cluster.add_command(info)
15 |
--------------------------------------------------------------------------------
/virl/cli/cluster/info/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/cluster/info/__init__.py
--------------------------------------------------------------------------------
/virl/cli/cluster/info/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import cluster_list_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | def info():
10 | """
11 | display cluster configuration details
12 | """
13 |
14 | server = VIRLServer()
15 | client = get_cml_client(server)
16 | pl = None
17 |
18 | system_health = None
19 | try:
20 | system_health = client.get_system_health()
21 | except Exception as e:
22 | click.secho(f"Failed to get system health data: {e}", fg="red")
23 | exit(1)
24 |
25 | try:
26 | pl = ViewerPlugin(viewer="cluster")
27 | except NoPluginError:
28 | pass
29 |
30 | if pl:
31 | pl.visualize(computes=system_health["computes"])
32 | else:
33 | cluster_list_table(system_health["computes"])
34 |
--------------------------------------------------------------------------------
/virl/cli/cockpit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/cockpit/__init__.py
--------------------------------------------------------------------------------
/virl/cli/cockpit/commands.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 |
7 |
8 | @click.command()
9 | def cockpit():
10 | """
11 | opens the Cockpit UI
12 | """
13 | server = VIRLServer()
14 | url = "https://{}:9090".format(server.host)
15 | subprocess.Popen(["open", url])
16 |
--------------------------------------------------------------------------------
/virl/cli/command/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/command/__init__.py
--------------------------------------------------------------------------------
/virl/cli/command/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 | from virl2_client.models.cl_pyats import (ClPyats, PyatsDeviceNotFound,
5 | PyatsNotInstalled)
6 |
7 | from virl.api import VIRLServer
8 | from virl.helpers import (get_cml_client, get_current_lab,
9 | safe_join_existing_lab)
10 |
11 |
12 | @click.command()
13 | @click.argument("node", nargs=1)
14 | @click.argument("command", nargs=1)
15 | @click.option("--config/--no-config", default=False, show_default=False, help="Command is a configuration command (default: False)")
16 | def command(node, command, config, **kwargs):
17 | """
18 | send a command or config to a node (requires pyATS)
19 | """
20 | server = VIRLServer()
21 | client = get_cml_client(server)
22 |
23 | current_lab = get_current_lab()
24 | if current_lab:
25 | lab = safe_join_existing_lab(current_lab, client)
26 | if lab:
27 | pylab = None
28 | try:
29 | pylab = ClPyats(lab)
30 | except PyatsNotInstalled:
31 | click.secho("pyATS is not installed, run 'pip install pyats'", fg="red")
32 | exit(1)
33 |
34 | pyats_username = server.config.get("CML_DEVICE_USERNAME")
35 | pyats_password = server.config.get("CML_DEVICE_PASSWORD")
36 | pyats_auth_password = server.config.get("CML_DEVICE_ENABLE_PASSWORD")
37 |
38 | if pyats_username:
39 | os.environ["PYATS_USERNAME"] = pyats_username
40 | if pyats_password:
41 | os.environ["PYATS_PASSWORD"] = pyats_password
42 | if pyats_auth_password:
43 | os.environ["PYATS_AUTH_PASS"] = pyats_auth_password
44 |
45 | pylab.sync_testbed(server.user, server.passwd)
46 |
47 | try:
48 | result = ""
49 | if config:
50 | result = pylab.run_config_command(node, command)
51 | else:
52 | result = pylab.run_command(node, command)
53 |
54 | click.secho(result)
55 | except PyatsDeviceNotFound:
56 | click.secho("Node '{}' is not supported by pyATS".format(node), fg="yellow")
57 | except Exception as e:
58 | click.secho("Failed to run '{}' on '{}': {}".format(command, node, e))
59 | exit(1)
60 | else:
61 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
62 | exit(1)
63 | else:
64 | click.secho("No current lab set", fg="red")
65 | exit(1)
66 |
--------------------------------------------------------------------------------
/virl/cli/console/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/console/__init__.py
--------------------------------------------------------------------------------
/virl/cli/console/commands.py:
--------------------------------------------------------------------------------
1 | import platform
2 | from subprocess import call
3 |
4 | import click
5 | from virl2_client.exceptions import NodeNotFound
6 |
7 | from virl import helpers
8 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
9 | from virl.cli.views.console import console_table
10 | from virl.helpers import (get_cml_client, get_current_lab,
11 | safe_join_existing_lab)
12 |
13 |
14 | @click.command()
15 | @click.argument("node", nargs=1)
16 | @click.option("--display/--none", default="False", help="Display Console information")
17 | def console(node, display, **kwargs):
18 | """
19 | console for node
20 | """
21 | server = VIRLServer()
22 | client = get_cml_client(server)
23 | skip_types = ["external_connector", "unmanaged_switch"]
24 |
25 | current_lab = get_current_lab()
26 | if current_lab:
27 | lab = safe_join_existing_lab(current_lab, client)
28 | if lab:
29 | try:
30 | node_obj = lab.get_node_by_label(node)
31 | except NodeNotFound:
32 | click.secho("Node {} was not found in lab {}".format(node, current_lab), fg="red")
33 | exit(1)
34 |
35 | if node_obj.node_definition not in skip_types:
36 | if node_obj.is_active():
37 | if len(lab.id) == 6:
38 | # Old-style (CML 2.2) lab IDs; console uses lab_id/node_id
39 | console = "/{}/{}/0".format(lab.id, node_obj.id)
40 | else:
41 | # From CML 2.3, console uses lab_title/node_label
42 | console = "/{}/{}/0".format(lab.title, node_obj.label)
43 | if display:
44 | try:
45 | pl = ViewerPlugin(viewer="console")
46 | pl.visualize(consoles=[{"node": node, "console": console}])
47 | except NoPluginError:
48 | console_table([{"node": node, "console": console}])
49 | else:
50 | # use user specified ssh command
51 | if "CML_CONSOLE_COMMAND" in server.config:
52 | cmd = server.config["CML_CONSOLE_COMMAND"]
53 | cmd = cmd.format(host=server.host, user=server.user, console="open " + console)
54 | print("Calling user specified command: {}".format(cmd))
55 | exit(call(cmd.split()))
56 |
57 | # someone still uses windows
58 | elif platform.system() == "Windows":
59 | with helpers.disable_file_system_redirection():
60 | cmd = "ssh -t {}@{} open {}".format(server.user, server.host, console)
61 | exit(call(cmd.split()))
62 |
63 | # why is shit so complicated?
64 | else:
65 | cmd = "ssh -t {}@{} open {}".format(server.user, server.host, console)
66 | exit(call(cmd.split()))
67 | else:
68 | click.secho("Node {} is not active".format(node), fg="red")
69 | exit(1)
70 | else:
71 | click.secho("Node type {} does not support console connectivity".format(node_obj.node_definition), fg="yellow")
72 | else:
73 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
74 | exit(1)
75 | else:
76 | click.secho("No current lab set", fg="red")
77 | exit(1)
78 |
--------------------------------------------------------------------------------
/virl/cli/definitions/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.definitions.images import images
4 | from virl.cli.definitions.nodes import nodes
5 |
6 |
7 | @click.group()
8 | def definitions():
9 | """
10 | manage image and node definitions
11 | """
12 | pass
13 |
14 |
15 | definitions.add_command(nodes)
16 | definitions.add_command(images)
17 |
--------------------------------------------------------------------------------
/virl/cli/definitions/images/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.definitions.images.export.commands import export
4 | from virl.cli.definitions.images.iimport import iimport
5 | from virl.cli.definitions.images.ls.commands import ls
6 |
7 |
8 | @click.group()
9 | def images():
10 | """
11 | manage image definitions
12 | """
13 | pass
14 |
15 |
16 | images.add_command(ls)
17 | images.add_command(export)
18 | images.add_command(iimport, name="import")
19 |
--------------------------------------------------------------------------------
/virl/cli/definitions/images/export/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/images/export/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/images/export/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import get_cml_client
5 |
6 |
7 | @click.command()
8 | @click.argument("image", nargs=1)
9 | @click.option("-f", "--filename", required=False, metavar="", help="filename to save to")
10 | def export(image, filename):
11 | """
12 | export an image definition
13 | """
14 |
15 | server = VIRLServer()
16 | client = get_cml_client(server)
17 |
18 | if not filename:
19 | filename = image + ".yaml"
20 |
21 | defs = client.definitions
22 |
23 | try:
24 | idef = defs.download_image_definition(image)
25 | except Exception as e:
26 | click.secho("Failed to download image definition for {}: {}".format(image, e), fg="red")
27 | exit(1)
28 | else:
29 | with open(filename, "w") as fd:
30 | fd.write(idef)
31 |
--------------------------------------------------------------------------------
/virl/cli/definitions/images/iimport/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.definitions.images.iimport.definition.commands import definition
4 | from virl.cli.definitions.images.iimport.image_file.commands import image_file
5 |
6 |
7 | @click.group()
8 | def iimport():
9 | """
10 | import images and image definitions
11 | """
12 | pass
13 |
14 |
15 | iimport.add_command(image_file)
16 | iimport.add_command(definition)
17 |
--------------------------------------------------------------------------------
/virl/cli/definitions/images/iimport/definition/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/images/iimport/definition/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/images/iimport/definition/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.option("-f", "--filename", required=True, metavar="", help="path to the local image definition file")
11 | def definition(filename):
12 | """
13 | import an image definition
14 | """
15 |
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 |
19 | if not os.path.isfile(filename):
20 | click.secho("Image definition file {} does not exist or is not a file", fg="red")
21 | exit(1)
22 | else:
23 | defs = client.definitions
24 | contents = None
25 |
26 | with open(filename, "r") as fd:
27 | contents = fd.read()
28 |
29 | try:
30 | defs.upload_image_definition(contents)
31 | except Exception as e:
32 | click.secho("Failed to import image definition for {}: {}".format(filename, e), fg="red")
33 | exit(1)
34 |
--------------------------------------------------------------------------------
/virl/cli/definitions/images/iimport/image_file/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/images/iimport/image_file/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/images/iimport/image_file/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.option("-f", "--filename", required=True, metavar="", help="path to local image file")
11 | @click.option("--rename", required=False, metavar="", help="optional new name to give the file on the server")
12 | def image_file(filename, rename):
13 | """
14 | import an image file
15 | """
16 |
17 | server = VIRLServer()
18 | client = get_cml_client(server)
19 |
20 | if not os.path.isfile(filename):
21 | click.secho("Image file {} does not exist or is not a file", fg="red")
22 | exit(1)
23 | else:
24 | defs = client.definitions
25 | try:
26 | defs.upload_image_file(filename, rename)
27 | except Exception as e:
28 | click.secho("Failed to import image file {}: {}".format(filename, e), fg="red")
29 | exit(1)
30 |
--------------------------------------------------------------------------------
/virl/cli/definitions/images/ls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/images/ls/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/images/ls/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import image_list_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | @click.option("--image", default=None)
10 | def ls(**kwargs):
11 | """
12 | list all images or the details of a specific image
13 | """
14 |
15 | image = kwargs.get("image")
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 | pl = None
19 |
20 | # Regardless of the argument, we have to get all the flavors
21 | # In the case of no arg, we print them all.
22 | # In the case of an arg, we have to go back and get details.
23 | defs = client.definitions.image_definitions()
24 |
25 | try:
26 | pl = ViewerPlugin(viewer="image_def")
27 | except NoPluginError:
28 | pass
29 |
30 | if image:
31 | for f in list(defs):
32 | if f["name"] == image:
33 | if pl:
34 | pl.visualize(image_defs=[f])
35 | else:
36 | image_list_table([f])
37 | break
38 | else:
39 | if pl:
40 | pl.visualize(image_defs=defs)
41 | else:
42 | image_list_table(defs)
43 |
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.definitions.nodes.export.commands import export
4 | from virl.cli.definitions.nodes.ls.commands import ls
5 | from virl.cli.definitions.nodes.nimport.commands import nimport
6 |
7 |
8 | @click.group()
9 | def nodes():
10 | """
11 | manage node definitions
12 | """
13 | pass
14 |
15 |
16 | nodes.add_command(ls)
17 | nodes.add_command(export)
18 | nodes.add_command(nimport, name="import")
19 |
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/export/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/nodes/export/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/export/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import get_cml_client
5 |
6 |
7 | @click.command()
8 | @click.argument("node", nargs=1)
9 | @click.option("-f", "--filename", required=False, metavar="", help="filename to save to")
10 | def export(node, filename):
11 | """
12 | export a node definition
13 | """
14 |
15 | server = VIRLServer()
16 | client = get_cml_client(server)
17 |
18 | if not filename:
19 | filename = node + ".yaml"
20 |
21 | defs = client.definitions
22 |
23 | try:
24 | ndef = defs.download_node_definition(node)
25 | except Exception as e:
26 | click.secho("Failed to download node definition for {}: {}".format(node, e), fg="red")
27 | exit(1)
28 | else:
29 | with open(filename, "w") as fd:
30 | fd.write(ndef)
31 |
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/ls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/nodes/ls/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/ls/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import node_def_list_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | @click.option("--node", default=None)
10 | def ls(**kwargs):
11 | """
12 | list all node definitions or the details of a specific node definition
13 | """
14 |
15 | node = kwargs.get("node")
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 | pl = None
19 |
20 | # Regardless of the argument, we have to get all the node definitions
21 | # In the case of no arg, we print them all.
22 | # In the case of an arg, we have to go back and get details.
23 | defs_orig = client.definitions.node_definitions()
24 |
25 | # Create a new list of the *flattened* node definitions. CML 2.3 removed the
26 | # extra "data" layer of nesting in the node def JSON format. To make the rest
27 | # of the code work no matter which version of CML we're talking to, create a
28 | # list of node definitions, removing the extra layer of nesting if needed.
29 | defs = [f["data"] if "data" in f else f for f in defs_orig]
30 |
31 | try:
32 | pl = ViewerPlugin(viewer="node_def")
33 | except NoPluginError:
34 | pass
35 |
36 | if node:
37 | for f in list(defs):
38 | if f["id"] == node:
39 | if pl:
40 | pl.visualize(node_defs=[f])
41 | else:
42 | node_def_list_table([f])
43 | break
44 | else:
45 | if pl:
46 | pl.visualize(node_defs=defs)
47 | else:
48 | node_def_list_table(defs)
49 |
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/nimport/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/definitions/nodes/nimport/__init__.py
--------------------------------------------------------------------------------
/virl/cli/definitions/nodes/nimport/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.option("-f", "--filename", required=True, metavar="", help="path to the local node definition file")
11 | def nimport(filename):
12 | """
13 | import a node definition
14 | """
15 |
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 |
19 | if not os.path.isfile(filename):
20 | click.secho("Node definition file {} does not exist or is not a file", fg="red")
21 | exit(1)
22 | else:
23 | defs = client.definitions
24 | contents = None
25 |
26 | with open(filename, "r") as fd:
27 | contents = fd.read()
28 |
29 | try:
30 | defs.upload_node_definition(contents)
31 | except Exception as e:
32 | click.secho("Failed to import node definition: {}".format(e), fg="red")
33 | exit(1)
34 |
--------------------------------------------------------------------------------
/virl/cli/down/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/down/__init__.py
--------------------------------------------------------------------------------
/virl/cli/down/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import (get_cml_client, get_current_lab,
5 | safe_join_existing_lab,
6 | safe_join_existing_lab_by_title)
7 |
8 |
9 | @click.command()
10 | @click.option("--id", required=False, help="An existing lab ID to stop (lab-name is ignored)")
11 | @click.option("--lab-name", "-n", "--sim-name", required=False, help="An existing lab name to stop")
12 | def down(id=None, lab_name=None):
13 | """
14 | stop a lab
15 | """
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 |
19 | lab = None
20 |
21 | if id:
22 | lab = safe_join_existing_lab(id, client)
23 |
24 | if not lab and lab_name:
25 | lab = safe_join_existing_lab_by_title(lab_name, client)
26 |
27 | if not lab:
28 | lab_id = get_current_lab()
29 | if lab_id:
30 | lab = safe_join_existing_lab(lab_id, client)
31 |
32 | if lab:
33 | if lab.is_active():
34 | click.secho("Shutting down lab {} (ID: {}).....".format(lab.title, lab.id))
35 | lab.stop()
36 | click.echo(click.style("SUCCESS", fg="green"))
37 | else:
38 | click.secho("Lab with ID {} and title {} is already stopped".format(lab.id, lab.title))
39 |
40 | else:
41 | click.secho("Failed to find lab on server", fg="red")
42 | exit(1)
43 |
--------------------------------------------------------------------------------
/virl/cli/extract/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/extract/__init__.py
--------------------------------------------------------------------------------
/virl/cli/extract/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import (cache_lab, extract_configurations, get_cml_client,
5 | get_current_lab, safe_join_existing_lab)
6 |
7 |
8 | @click.command()
9 | @click.option("--update-cache/--no-update-cache", default=True, help="update the local cache (default: True)")
10 | def extract(update_cache, **kwargs):
11 | """
12 | extract configurations from all nodes in a lab
13 | """
14 | server = VIRLServer()
15 | client = get_cml_client(server)
16 |
17 | current_lab = get_current_lab()
18 | if current_lab:
19 | lab = safe_join_existing_lab(current_lab, client)
20 | if lab:
21 | extract_configurations(lab)
22 |
23 | if update_cache:
24 | cache_lab(lab, force=True)
25 | else:
26 | click.secho("Failed to find running lab {}".format(current_lab), fg="red")
27 | exit(1)
28 | else:
29 | click.secho("Current lab is not set", fg="red")
30 | exit(1)
31 |
--------------------------------------------------------------------------------
/virl/cli/generate/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, check_valid_plugin, plugin
4 | from virl.cli.generate.ansible.commands import ansible
5 | from virl.cli.generate.nso.commands import nso
6 | from virl.cli.generate.pyats.commands import pyats
7 |
8 |
9 | @click.group()
10 | def generate():
11 | """
12 | generate inv file for various tools
13 | """
14 | pass
15 |
16 |
17 | def init_generators():
18 | generate.add_command(ansible)
19 | generate.add_command(pyats)
20 | generate.add_command(nso)
21 |
22 | for gen in plugin.Plugin.get_plugins("generator"):
23 | try:
24 | pl = plugin.GeneratorPlugin(generator=gen)
25 | except NoPluginError:
26 | continue
27 | if not check_valid_plugin(pl, pl.generate, "generate"):
28 | click.secho(
29 | "ERROR: Malformed plugin for generator {}. The `generate` method must be static and a click.command".format(gen), fg="red"
30 | )
31 | plugin.Plugin.remove_plugin("generator", gen)
32 | else:
33 | generate.add_command(pl.generate, name=gen)
34 |
--------------------------------------------------------------------------------
/virl/cli/generate/ansible/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/generate/ansible/__init__.py
--------------------------------------------------------------------------------
/virl/cli/generate/ansible/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.generators import ansible_inventory_generator
5 | from virl.helpers import (get_cml_client, get_current_lab,
6 | safe_join_existing_lab)
7 |
8 |
9 | @click.command()
10 | @click.option("--output", "-o", help="output File name ")
11 | @click.option("--style", help="output format (default is yaml)", type=click.Choice(["ini", "yaml"]))
12 | def ansible(**kwargs):
13 | """
14 | generate ansible inventory
15 | """
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 |
19 | current_lab = get_current_lab()
20 | if current_lab:
21 | lab = safe_join_existing_lab(current_lab, client)
22 | if lab:
23 | if kwargs.get("output"):
24 | file_name = kwargs.get("output")
25 | elif kwargs.get("style") == "ini":
26 | file_name = "{}_inventory.ini".format(lab.id)
27 | else:
28 | file_name = "{}_inventory.yaml".format(lab.id)
29 |
30 | inv = None
31 |
32 | if kwargs.get("style") == "ini":
33 | inv = ansible_inventory_generator(lab, server, style="ini")
34 | else:
35 | inv = ansible_inventory_generator(lab, server)
36 |
37 | if inv:
38 | click.secho("Writing {}".format(file_name))
39 | with open(file_name, "w") as fd:
40 | fd.write(inv)
41 | else:
42 | click.secho("Failed to get inventory data", fg="red")
43 | exit(1)
44 | else:
45 | click.secho("Failed to find running lab {}".format(current_lab), fg="red")
46 | exit(1)
47 | else:
48 | click.secho("Current lab is not set", fg="red")
49 | exit(1)
50 |
--------------------------------------------------------------------------------
/virl/cli/generate/nso/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/generate/nso/__init__.py
--------------------------------------------------------------------------------
/virl/cli/generate/nso/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.api.nso import NSO
5 | from virl.cli.views import sync_table
6 | from virl.generators import nso_payload_generator
7 | from virl.helpers import (get_cml_client, get_current_lab,
8 | safe_join_existing_lab)
9 |
10 |
11 | @click.command()
12 | @click.option("--output", "-o", help="just dump the payload to file without sending")
13 | @click.option("--syncfrom/--no-syncfrom", default=False, help="Perform sync-from after updating devices")
14 | def nso(syncfrom, **kwargs):
15 | """
16 | generate nso inventory
17 | """
18 |
19 | server = VIRLServer()
20 | client = get_cml_client(server)
21 |
22 | current_lab = get_current_lab()
23 | if current_lab:
24 | lab = safe_join_existing_lab(current_lab, client)
25 | if lab:
26 | if kwargs.get("output"):
27 | file_name = kwargs.get("output")
28 | else:
29 | file_name = None
30 |
31 | inv = nso_payload_generator(lab, server)
32 |
33 | if inv:
34 | if file_name:
35 | click.secho("Writing {}".format(file_name))
36 | with open(file_name, "w") as fd:
37 | fd.write(inv)
38 | else:
39 | click.secho("Updating NSO....")
40 | nso_obj = NSO()
41 | nso_response = nso_obj.update_devices(inv)
42 | if nso_response.ok:
43 | click.secho("Successfully added CML devices to NSO")
44 | else:
45 | click.secho("Error updating NSO: ", fg="red")
46 | click.secho(nso_response.text)
47 | if syncfrom:
48 | resp = nso_obj.perform_sync_from()
49 | sync_table(resp.json())
50 | else:
51 | click.secho("Failed to get inventory data", fg="red")
52 | exit(1)
53 | else:
54 | click.secho("Failed to find running lab {}".format(current_lab), fg="red")
55 | exit(1)
56 | else:
57 | click.secho("Current lab is not set", fg="red")
58 | exit(1)
59 |
--------------------------------------------------------------------------------
/virl/cli/generate/pyats/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/generate/pyats/__init__.py
--------------------------------------------------------------------------------
/virl/cli/generate/pyats/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.generators import pyats_testbed_generator
5 | from virl.helpers import (get_cml_client, get_current_lab,
6 | safe_join_existing_lab)
7 |
8 |
9 | @click.command()
10 | @click.option("--output", "-o", help="output File name")
11 | def pyats(**kwargs):
12 | """
13 | generates a pyats testbed config for a lab
14 | """
15 | server = VIRLServer()
16 | client = get_cml_client(server)
17 |
18 | current_lab = get_current_lab()
19 | if current_lab:
20 | lab = safe_join_existing_lab(current_lab, client)
21 | if lab:
22 | if kwargs.get("output"):
23 | # user specified output filename
24 | file_name = kwargs.get("output")
25 | else:
26 | # writes to _testbed.yaml by default
27 | file_name = "{}_testbed.yaml".format(lab.id)
28 |
29 | testbed = pyats_testbed_generator(lab)
30 |
31 | if testbed:
32 | click.secho("Writing {}".format(file_name))
33 | with open(file_name, "w") as fd:
34 | fd.write(testbed)
35 | else:
36 | click.secho("Failed to get testbed data", fg="red")
37 | exit(1)
38 | else:
39 | click.secho("Failed to find running lab {}".format(current_lab), fg="red")
40 | exit(1)
41 | else:
42 | click.secho("Current lab is not set", fg="red")
43 | exit(1)
44 |
--------------------------------------------------------------------------------
/virl/cli/groups/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.groups.create.commands import create_groups
4 | from virl.cli.groups.delete.commands import delete_groups
5 | from virl.cli.groups.ls.commands import list_groups
6 | from virl.cli.groups.update.commands import update_groups
7 |
8 |
9 | @click.group()
10 | def groups():
11 | """
12 | manage groups
13 | """
14 | pass
15 |
16 |
17 | groups.add_command(list_groups, name="ls")
18 | groups.add_command(create_groups, name="create")
19 | groups.add_command(update_groups, name="update")
20 | groups.add_command(delete_groups, name="delete")
21 |
--------------------------------------------------------------------------------
/virl/cli/groups/create/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/groups/create/__init__.py
--------------------------------------------------------------------------------
/virl/cli/groups/create/commands.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.option("--member", help="Assign one or more users to the groups (e.g., --member user1 --member user2)", multiple=True)
11 | @click.option("--add-all-users", help="Assign all users to the groups", is_flag=True)
12 | @click.option(
13 | "--lab",
14 | type=(str, click.Choice(["read_only", "read_write"])),
15 | help="Labs to assign the groups (e.g, --labs lab_id1 read_only --labs lab_id2 read_write)",
16 | default=[],
17 | multiple=True,
18 | metavar="lab_id [read_only|read_write]",
19 | )
20 | @click.option(
21 | "--add-all-labs",
22 | type=click.Choice(["read_only", "read_write"]),
23 | help="Assign all labs to the groups with either read_only or read_write permissions",
24 | )
25 | @click.argument("groupnames", nargs=-1, required=True)
26 | def create_groups(groupnames, member, add_all_users, lab, add_all_labs):
27 | """
28 | Create one or more groups (e.g., group1 group2)
29 | """
30 |
31 | server = VIRLServer()
32 | client = get_cml_client(server)
33 |
34 | all_users = client.user_management.users()
35 | all_users_ids = [u["id"] for u in all_users]
36 | members_ids = all_users_ids if add_all_users else [u["id"] for u in all_users if u["username"] in member]
37 |
38 | lab_ids = [{"id": lab_id, "permission": permission} for lab_id, permission in lab]
39 | lab_ids = None if add_all_labs is None else [{"id": lid, "permission": add_all_labs} for lid in client.get_lab_list()]
40 |
41 | for name in groupnames:
42 | kwargs = {
43 | "name": name,
44 | "members": members_ids,
45 | "labs": lab_ids,
46 | }
47 | try:
48 | client.group_management.create_group(**kwargs)
49 | click.secho(f"Group {name} successfully created", fg="green")
50 | except Exception as e:
51 | click.secho(f"Failed to create group: {e}", fg="red")
52 | sys.exit(1)
53 |
--------------------------------------------------------------------------------
/virl/cli/groups/delete/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/groups/delete/__init__.py
--------------------------------------------------------------------------------
/virl/cli/groups/delete/commands.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.argument("groupnames", nargs=-1, required=True)
11 | def delete_groups(groupnames):
12 | """
13 | Delete one or more groups (e.g., group1 group2)
14 | """
15 |
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 | group_mapping = {g["name"]: g["id"] for g in client.group_management.groups()}
19 |
20 | for groupname in groupnames:
21 | try:
22 | group_id = group_mapping[groupname]
23 | client.group_management.delete_group(group_id)
24 | click.secho(f"Group {groupname} successfully deleted", fg="green")
25 | except KeyError:
26 | click.secho(f"Group {groupname} not found", fg="red")
27 | sys.exit(1)
28 | except Exception as e:
29 | click.secho(f"Failed to delete group: {e}", fg="red")
30 | sys.exit(1)
31 |
--------------------------------------------------------------------------------
/virl/cli/groups/ls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/groups/ls/__init__.py
--------------------------------------------------------------------------------
/virl/cli/groups/ls/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import group_list_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | @click.option("-v", "--verbose", is_flag=True, help="Include user IDs in the output")
10 | def list_groups(verbose):
11 | """
12 | List all groups on the server
13 | """
14 | server = VIRLServer()
15 | client = get_cml_client(server)
16 | user_mapping = {u["id"]: u["username"] for u in client.user_management.users()}
17 | labs_mapping = {lab.id: lab.title for lab in client.all_labs(show_all=True)}
18 | groups = client.group_management.groups()
19 | for group in groups:
20 | group["members"] = [user_mapping[uid] for uid in group["members"]]
21 | group["labs"] = [{"title": labs_mapping[lab["id"]], "permission": lab["permission"]} for lab in group["labs"]]
22 | try:
23 | pl = ViewerPlugin(viewer="group")
24 | pl.visualize(groups=groups)
25 | except NoPluginError:
26 | group_list_table(groups, verbose=verbose)
27 |
--------------------------------------------------------------------------------
/virl/cli/groups/update/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/groups/update/__init__.py
--------------------------------------------------------------------------------
/virl/cli/groups/update/commands.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.option("--member", help="Assign one or more users to the groups (e.g., --member user1 --member user2)", multiple=True)
11 | @click.option("--add-all-users", help="Assign all users to the groups", is_flag=True)
12 | @click.option(
13 | "--lab",
14 | type=(str, click.Choice(["read_only", "read_write"])),
15 | help="Labs to assign the groups",
16 | default=[],
17 | multiple=True,
18 | metavar="lab_id [read_only|read_write]",
19 | )
20 | @click.option(
21 | "--add-all-labs",
22 | type=click.Choice(["read_only", "read_write"]),
23 | help="Assign all labs to the groups with either read_only or read_write permissions",
24 | )
25 | @click.argument("groupnames", nargs=-1, required=True)
26 | def update_groups(groupnames, member, add_all_users, lab, add_all_labs):
27 | """
28 | Update one or more groups (e.g., group1 group2)
29 | """
30 |
31 | server = VIRLServer()
32 | client = get_cml_client(server)
33 |
34 | all_users = client.user_management.users()
35 | all_users_ids = [u["id"] for u in all_users]
36 | members_ids = all_users_ids if add_all_users else [u["id"] for u in all_users if u["username"] in member]
37 | members_ids = members_ids if members_ids else None
38 |
39 | lab_ids = [{"id": lab_id, "permission": permission} for lab_id, permission in lab]
40 | lab_ids = None if add_all_labs is None else [{"id": lid, "permission": add_all_labs} for lid in client.get_lab_list()]
41 | lab_ids = lab_ids if lab_ids else None
42 |
43 | groups = client.group_management.groups()
44 | group_mapping = {group["name"]: group["id"] for group in groups}
45 | for name in groupnames:
46 | group_id = group_mapping[name]
47 | kwargs = {
48 | "group_id": group_id,
49 | "members": members_ids,
50 | "labs": lab_ids,
51 | }
52 | # only pass kwargs that are not None
53 | kwargs = {k: v for k, v in kwargs.items() if v is not None}
54 | try:
55 | client.group_management.update_group(**kwargs)
56 | click.secho(f"Group {name} successfully updated", fg="green")
57 | except Exception as e:
58 | click.secho(f"Failed to update group: {e}", fg="red")
59 | sys.exit(1)
60 |
--------------------------------------------------------------------------------
/virl/cli/id/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/id/__init__.py
--------------------------------------------------------------------------------
/virl/cli/id/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import CachedLab, VIRLServer
4 | from virl.helpers import (get_cml_client, get_current_lab,
5 | get_current_lab_link, safe_join_existing_lab)
6 |
7 |
8 | @click.command()
9 | def lid():
10 | """
11 | get the current lab title and ID
12 | """
13 | server = VIRLServer()
14 | client = get_cml_client(server)
15 | current_lab = get_current_lab()
16 | if current_lab:
17 | lab = safe_join_existing_lab(current_lab, client)
18 | # The lab really should be on the server.
19 | if not lab:
20 | try:
21 | lab = CachedLab(current_lab, get_current_lab_link())
22 | except Exception:
23 | pass
24 |
25 | if lab:
26 | click.echo("{} (ID: {})".format(lab.title, current_lab))
27 | else:
28 | click.secho("Current lab is set to {}, but is not on server or in cache!".format(current_lab), fg="red")
29 |
--------------------------------------------------------------------------------
/virl/cli/license/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.license.deregister.commands import deregister as deregisterc
4 | from virl.cli.license.features import features as featuresc
5 | from virl.cli.license.register.commands import register as registerc
6 | from virl.cli.license.renew import renew as renewc
7 | from virl.cli.license.show.commands import show as showc
8 |
9 |
10 | @click.group()
11 | def license():
12 | """
13 | work with product licensing
14 | """
15 | pass
16 |
17 |
18 | license.add_command(showc, name="show")
19 | license.add_command(registerc, name="register")
20 | license.add_command(renewc, name="renew")
21 | license.add_command(deregisterc, name="deregister")
22 | license.add_command(featuresc, name="features")
23 |
--------------------------------------------------------------------------------
/virl/cli/license/deregister/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/deregister/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/deregister/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import get_cml_client
5 |
6 |
7 | @click.command()
8 | @click.option(
9 | "--confirm/--no-confirm",
10 | show_default=False,
11 | default=True,
12 | help="Do not prompt for confirmation (default: prompt)",
13 | required=False,
14 | )
15 | def deregister(confirm):
16 | """
17 | deregister the Smart License
18 | """
19 | server = VIRLServer()
20 | client = get_cml_client(server)
21 | licensing = client.licensing
22 |
23 | ret = "y"
24 | if confirm:
25 | ret = input("Are you sure you want to deregister [y/N]? ")
26 | if not ret.lower().startswith("y"):
27 | click.secho("Not deregistering")
28 | exit(0)
29 |
30 | try:
31 | licensing.deregister()
32 | except Exception as e:
33 | click.secho("Failed to deregister with Smart Licensing: {}".format(e), fg="red")
34 | exit(1)
35 |
--------------------------------------------------------------------------------
/virl/cli/license/features/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.license.features.show.commands import show as showc
4 | from virl.cli.license.features.update.commands import update as updatec
5 |
6 |
7 | @click.group()
8 | def features():
9 | """
10 | work with licensed features
11 | """
12 | pass
13 |
14 |
15 | features.add_command(showc, name="show")
16 | features.add_command(updatec, name="update")
17 |
--------------------------------------------------------------------------------
/virl/cli/license/features/show/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/features/show/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/features/show/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import license_features_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | def show():
10 | """
11 | display license details
12 | """
13 | server = VIRLServer()
14 | client = get_cml_client(server)
15 | licensing = client.licensing
16 |
17 | try:
18 | license = licensing.features()
19 | except Exception as e:
20 | click.secho("Failed to get license features: {}".format(e), fg="red")
21 | exit(1)
22 | else:
23 | try:
24 | pl = ViewerPlugin(viewer="license_feature")
25 | pl.visualize(features=license)
26 | except NoPluginError:
27 | license_features_table(license)
28 |
--------------------------------------------------------------------------------
/virl/cli/license/features/update/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/features/update/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/features/update/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import get_cml_client
5 |
6 |
7 | @click.command()
8 | @click.option(
9 | "--id",
10 | "-i",
11 | required=True,
12 | help="ID for the Smart License feature to modify",
13 | )
14 | @click.option("--value", "-v", required=True, type=int, help="Number of licenses of this feature to use")
15 | def update(id, value):
16 | """
17 | update the number of feature instances
18 | """
19 | server = VIRLServer()
20 | client = get_cml_client(server)
21 | licensing = client.licensing
22 |
23 | try:
24 | licensing.update_features({id: value})
25 | except Exception as e:
26 | click.secho("Failed to update features: {}".format(e), fg="red")
27 | exit(1)
28 |
--------------------------------------------------------------------------------
/virl/cli/license/register/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/register/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/register/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.option(
11 | "--token",
12 | "-t",
13 | required=True,
14 | help="Smart License token for registration",
15 | )
16 | @click.option(
17 | "--reregister/--no-reregister",
18 | "--force/--no-force",
19 | required=False,
20 | default=False,
21 | help="Force registration even if already registered",
22 | )
23 | @click.option("--smart-license-server", "-s", required=False, help="URL for the Smart License server or satellite")
24 | @click.option("--proxy-host", "-p", required=False, help="Hostname or IP address of proxy to use for registration (if required)")
25 | @click.option("--proxy-port", "-o", required=False, default=80, help="Port number to use for proxy host (default: 80)")
26 | @click.option("--certificate", "-c", required=False, help="Path to a PEM-encoded certificate for the Smart License SSMS")
27 | def register(token, **kwargs):
28 | """
29 | register with a Smart License account
30 | """
31 | ssms = kwargs["smart_license_server"]
32 | proxy = kwargs["proxy_host"]
33 | port = None
34 | cert = kwargs["certificate"]
35 | reregister = kwargs["reregister"]
36 | server = VIRLServer()
37 | client = get_cml_client(server)
38 | licensing = client.licensing
39 |
40 | if ssms or proxy:
41 | if not ssms:
42 | ssms = licensing.status()["transport"]["default_ssms"]
43 | if proxy:
44 | port = kwargs["proxy_port"]
45 |
46 | try:
47 | licensing.set_transport(ssms, proxy, port)
48 | except Exception as e:
49 | click.secho("Failed to configure Smart License server and proxy: {}".format(e), fg="red")
50 | exit(1)
51 | else:
52 | try:
53 | licensing.delete_certificate()
54 | except Exception:
55 | pass
56 |
57 | licensing.set_default_transport()
58 |
59 | if cert:
60 | if not os.path.isfile(cert):
61 | click.secho("Certificate {} is not a valid file!".format(cert), fg="red")
62 | exit(1)
63 |
64 | with open(cert, "r") as fd:
65 | contents = fd.read()
66 | try:
67 | licensing.upload_certificate(contents)
68 | except Exception as e:
69 | click.secho("Failed to upload certificate {}: {}".format(cert, e), fg="red")
70 | exit(1)
71 |
72 | try:
73 | licensing.register(token, reregister)
74 | except Exception as e:
75 | click.secho("Failed to register with Smart Licensing: {}".format(e), fg="red")
76 | exit(1)
77 |
--------------------------------------------------------------------------------
/virl/cli/license/renew/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.license.renew.authorization.commands import \
4 | authorization as authorizationc
5 | from virl.cli.license.renew.registration.commands import \
6 | registration as registrationc
7 |
8 |
9 | @click.group()
10 | def renew():
11 | """
12 | renew registration or authorization
13 | """
14 | pass
15 |
16 |
17 | renew.add_command(registrationc, name="registration")
18 | renew.add_command(authorizationc, name="authorization")
19 |
--------------------------------------------------------------------------------
/virl/cli/license/renew/authorization/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/renew/authorization/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/renew/authorization/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import get_cml_client
5 |
6 |
7 | @click.command()
8 | def authorization():
9 | """
10 | renew Smart License authorization
11 | """
12 | server = VIRLServer()
13 | client = get_cml_client(server)
14 | licensing = client.licensing
15 |
16 | try:
17 | licensing.renew_authorization()
18 | except Exception as e:
19 | click.secho("Failed to renew authorization: {}".format(e), fg="red")
20 | exit(1)
21 |
--------------------------------------------------------------------------------
/virl/cli/license/renew/registration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/renew/registration/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/renew/registration/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import get_cml_client
5 |
6 |
7 | @click.command()
8 | def registration():
9 | """
10 | renew Smart License registration
11 | """
12 | server = VIRLServer()
13 | client = get_cml_client(server)
14 | licensing = client.licensing
15 |
16 | try:
17 | licensing.register_renew()
18 | except Exception as e:
19 | click.secho("Failed to renew registration: {}".format(e), fg="red")
20 | exit(1)
21 |
--------------------------------------------------------------------------------
/virl/cli/license/show/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/license/show/__init__.py
--------------------------------------------------------------------------------
/virl/cli/license/show/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import license_details_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | def show():
10 | """
11 | display license details
12 | """
13 | server = VIRLServer()
14 | client = get_cml_client(server)
15 | licensing = client.licensing
16 |
17 | try:
18 | license = licensing.status()
19 | except Exception as e:
20 | click.secho("Failed to get license details: {}".format(e), fg="red")
21 | exit(1)
22 | else:
23 | try:
24 | pl = ViewerPlugin(viewer="license")
25 | pl.visualize(license=license)
26 | except NoPluginError:
27 | license_details_table(license)
28 |
--------------------------------------------------------------------------------
/virl/cli/ls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/ls/__init__.py
--------------------------------------------------------------------------------
/virl/cli/ls/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 |
5 | from virl.api import CachedLab, NoPluginError, ViewerPlugin, VIRLServer
6 | from virl.cli.views import lab_list_table
7 | from virl.helpers import get_cache_root, get_cml_client
8 |
9 |
10 | @click.command()
11 | @click.option(
12 | "--all/--server",
13 | default=False,
14 | show_default=False,
15 | required=False,
16 | help="Display cached labs in addition to those on the server (default: server labs only)",
17 | )
18 | @click.option(
19 | "--all-users/--only-me",
20 | default=False,
21 | show_default=False,
22 | required=False,
23 | help="Display labs for all users (only if current user is an admin) (default: only show labs owned by me)",
24 | )
25 | def ls(all, all_users):
26 | """
27 | lists running labs and optionally those in the cache
28 | """
29 | server = VIRLServer()
30 | client = get_cml_client(server)
31 | labs = []
32 | cached_labs = None
33 | users = client.user_management.users()
34 | ownerids_usernames = {u["id"]: u["username"] for u in users}
35 |
36 | lab_ids = client.get_lab_list(all_users)
37 | for id in lab_ids:
38 | labs.append(client.join_existing_lab(id))
39 |
40 | if all:
41 | cached_labs = []
42 | cache_root = get_cache_root()
43 | if os.path.isdir(cache_root):
44 | for f in os.listdir(cache_root):
45 | lab_id = f
46 | cached_labs.append(CachedLab(lab_id, cache_root + "/" + f))
47 |
48 | try:
49 | pl = ViewerPlugin(viewer="lab")
50 | pl.visualize(labs=labs, ownerids_usernames=ownerids_usernames, cached_labs=cached_labs)
51 | except NoPluginError:
52 | lab_list_table(labs, ownerids_usernames, cached_labs=cached_labs)
53 |
--------------------------------------------------------------------------------
/virl/cli/nodes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/nodes/__init__.py
--------------------------------------------------------------------------------
/virl/cli/nodes/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import node_list_table
5 | from virl.helpers import (get_cml_client, get_current_lab,
6 | safe_join_existing_lab)
7 |
8 |
9 | @click.command()
10 | def nodes():
11 | """
12 | get node list for the current lab
13 | """
14 | server = VIRLServer()
15 | client = get_cml_client(server)
16 |
17 | current_lab = get_current_lab()
18 | if current_lab:
19 | lab = safe_join_existing_lab(current_lab, client)
20 | if lab:
21 | # Force an operational sync.
22 | try:
23 | lab.sync_operational_if_outdated()
24 | except Exception:
25 | pass
26 |
27 | computes = {}
28 | try:
29 | computes = client.get_system_health()["computes"]
30 | except Exception:
31 | pass
32 |
33 | try:
34 | pl = ViewerPlugin(viewer="node")
35 | pl.visualize(nodes=lab.nodes(), computes=computes)
36 | except NoPluginError:
37 | node_list_table(lab.nodes(), computes)
38 | else:
39 | click.secho("Lab {} is not running".format(current_lab), fg="red")
40 | exit(1)
41 | else:
42 | click.secho("No current lab selected", fg="red")
43 | exit(1)
44 |
--------------------------------------------------------------------------------
/virl/cli/pull/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/pull/__init__.py
--------------------------------------------------------------------------------
/virl/cli/pull/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 | import requests
3 |
4 |
5 | def do_pull(repo, fname, branch="master", recurse=False):
6 | click.secho("Pulling {} from {} on branch {}".format(fname, repo, branch))
7 | url = "https://raw.githubusercontent.com/"
8 | url = url + "{}/{}/{}".format(repo, branch, fname)
9 | resp = requests.get(url)
10 | if resp.ok:
11 | with open(fname, "w") as fh:
12 | fh.write(resp.text)
13 | click.secho("Saved topology as {}".format(fname), fg="green")
14 | return True
15 | else:
16 | click.secho("Error pulling {} from {} on branch {} - repo, file, or branch not found".format(fname, repo, branch), fg="red")
17 | return False
18 |
19 |
20 | @click.command()
21 | @click.argument("repo")
22 | @click.option("--file", default="topology.yaml", required=False, help="Filename to pull (default: topology.yaml)")
23 | @click.option("--branch", default="main", required=False, help="Branch name from which to pull (default: main)")
24 | def pull(repo, file, branch):
25 | """
26 | pull CML lab YAML file from repo
27 | """
28 | ret = do_pull(repo, fname=file, branch=branch)
29 | if not ret:
30 | exit(1)
31 |
--------------------------------------------------------------------------------
/virl/cli/rm/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/rm/__init__.py
--------------------------------------------------------------------------------
/virl/cli/rm/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import (check_lab_cache, clear_current_lab, get_cml_client,
7 | get_current_lab, safe_join_existing_lab)
8 |
9 |
10 | @click.command()
11 | @click.option(
12 | "--force/--no-force",
13 | "-f",
14 | default=False,
15 | required=False,
16 | help="Stop and/or wipe a lab (if it's started) then remove it (default: False)",
17 | )
18 | @click.option(
19 | "--confirm/--no-confirm",
20 | show_default=False,
21 | default=True,
22 | help="Do not prompt for confirmation (default: prompt)",
23 | required=False,
24 | )
25 | @click.option(
26 | "--from-cache/--no-from-cache",
27 | default=False,
28 | required=False,
29 | show_default=False,
30 | help="Remove the lab from the cache (default: do not remove from cache)",
31 | )
32 | def rm(force, confirm, from_cache):
33 | """
34 | remove a lab
35 | """
36 | server = VIRLServer()
37 | client = get_cml_client(server)
38 |
39 | current_lab = get_current_lab()
40 | if current_lab:
41 | lab = safe_join_existing_lab(current_lab, client)
42 | if lab:
43 | if lab.is_active() and force:
44 | lab.stop(wait=True)
45 |
46 | if lab.state() != "DEFINED_ON_CORE" and force:
47 | lab.wipe(wait=True)
48 |
49 | # Check again just to be sure.
50 | if lab.state() == "DEFINED_ON_CORE":
51 | ret = "y"
52 | if confirm:
53 | ret = input("Are you sure you want to remove lab {} (ID: {}) [y/N]? ".format(lab.title, current_lab))
54 | if ret.lower().startswith("y"):
55 | # We need to save the lab's title before we remove it.
56 | title = lab.title
57 | lab.remove()
58 | click.secho("Lab {} (ID: {}) removed".format(title, current_lab))
59 | if from_cache:
60 | try:
61 | os.remove(check_lab_cache(current_lab))
62 | except OSError:
63 | # File doesn't exist.
64 | pass
65 |
66 | click.secho("Removed lab {} from cache".format(current_lab))
67 | clear_current_lab()
68 | else:
69 | click.secho("Not removing lab {} (ID: {})".format(lab.title, current_lab))
70 |
71 | else:
72 | click.secho(
73 | "Lab {} (ID: {}) is either active or not wiped; either down and wipe it or use --force".format(lab.title, current_lab),
74 | fg="red",
75 | )
76 | exit(1)
77 | else:
78 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
79 | exit(1)
80 | else:
81 | click.secho("Current lab is not set", fg="red")
82 | exit(1)
83 |
--------------------------------------------------------------------------------
/virl/cli/save/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/save/__init__.py
--------------------------------------------------------------------------------
/virl/cli/save/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import (extract_configurations, get_cml_client,
5 | get_current_lab, safe_join_existing_lab)
6 |
7 |
8 | @click.command()
9 | @click.option("--extract/--no-extract", default=True, help="extract the configurations from devices before export (default: True)")
10 | @click.option(
11 | "-f", "--filename", required=False, default="topology.yaml", metavar="", help="filename to save to, defaults to topology.yaml"
12 | )
13 | def save(extract, filename, **kwargs):
14 | """
15 | save lab to a local yaml file
16 | """
17 | server = VIRLServer()
18 | client = get_cml_client(server)
19 |
20 | current_lab = get_current_lab()
21 | if current_lab:
22 | lab = safe_join_existing_lab(current_lab, client)
23 | if lab:
24 | if extract:
25 | click.secho("Extracting configurations...")
26 | extract_configurations(lab)
27 |
28 | lab_export = lab.download()
29 |
30 | click.secho("Writing {}".format(filename))
31 | with open(filename, "w") as fd:
32 | fd.write(lab_export)
33 | else:
34 | click.secho("Failed to find running lab {}".format(current_lab), fg="red")
35 | exit(1)
36 | else:
37 | click.secho("Current lab is not set", fg="red")
38 | exit(1)
39 |
--------------------------------------------------------------------------------
/virl/cli/search/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/search/__init__.py
--------------------------------------------------------------------------------
/virl/cli/search/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin
4 | from virl.api.github import get_repos
5 | from virl.cli.views.search import repo_table
6 |
7 |
8 | @click.command()
9 | @click.argument("query", required=False)
10 | @click.option("--org", default="virlfiles", required=False, help="GitHub organization to search (default: virlfiles)")
11 | def search(query=None, **kwargs):
12 | """
13 | list topologies available via github
14 | """
15 |
16 | repos = get_repos(org=kwargs["org"], query=query)
17 | if query is not None:
18 | click.secho("Displaying {} Results For {}".format(len(repos), query))
19 | else:
20 | click.secho("Displaying {} Results".format(len(repos)))
21 | try:
22 | pl = ViewerPlugin(viewer="search")
23 | pl.visualize(repos=repos)
24 | except NoPluginError:
25 | repo_table(repos)
26 |
--------------------------------------------------------------------------------
/virl/cli/ssh/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/ssh/__init__.py
--------------------------------------------------------------------------------
/virl/cli/ssh/commands.py:
--------------------------------------------------------------------------------
1 | from subprocess import call
2 |
3 | import click
4 | from virl2_client.exceptions import NodeNotFound
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import (get_cml_client, get_current_lab, get_node_mgmt_ip,
8 | safe_join_existing_lab)
9 |
10 |
11 | @click.command()
12 | @click.argument("node", nargs=1)
13 | def ssh(node):
14 | """
15 | ssh to a node
16 | """
17 | server = VIRLServer()
18 | client = get_cml_client(server)
19 | username = server.config.get("VIRL_SSH_USERNAME", "cisco")
20 |
21 | current_lab = get_current_lab()
22 | if current_lab:
23 | lab = safe_join_existing_lab(current_lab, client)
24 | if lab:
25 | try:
26 | node_obj = lab.get_node_by_label(node)
27 | except NodeNotFound:
28 | click.secho("Node {} was not found in lab {}".format(node, current_lab), fg="red")
29 | exit(1)
30 |
31 | if node_obj.is_active():
32 | mgmtip = get_node_mgmt_ip(node_obj)
33 | if mgmtip:
34 | if "VIRL_SSH_COMMAND" in server.config:
35 | cmd = server.config["VIRL_SSH_COMMAND"]
36 | cmd = cmd.format(host=mgmtip, username=username)
37 | print("Calling user specified command: {}".format(cmd))
38 | exit(call(cmd.split()))
39 | else:
40 | click.secho("Attemping ssh connection to {} at {}".format(node_obj.label, mgmtip))
41 |
42 | exit(call(["ssh", "{}@{}".format(username, mgmtip)]))
43 | else:
44 | click.secho("Node {} does not have an external management IP".format(node_obj.label))
45 | else:
46 | click.secho("Node {} is not active".format(node_obj.label), fg="yellow")
47 | else:
48 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
49 | exit(1)
50 | else:
51 | click.secho("No current lab set", fg="red")
52 | exit(1)
53 |
--------------------------------------------------------------------------------
/virl/cli/start/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/start/__init__.py
--------------------------------------------------------------------------------
/virl/cli/start/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 | from subprocess import call
3 | from virl2_client.exceptions import NodeNotFound
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client, get_current_lab, safe_join_existing_lab, get_command
7 |
8 |
9 | @click.command()
10 | @click.argument("node", required=False)
11 | @click.option("--id", required=False, help="An existing node ID to start (the node name argument is ignored)")
12 | def start(node, id):
13 | """
14 | start a node
15 | """
16 | if not node and not id:
17 | exit(call([get_command(), "start", "--help"]))
18 |
19 | server = VIRLServer()
20 | client = get_cml_client(server)
21 |
22 | current_lab = get_current_lab()
23 | if current_lab:
24 | lab = safe_join_existing_lab(current_lab, client)
25 | if lab:
26 | try:
27 | if id:
28 | node_obj = lab.get_node_by_id(id)
29 | else:
30 | node_obj = lab.get_node_by_label(node)
31 |
32 | if not node_obj.is_active():
33 | node_obj.start(wait=True)
34 | click.secho("Started node {}".format(node_obj.label))
35 | else:
36 | click.secho("Node {} is already active".format(node_obj.label), fg="yellow")
37 | except NodeNotFound:
38 | click.secho("Node {} was not found in lab {}".format(id if id else node, current_lab), fg="red")
39 | exit(1)
40 | else:
41 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
42 | exit(1)
43 | else:
44 | click.secho("No current lab set", fg="red")
45 | exit(1)
46 |
--------------------------------------------------------------------------------
/virl/cli/stop/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/stop/__init__.py
--------------------------------------------------------------------------------
/virl/cli/stop/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 | from subprocess import call
3 |
4 | from virl2_client.exceptions import NodeNotFound
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import get_cml_client, get_current_lab, safe_join_existing_lab, get_command
8 |
9 |
10 | @click.command()
11 | @click.argument("node", required=False)
12 | @click.option("--id", required=False, help="An existing node ID to stop (the node name argument is ignored)")
13 | def stop(node, id):
14 | """
15 | stop a node
16 | """
17 | if not node and not id:
18 | exit(call([get_command(), "stop", "--help"]))
19 |
20 | server = VIRLServer()
21 | client = get_cml_client(server)
22 |
23 | current_lab = get_current_lab()
24 | if current_lab:
25 | lab = safe_join_existing_lab(current_lab, client)
26 | if lab:
27 | try:
28 | if id:
29 | node_obj = lab.get_node_by_id(id)
30 | else:
31 | node_obj = lab.get_node_by_label(node)
32 |
33 | if node_obj.is_active():
34 | node_obj.stop(wait=True)
35 | click.secho("Stopped node {}".format(node_obj.label))
36 | else:
37 | click.secho("Node {} is already stopped".format(node_obj.label), fg="yellow")
38 | except NodeNotFound:
39 | click.secho("Node {} was not found in lab {}".format(id if id else node, current_lab), fg="red")
40 | exit(1)
41 | else:
42 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
43 | exit(1)
44 | else:
45 | click.secho("No current lab set", fg="red")
46 | exit(1)
47 |
--------------------------------------------------------------------------------
/virl/cli/telnet/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/telnet/__init__.py
--------------------------------------------------------------------------------
/virl/cli/telnet/commands.py:
--------------------------------------------------------------------------------
1 | from subprocess import call
2 |
3 | import click
4 | from virl2_client.exceptions import NodeNotFound
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import (get_cml_client, get_current_lab, get_node_mgmt_ip,
8 | safe_join_existing_lab)
9 |
10 |
11 | @click.command()
12 | @click.argument("node", nargs=1)
13 | def telnet(node):
14 | """
15 | telnet to a node
16 | """
17 | server = VIRLServer()
18 | client = get_cml_client(server)
19 |
20 | current_lab = get_current_lab()
21 | if current_lab:
22 | lab = safe_join_existing_lab(current_lab, client)
23 | if lab:
24 | try:
25 | node_obj = lab.get_node_by_label(node)
26 | except NodeNotFound:
27 | click.secho("Node {} was not found in lab {}".format(node, current_lab), fg="red")
28 | exit(1)
29 |
30 | if node_obj.is_active():
31 | mgmtip = get_node_mgmt_ip(node_obj)
32 | if mgmtip:
33 | if "VIRL_TELNET_COMMAND" in server.config:
34 | cmd = server.config["VIRL_TELNET_COMMAND"]
35 | cmd = cmd.format(host=mgmtip)
36 | print("Calling user specified command: {}".format(cmd))
37 | exit(call(cmd.split()))
38 | else:
39 | click.secho("Attemping telnet connection to {} at {}".format(node_obj.label, mgmtip))
40 |
41 | exit(call(["telnet", mgmtip]))
42 | else:
43 | click.secho("Node {} does not have an external management IP".format(node_obj.label))
44 | else:
45 | click.secho("Node {} is not active".format(node_obj.label), fg="yellow")
46 | else:
47 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
48 | exit(1)
49 | else:
50 | click.secho("No current lab set", fg="red")
51 | exit(1)
52 |
--------------------------------------------------------------------------------
/virl/cli/tmux/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/tmux/__init__.py
--------------------------------------------------------------------------------
/virl/cli/tmux/commands.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import click
4 | import libtmux
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import (get_cml_client, get_current_lab,
8 | safe_join_existing_lab)
9 |
10 |
11 | def connect_tmux(session_title, node_console_cmd, group):
12 | tmux_server = libtmux.server.Server()
13 | session = tmux_server.new_session(session_name=session_title, kill_session=True)
14 | if group == "panes":
15 | window = session.windows[0]
16 | panes_len = len(window.panes)
17 | for node_obj, cmd in node_console_cmd:
18 | label = node_obj.label
19 | if panes_len == 1:
20 | panes_len += 1
21 | pane = window.panes[0]
22 | else:
23 | pane = window.split_window()
24 | pane.send_keys("printf '\\033]2;%s\\033\\\\' '{}'".format(label), suppress_history=True)
25 | pane.send_keys(cmd, suppress_history=True)
26 | window.select_layout("tiled")
27 |
28 | if group == "windows":
29 | windows_len = len(session.windows)
30 | for node_obj, cmd in node_console_cmd:
31 | label = node_obj.label
32 | if windows_len == 1:
33 | windows_len += 1
34 | window = session.windows[0]
35 | window.cmd("rename-window", label)
36 | else:
37 | window = session.new_window(window_name=label)
38 | pane = window.panes[0]
39 | pane.send_keys(cmd, suppress_history=True)
40 |
41 | if "TMUX" in os.environ:
42 | session.switch_client()
43 | else:
44 | session.attach_session()
45 |
46 |
47 | @click.command(help="console to all nodes using tmux")
48 | @click.option(
49 | "--group",
50 | type=click.Choice(("panes", "windows")),
51 | default="panes",
52 | show_default=True,
53 | help="'panes': group all nodes in one window, 'windows': one node per window",
54 | )
55 | def tmux(group):
56 | """
57 | console to all devices in the lab with tmux
58 | """
59 | server = VIRLServer()
60 | client = get_cml_client(server)
61 | skip_types = ["external_connector", "unmanaged_switch"]
62 |
63 | node_console_cmd = []
64 | current_lab = get_current_lab()
65 | if current_lab:
66 | lab = safe_join_existing_lab(current_lab, client)
67 | if lab:
68 | for node_obj in lab.nodes():
69 | if node_obj.node_definition in skip_types or not node_obj.is_active():
70 | continue
71 | if len(lab.id) == 6:
72 | # Old-style (CML 2.2) lab IDs; console uses lab_id/node_id
73 | console = "/{}/{}/0".format(lab.id, node_obj.id)
74 | else:
75 | # From CML 2.3, console uses lab_title/node_label
76 | console = "/{}/{}/0".format(lab.title, node_obj.label)
77 | # use user specified ssh command
78 | if "CML_CONSOLE_COMMAND" in server.config:
79 | cmd = server.config["CML_CONSOLE_COMMAND"]
80 | cmd = cmd.format(host=server.host, user=server.user, console="open " + console)
81 | print("Calling user specified command: {}".format(cmd))
82 | else:
83 | cmd = "ssh -t {}@{} open {}".format(server.user, server.host, console)
84 | node_console_cmd.append((node_obj, cmd))
85 |
86 | if node_console_cmd:
87 | session_title = "{}-{}".format(str(lab.title).replace(".", "_").replace(":", "_"), lab.id[:4])
88 | connect_tmux(session_title, node_console_cmd, group)
89 | else:
90 | click.secho("Unable to find any valid nodes", fg="red")
91 | exit(1)
92 | else:
93 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
94 | exit(1)
95 | else:
96 | click.secho("No current lab set", fg="red")
97 | exit(1)
98 |
--------------------------------------------------------------------------------
/virl/cli/ui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/ui/__init__.py
--------------------------------------------------------------------------------
/virl/cli/ui/commands.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import (get_cml_client, get_current_lab,
7 | safe_join_existing_lab)
8 |
9 |
10 | @click.command()
11 | def ui():
12 | """
13 | opens the Workbench for the current lab
14 | """
15 | server = VIRLServer()
16 | client = get_cml_client(server)
17 |
18 | current_lab = get_current_lab()
19 | if current_lab:
20 | lab = safe_join_existing_lab(current_lab, client)
21 | if lab:
22 | url = "https://{}/lab/{}".format(server.host, current_lab)
23 | subprocess.Popen(["open", url])
24 |
--------------------------------------------------------------------------------
/virl/cli/up/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/up/__init__.py
--------------------------------------------------------------------------------
/virl/cli/use/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/use/__init__.py
--------------------------------------------------------------------------------
/virl/cli/use/commands.py:
--------------------------------------------------------------------------------
1 | from subprocess import call
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import (cache_lab, check_lab_cache, get_cml_client,
7 | get_command, safe_join_existing_lab,
8 | safe_join_existing_lab_by_title, set_current_lab)
9 |
10 |
11 | # This may need to become a helper
12 | def check_lab_cache_server(lab_id, client):
13 | """
14 | check if a lab exists in either the cache or the server.
15 | if on server and not in cache, cache the lab.
16 | """
17 | ret = None
18 |
19 | if not check_lab_cache(lab_id):
20 | lab_obj = safe_join_existing_lab(lab_id, client)
21 | if lab_obj:
22 | cache_lab(lab_obj)
23 | ret = lab_id
24 | else:
25 | ret = lab_id
26 |
27 | return ret
28 |
29 |
30 | @click.command()
31 | @click.argument("lab", required=False)
32 | @click.option("--id", required=False, help="An existing lab ID to make the current lab (lab-name is ignored)")
33 | @click.option("--lab-name", "-n", required=False, help="An existing lab name to make the current lab")
34 | def use(lab, id, lab_name):
35 | """
36 | use lab launched elsewhere
37 | """
38 | server = VIRLServer()
39 | client = get_cml_client(server)
40 | lab_id = None
41 |
42 | if not lab and not id and not lab_name:
43 | exit(call([get_command(), "use", "--help"]))
44 |
45 | if id:
46 | lab_id = check_lab_cache_server(id, client)
47 |
48 | # Prefer --lab-name over positional argument
49 | if lab_name:
50 | lab = lab_name
51 |
52 | if not id and lab:
53 | lab_obj = safe_join_existing_lab_by_title(lab, client)
54 | if lab_obj:
55 | # Make sure this lab is cached.
56 | lab_id = check_lab_cache_server(lab_obj.id, client)
57 |
58 | if lab_id:
59 | set_current_lab(lab_id)
60 | else:
61 | click.secho("Unable to find unique lab in the cache or on the server", fg="red")
62 | exit(1)
63 |
--------------------------------------------------------------------------------
/virl/cli/users/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.users.create.commands import create_users
4 | from virl.cli.users.delete.commands import delete_users
5 | from virl.cli.users.ls.commands import list_users
6 | from virl.cli.users.update.commands import update_users
7 |
8 |
9 | @click.group()
10 | def users():
11 | """
12 | manage users
13 | """
14 | pass
15 |
16 |
17 | users.add_command(list_users, name="ls")
18 | users.add_command(create_users, name="create")
19 | users.add_command(update_users, name="update")
20 | users.add_command(delete_users, name="delete")
21 |
--------------------------------------------------------------------------------
/virl/cli/users/create/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/users/create/__init__.py
--------------------------------------------------------------------------------
/virl/cli/users/create/commands.py:
--------------------------------------------------------------------------------
1 | import getpass
2 | import sys
3 |
4 | import click
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import get_cml_client
8 |
9 |
10 | @click.command()
11 | @click.option("--admin/--no-admin", is_flag=True, default=False, help="Grant or revoke admin privileges for the users")
12 | @click.option("--group", default=[], multiple=True, help="Assign the users to one or more groups (e.g., --group group1 --group group2)")
13 | @click.argument("usernames", nargs=-1, required=True)
14 | def create_users(usernames, admin, group):
15 | """
16 | Create one or more users (e.g., user1 user2)
17 | """
18 |
19 | server = VIRLServer()
20 | client = get_cml_client(server)
21 | group_ids = [client.group_management.group_id(g) for g in group]
22 |
23 | for username in usernames:
24 | kwargs = {
25 | "username": username,
26 | "admin": admin,
27 | "groups": group_ids,
28 | }
29 | try:
30 | passwd = confirm_password(username)
31 | kwargs["pwd"] = passwd
32 | client.user_management.create_user(**kwargs)
33 | click.secho(f"User {username} successfully created", fg="green")
34 | except Exception as e:
35 | click.secho(f"Failed to create user: {e}", fg="red")
36 | sys.exit(1)
37 |
38 |
39 | def confirm_password(username):
40 | """
41 | Prompts the user for a password and confirms it
42 | """
43 | passwd = getpass.getpass(f"Enter {username}'s password: ")
44 | re_entered_passwd = getpass.getpass(f"Re-Enter {username}'s password: ")
45 | if passwd != re_entered_passwd:
46 | click.secho("Passwords do not match", fg="red")
47 | sys.exit(1)
48 | return passwd
49 |
--------------------------------------------------------------------------------
/virl/cli/users/delete/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/users/delete/__init__.py
--------------------------------------------------------------------------------
/virl/cli/users/delete/commands.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import click
4 |
5 | from virl.api import VIRLServer
6 | from virl.helpers import get_cml_client
7 |
8 |
9 | @click.command()
10 | @click.argument("usernames", nargs=-1, required=True)
11 | def delete_users(usernames):
12 | """
13 | Delete one or more users (e.g., user1 user2)
14 | """
15 |
16 | server = VIRLServer()
17 | client = get_cml_client(server)
18 | user_mapping = {u["username"]: u["id"] for u in client.user_management.users()}
19 |
20 | for username in usernames:
21 | try:
22 | user_id = user_mapping[username]
23 | client.user_management.delete_user(user_id)
24 | click.secho(f"User {username} successfully deleted", fg="green")
25 | except Exception as e:
26 | click.secho(f"Failed to delete user: {e}", fg="red")
27 | sys.exit(1)
28 |
--------------------------------------------------------------------------------
/virl/cli/users/ls/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/users/ls/__init__.py
--------------------------------------------------------------------------------
/virl/cli/users/ls/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import NoPluginError, ViewerPlugin, VIRLServer
4 | from virl.cli.views import user_list_table
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | @click.option("-v", "--verbose", is_flag=True, help="Include user IDs in the output")
10 | def list_users(verbose):
11 | """
12 | List all users on the server
13 | """
14 | server = VIRLServer()
15 | client = get_cml_client(server)
16 |
17 | users = client.user_management.users()
18 | user_keys = (
19 | "id",
20 | "created",
21 | "modified",
22 | "username",
23 | "fullname",
24 | "email",
25 | "description",
26 | "admin",
27 | "directory_dn",
28 | "opt_in",
29 | "resource_pool",
30 | "tour_version",
31 | "pubkey_info",
32 | )
33 | labs_mapping = {lab.id: lab.title for lab in client.all_labs(show_all=True)}
34 | group_mapping = {g["id"]: g["name"] for g in client.group_management.groups()}
35 | for user in users:
36 | user["groups"] = [group_mapping[group_id] for group_id in user.get("groups", [])]
37 | user["labs"] = [labs_mapping[lab_id] for lab_id in user.get("labs", [])]
38 | for k in user_keys:
39 | user.setdefault(k, "N/A")
40 | try:
41 | pl = ViewerPlugin(viewer="user")
42 | pl.visualize(users=users)
43 | except NoPluginError:
44 | user_list_table(users, verbose=verbose)
45 |
--------------------------------------------------------------------------------
/virl/cli/users/update/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/users/update/__init__.py
--------------------------------------------------------------------------------
/virl/cli/users/update/commands.py:
--------------------------------------------------------------------------------
1 | import getpass
2 | import sys
3 |
4 | import click
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import get_cml_client
8 |
9 |
10 | @click.command()
11 | @click.option("--admin/--no-admin", is_flag=True, default=None, help="Grant or revoke admin privileges for the users")
12 | @click.option(
13 | "--group",
14 | multiple=True,
15 | help="Assign the user to one or more groups (e.g., --group group1 --group group2)",
16 | )
17 | @click.option("--remove-from-all-groups", is_flag=True, help="Remove the users from all groups")
18 | @click.option("--change-password", is_flag=True, help="Prompt to change the users' password")
19 | @click.option("--all-users", is_flag=True, help="Apply the changes to all users")
20 | @click.argument("usernames", nargs=-1, required=True)
21 | def update_users(usernames, admin, group, remove_from_all_groups, change_password, all_users):
22 | """
23 | Update one or more users (e.g., user1 user2)
24 | """
25 |
26 | server = VIRLServer()
27 | client = get_cml_client(server)
28 |
29 | group = group if group else None
30 | group = [] if remove_from_all_groups else group
31 | group_ids = [g["id"] for g in client.group_management.groups() if g["name"] in group] if group else group
32 |
33 | users = client.user_management.users()
34 | user_mapping = {user["username"]: user["id"] for user in users}
35 | all_usernames = users if all_users else usernames
36 |
37 | for username in all_usernames:
38 | user_id = user_mapping[username]
39 | password_dict = get_password_dict(username) if change_password else None
40 | kwargs = {
41 | "user_id": user_id,
42 | "admin": admin,
43 | "groups": group_ids,
44 | "password_dict": password_dict,
45 | }
46 | # only pass kwargs that are not None
47 | kwargs = {k: v for k, v in kwargs.items() if v is not None}
48 | try:
49 | client.user_management.update_user(**kwargs)
50 | click.secho(f"User {username} successfully updated", fg="green")
51 | except Exception as e:
52 | click.secho(f"Failed to create user: {e}", fg="red")
53 | sys.exit(1)
54 |
55 |
56 | def get_password_dict(username):
57 | """
58 | Prompt the user for old and new password and verify it
59 | A user with administrative privileges can set a new password by providing an arbitrary or empty old password
60 | """
61 | old_passwd = getpass.getpass(f"Enter {username}'s old password (password can be blank if you are an admin): ")
62 | new_passwd = getpass.getpass(f"Enter {username}'s new password: ")
63 | return {"old_password": old_passwd, "new_password": new_passwd}
64 |
--------------------------------------------------------------------------------
/virl/cli/version/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/version/__init__.py
--------------------------------------------------------------------------------
/virl/cli/version/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl import __version__
4 | from virl.api import VIRLServer
5 | from virl.helpers import get_cml_client
6 |
7 |
8 | @click.command()
9 | def version():
10 | """
11 | version information
12 | """
13 | server = VIRLServer()
14 | client = get_cml_client(server)
15 | server_version = "Unknown"
16 | try:
17 | server_version = client.system_info()["version"]
18 | except Exception:
19 | pass
20 | virlutils_version = __version__
21 | click.secho("cmlutils Version: {}".format(virlutils_version))
22 | click.secho("CML Controller Version: {}".format(server_version))
23 |
--------------------------------------------------------------------------------
/virl/cli/views/__init__.py:
--------------------------------------------------------------------------------
1 | from .cluster.cluster_views import cluster_list_table # noqa
2 | from .generate.nso import sync_table # noqa
3 | from .groups.group_views import group_list_table # noqa
4 | from .images.image_views import image_list_table # noqa
5 | from .labs.lab_views import lab_list_table # noqa
6 | from .license.license_views import (license_details_table, # noqa
7 | license_features_table)
8 | from .node_defs.node_def_views import node_def_list_table # noqa
9 | from .nodes.node_views import node_list_table # noqa
10 | from .users.user_views import user_list_table # noqa
11 |
--------------------------------------------------------------------------------
/virl/cli/views/cluster/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/cluster/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/cluster/cluster_views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def cluster_list_table(computes: dict) -> None:
6 | table = list()
7 | headers = ["ID", "Hostname", "Is Controller?", "Status"]
8 | for cid, compute in computes.items():
9 | tr = list()
10 |
11 | tr.append(cid)
12 | tr.append(compute["hostname"])
13 | tr.append(compute["is_controller"])
14 |
15 | healthy = "HEALTHY"
16 | bad_props = []
17 | for stat_prop, description in {
18 | "kvm_vmx_enabled": "KVM",
19 | "enough_cpus": "CPU",
20 | "refplat_images_available": "REFPLAT",
21 | "lld_connected": "LLD",
22 | "valid": "VALID",
23 | }.items():
24 | if not compute[stat_prop]:
25 | healthy = "UNHEALTHY"
26 | bad_props.append(description)
27 |
28 | color = "green"
29 | if len(bad_props) > 0:
30 | healthy += " ({})".format(",".join(bad_props))
31 | color = "red"
32 |
33 | tr.append(click.style(healthy, fg=color))
34 | table.append(tr)
35 | # wrap the output in this try/except block as some terminals
36 | # may have problem with the 'fancy_grid'
37 | try:
38 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
39 | except UnicodeEncodeError:
40 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
41 |
--------------------------------------------------------------------------------
/virl/cli/views/console/__init__.py:
--------------------------------------------------------------------------------
1 | from .console_views import console_table # noqa
2 |
--------------------------------------------------------------------------------
/virl/cli/views/console/console_views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def console_table(consoles):
6 | click.secho("Here is a list of all the running consoles")
7 | headers = ["Node", "Console Path"]
8 | table = list()
9 | for console in consoles:
10 | tr = list()
11 | tr.append(console["node"])
12 | tr.append(console["console"])
13 | table.append(tr)
14 | # wrap the output in this try/except block as some terminals
15 | # may have problem with the 'fancy_grid'
16 | try:
17 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
18 | except UnicodeEncodeError:
19 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
20 |
--------------------------------------------------------------------------------
/virl/cli/views/generate/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/generate/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/generate/nso/__init__.py:
--------------------------------------------------------------------------------
1 | from .sync_result import sync_table # noqa
2 |
--------------------------------------------------------------------------------
/virl/cli/views/generate/nso/sync_result.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def sync_table(sync_result):
6 | click.secho("""
7 | NSO Sync Report
8 | """)
9 | headers = ["Device", "Result"]
10 | table = list()
11 |
12 | sync_results = sync_result['tailf-ncs:output']['sync-result']
13 | for item in sync_results:
14 | tr = list()
15 | tr.append(item['device'])
16 |
17 | result = item['result']
18 | if result is True:
19 | result = "SUCCESS"
20 | color = 'green'
21 | else:
22 | result = "FAILED"
23 | color = 'red'
24 |
25 | tr.append(click.style(result, fg=color))
26 | table.append(tr)
27 | # wrap the output in this try/except block as some terminals
28 | # may have problem with the 'fancy_grid'
29 | try:
30 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
31 | except UnicodeEncodeError:
32 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
33 |
--------------------------------------------------------------------------------
/virl/cli/views/groups/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/groups/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/groups/group_views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import textwrap
3 |
4 | import click
5 | import tabulate
6 |
7 |
8 | def group_list_table(groups, verbose=False):
9 | click.secho("Groups on Server", fg="green")
10 | table = []
11 |
12 | headers = ["ID"] if verbose else []
13 | headers.extend(["Name", "Description", "Users", "Labs"])
14 | for group in groups:
15 | tr = []
16 | if verbose:
17 | tr.append(group["id"])
18 | tr.append(group["name"])
19 | wrapped_description = textwrap.fill(group["description"], width=20)
20 | tr.append(wrapped_description)
21 |
22 | tr.append("\n".join(group["members"]))
23 | tr.append("\n".join(f"{lab['title']} ({lab['permission']})" for lab in group["labs"]))
24 | table.append(tr)
25 | # wrap the output in this try/except block as some terminals
26 | # may have problem with the 'fancy_grid'
27 | try:
28 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
29 | except UnicodeEncodeError:
30 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
31 |
--------------------------------------------------------------------------------
/virl/cli/views/images/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/images/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/images/image_views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def image_list_table(image_list):
6 |
7 | headers = ["ID", "Node Definition ID", "Label", "Description", "RAM", "CPUs", "Boot Disk Size"]
8 | table = list()
9 |
10 | for f in list(image_list):
11 | tr = list()
12 | tr.append(str(f["id"]))
13 | tr.append(str(f["node_definition_id"]))
14 | tr.append(str(f["label"]))
15 | tr.append(str(f["description"]))
16 | tr.append(str(f["ram"]))
17 | tr.append(str(f["cpus"]))
18 | tr.append(str(f["boot_disk_size"]))
19 |
20 | table.append(tr)
21 |
22 | try:
23 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
24 | except UnicodeEncodeError:
25 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
26 |
--------------------------------------------------------------------------------
/virl/cli/views/labs/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/labs/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/labs/lab_views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import textwrap
3 |
4 | import click
5 | import tabulate
6 |
7 |
8 | def lab_list_table(labs, ownerids_usernames, cached_labs=None):
9 | click.secho("Labs on Server", fg="green")
10 | print_labs(labs, ownerids_usernames)
11 | if cached_labs:
12 | click.secho("Cached Labs", fg="yellow")
13 | print_labs(cached_labs, ownerids_usernames)
14 |
15 |
16 | def print_labs(labs, ownerids_usernames):
17 | table = list()
18 | headers = ["ID", "Title", "Description", "Owner", "Status", "Nodes", "Links", "Interfaces"]
19 | for lab in labs:
20 | tr = list()
21 | tr.append(lab.id)
22 | tr.append(lab.title)
23 | wrapped_description = textwrap.fill(lab.description, width=40)
24 | tr.append(wrapped_description)
25 | owner = ownerids_usernames.get(lab.owner, lab.owner)
26 | tr.append(owner)
27 | status = lab.state()
28 | stats = lab.statistics
29 | if status in {"BOOTED", "STARTED"}:
30 | color = "green"
31 | elif status in {"QUEUED"}:
32 | color = "yellow"
33 | else:
34 | color = "red"
35 | tr.append(click.style(status, fg=color))
36 | tr.append(stats["nodes"])
37 | tr.append(stats["links"])
38 | tr.append(stats["interfaces"])
39 | table.append(tr)
40 | # wrap the output in this try/except block as some terminals
41 | # may have problem with the 'fancy_grid'
42 | try:
43 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
44 | except UnicodeEncodeError:
45 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
46 |
--------------------------------------------------------------------------------
/virl/cli/views/license/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/license/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/license/license_views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def license_details_table(license):
6 | click.secho("Registration Details")
7 | print_registration(license["registration"])
8 | click.secho("Authorization Details")
9 | print_authorization(license["authorization"])
10 | click.secho("Features")
11 | print_features(license["features"])
12 |
13 |
14 | def license_features_table(license):
15 | table = list()
16 | headers = ["ID", "Name", "In Use"]
17 | for feature in license:
18 | tr = list()
19 | tr.append(feature["id"])
20 | tr.append(feature["name"])
21 | tr.append(feature["in_use"])
22 |
23 | table.append(tr)
24 |
25 | # wrap the output in this try/except block as some terminals
26 | # may have problem with the 'fancy_grid'
27 | try:
28 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
29 | except UnicodeEncodeError:
30 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
31 |
32 |
33 | def print_registration(reg_obj):
34 | table = list()
35 | headers = ["Status", "Expires", "Smart Account", "Virtual Account", "Registration Time", "Registration Status", "Next Renewal Time"]
36 | tr = list()
37 | stat_color = None
38 | if reg_obj["status"] == "COMPLETED":
39 | stat_color = "green"
40 | elif reg_obj["status"] == "IN_PROGRESS":
41 | stat_color = "yellow"
42 | else:
43 | stat_color = "red"
44 | tr.append(click.style(reg_obj["status"], fg=stat_color))
45 | tr.append(reg_obj["expires"])
46 | tr.append(reg_obj["smart_account"])
47 | tr.append(reg_obj["virtual_account"])
48 | if reg_obj["register_time"]["attempted"]:
49 | reg_color = None
50 | tr.append(reg_obj["register_time"]["attempted"])
51 | if reg_obj["register_time"]["success"] == "SUCCESS":
52 | reg_color = "green"
53 | else:
54 | reg_color = "red"
55 | tr.append(click.style(reg_obj["register_time"]["success"], fg=reg_color))
56 | else:
57 | tr.append("N/A")
58 | tr.append("N/A")
59 | if reg_obj["renew_time"]["scheduled"]:
60 | tr.append(reg_obj["renew_time"]["scheduled"])
61 | else:
62 | tr.append("N/A")
63 |
64 | table.append(tr)
65 |
66 | # wrap the output in this try/except block as some terminals
67 | # may have problem with the 'fancy_grid'
68 | try:
69 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
70 | except UnicodeEncodeError:
71 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
72 |
73 |
74 | def print_authorization(auth_obj):
75 | table = list()
76 | headers = ["Status", "Expires", "Renewal Time", "Renewal Status", "Next Renewal Time"]
77 | tr = list()
78 | auth_color = None
79 | if auth_obj["status"] == "IN_COMPLIANCE":
80 | auth_color = "green"
81 | else:
82 | auth_color = "red"
83 | tr.append(click.style(auth_obj["status"], fg=auth_color))
84 | tr.append(auth_obj["expires"])
85 | if auth_obj["renew_time"]["attempted"]:
86 | renew_color = None
87 | if auth_obj["renew_time"]["status"] == "SUCCEEDED":
88 | renew_color = "green"
89 | elif auth_obj["renew_time"]["status"] == "NOT STARTED":
90 | renew_color = "yellow"
91 | else:
92 | renew_color = "red"
93 | tr.append(auth_obj["renew_time"]["attempted"])
94 | tr.append(click.style(auth_obj["renew_time"]["status"], fg=renew_color))
95 | else:
96 | tr.append("N/A")
97 | tr.append("N/A")
98 | if auth_obj["renew_time"]["scheduled"]:
99 | tr.append(auth_obj["renew_time"]["scheduled"])
100 | else:
101 | tr.append("N/A")
102 |
103 | table.append(tr)
104 |
105 | # wrap the output in this try/except block as some terminals
106 | # may have problem with the 'fancy_grid'
107 | try:
108 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
109 | except UnicodeEncodeError:
110 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
111 |
112 |
113 | def print_features(feature_obj):
114 | table = list()
115 | headers = ["Name", "Description", "In Use", "Status", "Version"]
116 | for feature in feature_obj:
117 | tr = list()
118 | tr.append(feature["name"])
119 | tr.append(feature["description"])
120 | tr.append(feature["in_use"])
121 | color = None
122 | if feature["status"] == "IN_COMPLIANCE":
123 | color = "green"
124 | elif feature["status"] != "INIT":
125 | color = "red"
126 | tr.append(click.style(feature["status"], fg=color))
127 | tr.append(feature["version"])
128 |
129 | table.append(tr)
130 |
131 | # wrap the output in this try/except block as some terminals
132 | # may have problem with the 'fancy_grid'
133 | try:
134 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
135 | except UnicodeEncodeError:
136 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
137 |
--------------------------------------------------------------------------------
/virl/cli/views/node_defs/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/node_defs/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/node_defs/node_def_views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def node_def_list_table(image_list):
6 | headers = ["ID", "Label", "Description", "Max No. Interfaces", "RAM", "CPUs", "Boot Disk Size"]
7 | table = list()
8 |
9 | for f in list(image_list):
10 | tr = list()
11 | tr.append(str(f["id"]))
12 | tr.append(str(f["ui"].get("label", "N/A")))
13 | tr.append(str(f["general"]["description"]))
14 | tr.append(str(len(f["device"]["interfaces"]["physical"])))
15 | linux_native = f["sim"].get("linux_native", None)
16 | if linux_native:
17 | ram = int(linux_native.get("ram", 0))
18 | unit = "GB"
19 | if ram > 1024:
20 | ram /= 1024
21 | else:
22 | unit = "MB"
23 | tr.append(str(ram) + " " + unit)
24 | tr.append(str(linux_native.get("cpus", "N/A")))
25 | if "boot_disk_size" in linux_native:
26 | tr.append(str(linux_native["boot_disk_size"]) + " GB")
27 | else:
28 | tr.append("N/A")
29 | else:
30 | tr.append("N/A") # RAM
31 | tr.append("N/A") # CPU
32 | tr.append("N/A") # Boot Disk Size
33 |
34 | table.append(tr)
35 |
36 | try:
37 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
38 | except UnicodeEncodeError:
39 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
40 |
--------------------------------------------------------------------------------
/virl/cli/views/nodes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/nodes/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/nodes/node_views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def node_list_table(nodes, computes):
6 | click.secho("Here is a list of nodes in this lab")
7 | table = list()
8 | headers = ["ID", "Label", "Type"]
9 | if computes:
10 | headers.append("Compute Node")
11 |
12 | headers += ["State", "Wiped?", "L3 Address(es)"]
13 | skip_types = []
14 | for node in nodes:
15 | # Skip a full operational sync per node.
16 | node.lab.auto_sync = True
17 | for sync in (
18 | # "sync_statistics_if_outdated",
19 | "sync_states_if_outdated",
20 | "sync_l3_addresses_if_outdated",
21 | "sync_topology_if_outdated",
22 | ):
23 | try:
24 | meth = getattr(node.lab, sync)
25 | except AttributeError:
26 | pass
27 | else:
28 | meth()
29 |
30 | node.lab.auto_sync = False
31 |
32 | tr = list()
33 | if node.node_definition in skip_types:
34 | continue
35 |
36 | tr.append(node.id)
37 | tr.append(node.label)
38 | tr.append(node.node_definition)
39 | try:
40 | node_compute_id = node.compute_id
41 | except AttributeError:
42 | node_compute_id = None
43 |
44 | if node_compute_id and node_compute_id in computes:
45 | tr.append(computes[node_compute_id]["hostname"])
46 | elif computes:
47 | tr.append("Unknown")
48 |
49 | color = "red"
50 | booted = node.is_booted()
51 | if booted:
52 | color = "green"
53 | elif node.is_active():
54 | color = "yellow"
55 |
56 | node_state = node.state
57 | tr.append(click.style(node_state, fg=color))
58 | tr.append(node_state == "DEFINED_ON_CORE")
59 | intfs = []
60 | if booted:
61 | for i in node.interfaces():
62 | disc_ipv4 = i.discovered_ipv4
63 | if disc_ipv4:
64 | intfs += disc_ipv4
65 |
66 | disc_ipv6 = i.discovered_ipv6
67 | if disc_ipv6:
68 | intfs += disc_ipv6
69 |
70 | tr.append("\n".join(intfs))
71 | table.append(tr)
72 | # wrap the output in this try/except block as some terminals
73 | # may have problem with the 'fancy_grid'
74 | try:
75 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
76 | except UnicodeEncodeError:
77 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
78 |
--------------------------------------------------------------------------------
/virl/cli/views/search/__init__.py:
--------------------------------------------------------------------------------
1 | from .views import repo_table # noqa
2 |
--------------------------------------------------------------------------------
/virl/cli/views/search/views.py:
--------------------------------------------------------------------------------
1 | import click
2 | import tabulate
3 |
4 |
5 | def repo_table(repo_entries):
6 | # sort by date
7 | headers = ["Name", "Stars", "Description"]
8 | table = list()
9 |
10 | for repo in repo_entries:
11 | tr = list()
12 | tr.append(repo["full_name"])
13 | tr.append(repo["stargazers_count"])
14 | tr.append(repo["description"])
15 | table.append(tr)
16 | # wrap the output in this try/except block as some terminals
17 | # may have problem with the 'fancy_grid'
18 | try:
19 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
20 | except UnicodeEncodeError:
21 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
22 |
--------------------------------------------------------------------------------
/virl/cli/views/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/views/users/__init__.py
--------------------------------------------------------------------------------
/virl/cli/views/users/user_views.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import textwrap
3 |
4 | import click
5 | import tabulate
6 |
7 |
8 | def user_list_table(users, verbose=False):
9 | click.secho("Users on Server", fg="green")
10 | table = []
11 | headers = ["ID"] if verbose else []
12 | headers.extend(["Username", "Administrator", "Full Name", "Email", "Groups", "Labs"])
13 | for user in users:
14 | tr = []
15 | if verbose:
16 | tr.append(user["id"])
17 | tr.append(user["username"])
18 | tr.append(user["admin"])
19 | wrapped_fullname = textwrap.fill(user["fullname"], width=20)
20 | tr.append(wrapped_fullname)
21 | tr.append(user["email"])
22 | tr.append("\n".join(user["groups"]))
23 | tr.append("\n".join(user["labs"]))
24 | table.append(tr)
25 | # wrap the output in this try/except block as some terminals
26 | # may have problem with the 'fancy_grid'
27 | try:
28 | click.echo(tabulate.tabulate(table, headers, tablefmt="fancy_grid"))
29 | except UnicodeEncodeError:
30 | click.echo(tabulate.tabulate(table, headers, tablefmt="grid"))
31 |
--------------------------------------------------------------------------------
/virl/cli/wipe/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.cli.wipe.lab.commands import lab as labc
4 | from virl.cli.wipe.node.commands import node as nodec
5 |
6 |
7 | @click.group()
8 | def wipe():
9 | """
10 | wipe a lab or nodes within a lab
11 | """
12 | pass
13 |
14 |
15 | wipe.add_command(labc, name="lab")
16 | wipe.add_command(nodec, name="node")
17 |
--------------------------------------------------------------------------------
/virl/cli/wipe/lab/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/wipe/lab/__init__.py
--------------------------------------------------------------------------------
/virl/cli/wipe/lab/commands.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from virl.api import VIRLServer
4 | from virl.helpers import (get_cml_client, get_current_lab,
5 | safe_join_existing_lab)
6 |
7 |
8 | @click.command()
9 | @click.option("--force/--no-force", "-f", default=False, required=False, help="Stop a lab (if it's started) then wipe it (default: False)")
10 | @click.option(
11 | "--confirm/--no-confirm",
12 | show_default=False,
13 | default=True,
14 | help="Do not prompt for confirmation (default: prompt)",
15 | required=False,
16 | )
17 | def lab(force, confirm):
18 | """
19 | wipe a lab
20 | """
21 | server = VIRLServer()
22 | client = get_cml_client(server)
23 |
24 | current_lab = get_current_lab()
25 | if current_lab:
26 | lab = safe_join_existing_lab(current_lab, client)
27 | if lab:
28 | active = lab.is_active()
29 | if active and force:
30 | lab.stop(wait=True)
31 |
32 | # Check again just to be sure.
33 | if not lab.is_active():
34 | ret = "y"
35 | if confirm:
36 | ret = input("Are you sure you want to wipe lab {} (ID: {}) [y/N]? ".format(lab.title, current_lab))
37 | if ret.lower().startswith("y"):
38 | lab.wipe(wait=True)
39 | click.secho("Lab {} (ID: {}) wiped".format(lab.title, current_lab))
40 | else:
41 | click.secho("Not wiping lab {} (ID: {})".format(lab.title, current_lab))
42 |
43 | else:
44 | click.secho("Lab {} (ID: {}) is active; either down it or use --force".format(lab.title, current_lab), fg="red")
45 | exit(1)
46 | else:
47 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
48 | exit(1)
49 | else:
50 | click.secho("Current lab is not set", fg="red")
51 | exit(1)
52 |
--------------------------------------------------------------------------------
/virl/cli/wipe/node/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/virlutils/6cec1fcd73f51e99a0aef8e34ae0a0c0bcb1bf91/virl/cli/wipe/node/__init__.py
--------------------------------------------------------------------------------
/virl/cli/wipe/node/commands.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | import click
4 | from virl2_client import NodeNotFound
5 |
6 | from virl.api import VIRLServer
7 | from virl.helpers import (get_cml_client, get_current_lab,
8 | safe_join_existing_lab)
9 |
10 |
11 | @click.command()
12 | @click.option("--force/--no-force", "-f", default=False, required=False, help="Stop a node (if it's started) then wipe it (default: False)")
13 | @click.option(
14 | "--confirm/--no-confirm",
15 | show_default=False,
16 | default=True,
17 | help="Do not prompt for confirmation (default: prompt)",
18 | required=False,
19 | )
20 | @click.argument("node", nargs=1)
21 | def node(node, force, confirm):
22 | """
23 | wipe a node
24 | """
25 | server = VIRLServer()
26 | client = get_cml_client(server)
27 |
28 | current_lab = get_current_lab()
29 | if current_lab:
30 | lab = safe_join_existing_lab(current_lab, client)
31 | if lab:
32 | try:
33 | node_obj = lab.get_node_by_label(node)
34 | except NodeNotFound:
35 | click.secho("Node {} was not found in lab {}".format(node, current_lab), fg="red")
36 | exit(1)
37 |
38 | if node_obj.is_active() and force:
39 | node_obj.stop()
40 | while node_obj.is_active():
41 | time.sleep(1)
42 |
43 | if not node_obj.is_active():
44 | ret = "y"
45 | if confirm:
46 | ret = input("Are you sure you want to wipe node {} [y/N]? ".format(node_obj.label))
47 | if ret.lower().startswith("y"):
48 | node_obj.wipe(wait=True)
49 | click.secho("Node {} wiped".format(node_obj.label))
50 | else:
51 | click.secho("Not wiping node {}".format(node_obj.label))
52 | else:
53 | click.secho("Node {} is active; either stop it or use --force".format(node_obj.label), fg="red")
54 | exit(1)
55 | else:
56 | click.secho("Unable to find lab {}".format(current_lab), fg="red")
57 | exit(1)
58 | else:
59 | click.secho("No current lab set", fg="red")
60 | exit(1)
61 |
--------------------------------------------------------------------------------
/virl/generators/__init__.py:
--------------------------------------------------------------------------------
1 | from .ansible_inventory import ansible_inventory_generator # noqa
2 | from .nso_payload import nso_payload_generator # noqa
3 | from .pyats_testbed import pyats_testbed_generator # noqa
4 |
--------------------------------------------------------------------------------
/virl/generators/ansible_inventory.py:
--------------------------------------------------------------------------------
1 | from jinja2 import Environment, PackageLoader
2 |
3 | from virl.helpers import get_node_mgmt_ip
4 |
5 |
6 | def generate_inventory_dict(lab, server):
7 | """
8 | common inventory info accross yaml/ini
9 | """
10 | # create inventory skeleton
11 | inventory = dict()
12 | inventory["all"] = dict()
13 | inventory["all"]["children"] = dict()
14 | inventory["all"]["hosts"] = dict()
15 |
16 | for node in lab.nodes():
17 | mgmtip = get_node_mgmt_ip(node)
18 |
19 | if not mgmtip:
20 | continue
21 |
22 | name = node.label
23 | entry = dict()
24 | entry["ansible_host"] = mgmtip
25 | # map console ports if they are available
26 | entry["console_server"] = server.host
27 | entry["console_user"] = server.user
28 | entry["console_path"] = "/{}/{}/0".format(lab.id, node.id)
29 |
30 | # determine device/os type
31 | try:
32 | type = node.node_definition.lower()
33 | if "nx" in type:
34 | entry["device_type"] = "nxos"
35 | elif "xr" in type:
36 | entry["device_type"] = "iosxr"
37 | elif "csr" in type:
38 | entry["device_type"] = "ios"
39 | elif "ios" in type:
40 | entry["device_type"] = "ios"
41 | elif "asa" in type:
42 | entry["device_type"] = "asa"
43 | else:
44 | entry["device_type"] = "unknown"
45 |
46 | except KeyError:
47 | entry["device_type"] = "unknown"
48 |
49 | ansible_group = None
50 | for tag in node.tags():
51 | if tag.startswith("ansible_group="):
52 | ansible_group = tag.split("=")[1].strip()
53 | break
54 |
55 | # try to map to ansible group
56 | if ansible_group:
57 | print("Placing {} into ansible group {}".format(name, ansible_group))
58 | if ansible_group not in inventory["all"]["children"]:
59 | inventory["all"]["children"][ansible_group] = dict()
60 | if name not in inventory["all"]["children"]:
61 | inventory["all"]["children"][ansible_group][name] = entry
62 | else:
63 | inventory["all"]["hosts"][name] = entry
64 |
65 | return inventory
66 |
67 |
68 | def render_inventory(lab, server, style):
69 | """
70 | render the YAML version of the inventory
71 | """
72 |
73 | j2_env = Environment(loader=PackageLoader("virl"), trim_blocks=False)
74 |
75 | inventory = generate_inventory_dict(lab, server)
76 | template = None
77 | if style == "ini":
78 | template = j2_env.get_template("ansible/inventory_ini_template.j2")
79 | elif style == "yaml":
80 | template = j2_env.get_template("ansible/inventory_template.j2")
81 |
82 | if template:
83 | return template.render(inventory=inventory, lab_id=lab.id, lab_title=lab.title)
84 |
85 | return None
86 |
87 |
88 | def ansible_inventory_generator(lab, server, style="yaml"):
89 | """
90 | given a lab produces an inventory file suitable for use with ansible
91 | """
92 | inv = render_inventory(lab, server, style)
93 |
94 | return inv
95 |
--------------------------------------------------------------------------------
/virl/generators/nso_payload.py:
--------------------------------------------------------------------------------
1 | from jinja2 import Environment, PackageLoader
2 |
3 | from virl.helpers import get_node_mgmt_ip
4 |
5 |
6 | def lab_info(lab, server, protocol):
7 | """
8 | common inventory info across xml/json
9 | """
10 | inventory = list()
11 |
12 | for node in lab.nodes():
13 | mgmtip = get_node_mgmt_ip(node)
14 |
15 | if not mgmtip:
16 | continue
17 |
18 | name = node.label
19 | entry = dict()
20 | entry["address"] = mgmtip
21 | entry["protocol"] = protocol
22 | name = node.label
23 | entry["name"] = name
24 | entry["ned"] = "unknown"
25 | entry["ns"] = "unkown"
26 |
27 | # determine device/os type
28 | try:
29 | node_type = node.node_definition.lower()
30 | if "nx" in node_type:
31 | entry["prefix"] = "{{ NX_PREFIX }}"
32 | entry["ned"] = "{{ NX_NED_ID }}"
33 | entry["ns"] = "{{ NX_NAMESPACE }}"
34 | elif "xr" in node_type:
35 | entry["prefix"] = "{{ XR_PREFIX }}"
36 | entry["ned"] = "{{ XR_NED_ID }}"
37 | entry["ns"] = "{{ XR_NAMESPACE }}"
38 | elif "csr" in node_type or "ios" in node_type or "cat" in node_type:
39 | entry["prefix"] = "{{ IOS_PREFIX }}"
40 | entry["ned"] = "{{ IOS_NED_ID }}"
41 | entry["ns"] = "{{ IOS_NAMESPACE }}"
42 | elif "asa" in node_type:
43 | entry["prefix"] = "{{ ASA_PREFIX }}"
44 | entry["ned"] = "{{ ASA_NED_ID }}"
45 | entry["ns"] = "{{ ASA_NAMESPACE }}"
46 | except KeyError:
47 | pass
48 |
49 | if entry.get("ned", None) not in ["unknown", None]:
50 | inventory.append(entry)
51 |
52 | return inventory
53 |
54 |
55 | def render_payload(lab, server, protocol, style):
56 | env = Environment(loader=PackageLoader("virl"), trim_blocks=False)
57 |
58 | if style == "json": # pragma: no cover
59 | raise NotImplementedError
60 |
61 | inventory = lab_info(lab, server, protocol)
62 | payload = env.get_template("nso/xml_payload.j2").render(inventory=inventory)
63 | return payload
64 |
65 |
66 | def nso_payload_generator(lab, server, style="xml", protocol="ssh"):
67 | """
68 | given a lab produces a payload file suitable for
69 | use with network services orchestrator
70 | """
71 | payload = render_payload(lab, server, protocol, style)
72 |
73 | return payload
74 |
--------------------------------------------------------------------------------
/virl/generators/pyats_testbed.py:
--------------------------------------------------------------------------------
1 | def pyats_testbed_generator(lab):
2 | return lab.get_pyats_testbed()
3 |
--------------------------------------------------------------------------------
/virl/templates/ansible/inventory_ini_template.j2:
--------------------------------------------------------------------------------
1 | # cmlutils generated ansible file for lab id: {{ lab_id }}, title: {{ lab_title }}
2 | #
3 | # the overall structure of the inventory follows best practices
4 | # at http://docs.ansible.com/ansible/latest/intro_inventory.html
5 |
6 | # we've rendered what we think is best if you disagree, override
7 | # virl.generators.ansible_inventory_generator
8 |
9 | # you can modify grouping behavior by adding a tag to your nodes:
10 | # ansible_group=GROUP_NAME
11 |
12 | {%- for group, devices in inventory.all.children.items() %}
13 | [{{group}}]
14 | {%- for name, device in devices.items() %}
15 | {{ name }} ansible_host={{ device.ansible_host }}{%- if device.console_server %}{%- if device.console_user %}{%- if device.console_path %} console_server={{ device.console_server }} console_user={{ device.console_user }} console_path={{ device.console_path }}{%- endif %}{%- endif %}{%- endif %}{%- if device.device_type %} ansible_network_os={{ device.device_type }}{%- endif %}{%- endfor %}{% endfor %}
16 |
--------------------------------------------------------------------------------
/virl/templates/ansible/inventory_template.j2:
--------------------------------------------------------------------------------
1 | # cmlutils generated ansible file for lab id: {{ lab_id }}, title: {{ lab_title }}
2 | #
3 | # the overall structure of the inventory follows best practices
4 | # at http://docs.ansible.com/ansible/latest/intro_inventory.html
5 |
6 | # we've rendered what we think is best if you disagree, override
7 | # virl.generators.ansible_inventory_generator
8 |
9 | # you can modify grouping behavior by adding a tag to your nodes:
10 | # ansible_group=GROUP_NAME
11 |
12 | all:
13 | children:
14 |
15 | {%- for group, devices in inventory.all.children.items() %}
16 | {{group}}:
17 | hosts:
18 | {%- for name, device in devices.items() %}
19 |
20 | {{ name }}:
21 | ansible_host: {{ device.ansible_host }}
22 | {%- if device.console_server %}
23 | {%- if device.console_user %}
24 | {%- if device.console_path %}
25 | console_server: {{ device.console_server }}
26 | console_user: {{ device.console_user }}
27 | console_path: {{ device.console_path }}
28 | {%- endif %}
29 | {%- endif %}
30 | {%- endif %}
31 | {%- if device.device_type %}
32 | ansible_network_os: {{ device.device_type }}
33 | {%- endif %}
34 |
35 | {%- endfor %}
36 | {% endfor %}
37 |
--------------------------------------------------------------------------------
/virl/templates/nso/xml_payload.j2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | virl
5 |
6 | cisco
7 | cisco
8 |
9 |
10 | admin
11 | cisco
12 | cisco
13 |
14 |
15 |
16 | {% for node in inventory %}
17 |
18 | {{ node.name }}
19 | {{ node.address }}
20 |
21 | none
22 |
23 | virl
24 |
25 |
26 | {{ node.prefix}}:{{ node.ned }}
27 | {{ node.protocol }}
28 |
29 |
30 |
31 | unlocked
32 |
33 |
34 | {% endfor %}
35 |
36 |
--------------------------------------------------------------------------------
/virl/templates/pyats/testbed_yaml.j2:
--------------------------------------------------------------------------------
1 | testbed:
2 |
3 | name: {{ name }}
4 |
5 | tacacs:
6 | username: "%ENV{PYATS_USERNAME}"
7 | passwords:
8 | tacacs: "%ENV{PYATS_PASSWORD}"
9 | enable: "%ENV{PYATS_AUTH_PASS}"
10 | line: "%ENV{PYATS_PASSWORD}"
11 |
12 | {%- if servers.items() %}
13 | servers:
14 | {%- for srv_name, props in servers.items() %}
15 | {{ srv_name }}:
16 | address: {{ props.address|default("''", true) }}
17 | server: {{ props.server|default("''", true) }}
18 | {%- endfor %}
19 | {%-endif %}
20 |
21 | devices:
22 | {%- for dev_name, props in devices.items() %}
23 |
24 | {{ dev_name }}:
25 | alias: {{ props.alias|default("''", true) }}
26 | {%- if props.os %}
27 | os: {{ props.os }}
28 | {%- endif %}
29 | type: {{ props.type|default("''", true) }}
30 | platform: {{ props.type|default("unknown", true) }}
31 |
32 | connections:
33 |
34 | defaults:
35 | class: {{ conn_class }}
36 |
37 | {%- for key, props in props.connections.items() %}
38 | {{ key }}:
39 | {%- for prop, prop_value in props.items() %}
40 | {%- if prop and prop_value %}
41 | {{ prop }}: {{ prop_value }}
42 | {%- endif %}
43 |
44 | {%- endfor %}
45 | {%- endfor %}
46 | custom:
47 | abstraction:
48 | order: [os, type]
49 | {%- endfor %}
50 |
51 | topology:
52 | {%- for dev, dev_ints in topology.items() %}
53 | {%- if dev in devices %}
54 | {{dev}}:
55 | interfaces:
56 | {%- for id, interface in dev_ints.items() %}
57 | {{ interface.name }}:
58 | {%- if interface['ip-address'] %}
59 | ipv4: {{ interface['ip-address'] }}
60 | {%- endif %}
61 | {%- if interface['network'] %}
62 | link: {{ interface['network'] }}
63 | {%- endif %}
64 | {%- if interface['type'] and 'oop' in interface['type'] %}
65 | type: loopback
66 | {%- else %}
67 | type: ethernet
68 | {%- endif %}
69 |
70 | {%- endfor %}
71 | {%- endif %}
72 | {%- endfor %}
73 |
--------------------------------------------------------------------------------