commit 5044249e1f6cc5cbcf3bde84ce58f25065645cf5 Author: Ansible Core Team <info@ansible.com> Date: Mon Mar 9 13:35:35 2020 +0000 Initial commit diff --git a/.github/workflows/collection-continuous-integration.yml b/.github/workflows/collection-continuous-integration.yml new file mode 100644 index 0000000..0190a66 --- /dev/null +++ b/.github/workflows/collection-continuous-integration.yml @@ -0,0 +1,308 @@ +name: Collection test suite + +on: + push: + pull_request: + schedule: + - cron: 3 0 * * * # Run daily at 0:03 UTC + +jobs: + build-collection-artifact: + name: Build collection + runs-on: ${{ matrix.runner-os }} + strategy: + matrix: + runner-os: + - ubuntu-latest + ansible-version: + - git+https://github.com/ansible/ansible.git@devel + runner-python-version: + - 3.8 + steps: + - name: Check out ${{ github.repository }} on disk + uses: actions/checkout@master + - name: Set up Python ${{ matrix.runner-python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.runner-python-version }} + - name: Set up pip cache + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('tests/sanity/requirements.txt') }}-${{ hashFiles('tests/unit/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Ansible ${{ matrix.ansible-version }} + run: >- + python -m + pip + install + --user + ${{ matrix.ansible-version }} + - name: Build a collection tarball + run: >- + ~/.local/bin/ansible-galaxy + collection + build + --output-path + "${GITHUB_WORKSPACE}/.cache/collection-tarballs" + - name: Store migrated collection artifacts + uses: actions/upload-artifact@v1 + with: + name: >- + collection + path: .cache/collection-tarballs + + sanity-test-collection-via-vms: + name: Sanity in VM ${{ matrix.os.vm || 'ubuntu-latest' }} + needs: + - build-collection-artifact + runs-on: ${{ matrix.os.vm || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: + ansible-version: + - git+https://github.com/ansible/ansible.git@devel + os: + - vm: ubuntu-latest + - vm: ubuntu-16.04 + - vm: macos-latest + python-version: + - 3.8 + - 3.7 + - 3.6 + - 3.5 + - 2.7 + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Set up pip cache + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ github.ref }}-sanity-VMs + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Ansible ${{ matrix.ansible-version }} + run: >- + python -m + pip + install + --user + ${{ matrix.ansible-version }} + - name: Download migrated collection artifacts + uses: actions/download-artifact@v1 + with: + name: >- + collection + path: .cache/collection-tarballs + - name: Install the collection tarball + run: >- + ~/.local/bin/ansible-galaxy + collection + install + .cache/collection-tarballs/*.tar.gz + - name: Run collection sanity tests + run: >- + ~/.local/bin/ansible-test + sanity + --color + --requirements + --venv + --python + "${{ matrix.python-version }}" + -vvv + working-directory: >- + /${{ runner.os == 'Linux' && 'home' || 'Users' }}/runner/.ansible/collections/ansible_collections/dellemc_networking/os9 + + sanity-test-collection-via-containers: + name: Sanity in container via Python ${{ matrix.python-version }} + needs: + - build-collection-artifact + runs-on: ${{ matrix.runner-os }} + strategy: + fail-fast: false + matrix: + runner-os: + - ubuntu-latest + runner-python-version: + - 3.8 + ansible-version: + - git+https://github.com/ansible/ansible.git@devel + python-version: + - 3.8 + - 2.7 + - 3.7 + - 3.6 + - 3.5 + - 2.6 + steps: + - name: Set up Python ${{ matrix.runner-python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.runner-python-version }} + - name: Set up pip cache + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ github.ref }}-sanity-containers + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Ansible ${{ matrix.ansible-version }} + run: >- + python -m + pip + install + --user + ${{ matrix.ansible-version }} + - name: Download migrated collection artifacts + uses: actions/download-artifact@v1 + with: + name: >- + collection + path: .cache/collection-tarballs + - name: Install the collection tarball + run: >- + ~/.local/bin/ansible-galaxy + collection + install + .cache/collection-tarballs/*.tar.gz + - name: Run collection sanity tests + run: >- + ~/.local/bin/ansible-test + sanity + --color + --requirements + --docker + --python + "${{ matrix.python-version }}" + -vvv + working-directory: >- + /home/runner/.ansible/collections/ansible_collections/dellemc_networking/os9 + + unit-test-collection-via-vms: + name: Units in VM ${{ matrix.os.vm || 'ubuntu-latest' }} + needs: + - build-collection-artifact + runs-on: ${{ matrix.os.vm || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: + ansible-version: + - git+https://github.com/ansible/ansible.git@devel + os: + - vm: ubuntu-latest + - vm: ubuntu-16.04 + - vm: macos-latest + python-version: + - 3.8 + - 3.7 + - 3.6 + - 3.5 + - 2.7 + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Set up pip cache + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ github.ref }}-units-VMs + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Ansible ${{ matrix.ansible-version }} + run: >- + python -m + pip + install + --user + ${{ matrix.ansible-version }} + - name: Download migrated collection artifacts + uses: actions/download-artifact@v1 + with: + name: >- + collection + path: .cache/collection-tarballs + - name: Install the collection tarball + run: >- + ~/.local/bin/ansible-galaxy + collection + install + .cache/collection-tarballs/*.tar.gz + - name: Run collection unit tests + run: | + [[ ! -d 'tests/unit' ]] && echo This collection does not have unit tests. Skipping... || \ + ~/.local/bin/ansible-test units --color --coverage --requirements --venv --python "${{ matrix.python-version }}" -vvv + working-directory: >- + /${{ runner.os == 'Linux' && 'home' || 'Users' }}/runner/.ansible/collections/ansible_collections/dellemc_networking/os9 + + unit-test-collection-via-containers: + name: Units in container ${{ matrix.container-image }} + needs: + - build-collection-artifact + runs-on: ${{ matrix.runner-os }} + strategy: + fail-fast: false + matrix: + runner-os: + - ubuntu-latest + runner-python-version: + - 3.8 + ansible-version: + - git+https://github.com/ansible/ansible.git@devel + container-image: + - fedora31 + - ubuntu1804 + - centos8 + - opensuse15 + - fedora30 + - centos7 + - opensuse15py2 + - ubuntu1604 + - centos6 + steps: + - name: Set up Python ${{ matrix.runner-python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.runner-python-version }} + - name: Set up pip cache + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ github.ref }}-units-containers + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Ansible ${{ matrix.ansible-version }} + run: >- + python -m + pip + install + --user + ${{ matrix.ansible-version }} + - name: Download migrated collection artifacts + uses: actions/download-artifact@v1 + with: + name: >- + collection + path: .cache/collection-tarballs + - name: Install the collection tarball + run: >- + ~/.local/bin/ansible-galaxy + collection + install + .cache/collection-tarballs/*.tar.gz + - name: Run collection unit tests + run: | + [[ ! -d 'tests/unit' ]] && echo This collection does not have unit tests. Skipping... || \ + ~/.local/bin/ansible-test units --color --coverage --requirements --docker "${{ matrix.container-image }}" -vvv + working-directory: >- + /home/runner/.ansible/collections/ansible_collections/dellemc_networking/os9 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6fc14a --- /dev/null +++ b/.gitignore @@ -0,0 +1,387 @@ + +# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv +# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv + +### dotenv ### +.env + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +#!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!# + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### PyCharm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### pydev ### +.pydevproject + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### WebStorm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/**/sonarlint/ + +# SonarQube Plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..10926e8 --- /dev/null +++ b/COPYING @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cab959 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +[](https://github.com/ansible-collection-migration/dellemc_networking.os9/actions?query=workflow%3A%22Collection%20test%20suite%22) + +Ansible Collection: dellemc_networking.os9 +================================================= \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..158937a --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,15 @@ +namespace: dellemc_networking +name: os9 +version: 0.1.0 +readme: README.md +authors: null +description: null +license: GPL-3.0-or-later +license_file: COPYING +tags: null +dependencies: + ansible.netcommon: '>=0.1.0' +repository: git@github.com:ansible-collection-migration/dellemc_networking.os9.git +documentation: https://github.com/ansible-collection-migration/dellemc_networking.os9/tree/master/docs +homepage: https://github.com/ansible-collection-migration/dellemc_networking.os9 +issues: https://github.com/ansible-collection-migration/dellemc_networking.os9/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc diff --git a/plugins/action/__init__.py b/plugins/action/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/action/dellos9.py b/plugins/action/dellos9.py new file mode 100644 index 0000000..7e18a5f --- /dev/null +++ b/plugins/action/dellos9.py @@ -0,0 +1,81 @@ +# +# (c) 2016 Red Hat Inc. +# +# Copyright (c) 2017 Dell Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import copy + +from ansible import constants as C +from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_provider_spec +from ansible.utils.display import Display + +display = Display() + + +class ActionModule(ActionNetworkModule): + + def run(self, tmp=None, task_vars=None): + del tmp # tmp no longer has any effect + + module_name = self._task.action.split('.')[-1] + self._config_module = True if module_name == 'dellos9_config' else False + socket_path = None + persistent_connection = self._play_context.connection.split('.')[-1] + + if persistent_connection == 'network_cli': + provider = self._task.args.get('provider', {}) + if any(provider.values()): + display.warning('provider is unnecessary when using network_cli and will be ignored') + del self._task.args['provider'] + elif self._play_context.connection == 'local': + provider = load_provider(dellos9_provider_spec, self._task.args) + pc = copy.deepcopy(self._play_context) + pc.connection = 'network_cli' + pc.network_os = 'dellos9' + pc.remote_addr = provider['host'] or self._play_context.remote_addr + pc.port = int(provider['port'] or self._play_context.port or 22) + pc.remote_user = provider['username'] or self._play_context.connection_user + pc.password = provider['password'] or self._play_context.password + pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file + command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT) + pc.become = provider['authorize'] or False + if pc.become: + pc.become_method = 'enable' + pc.become_pass = provider['auth_pass'] + + display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr) + connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid) + connection.set_options(direct={'persistent_command_timeout': command_timeout}) + + socket_path = connection.run() + display.vvvv('socket_path: %s' % socket_path, pc.remote_addr) + if not socket_path: + return {'failed': True, + 'msg': 'unable to open shell. Please see: ' + + 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'} + + task_vars['ansible_socket'] = socket_path + + result = super(ActionModule, self).run(task_vars=task_vars) + return result diff --git a/plugins/cliconf/__init__.py b/plugins/cliconf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/cliconf/dellos9.py b/plugins/cliconf/dellos9.py new file mode 100644 index 0000000..b360807 --- /dev/null +++ b/plugins/cliconf/dellos9.py @@ -0,0 +1,122 @@ +# +# (c) 2017 Red Hat Inc. +# +# (c) 2017 Dell EMC. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +cliconf: dellos9 +short_description: Use dellos9 cliconf to run command on Dell OS9 platform +description: + - This dellos9 plugin provides low level abstraction apis for + sending and receiving CLI commands from Dell OS9 network devices. +''' + +import re +import json + +from itertools import chain + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_bytes, to_text +from ansible.module_utils.common._collections_compat import Mapping +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible.plugins.cliconf import CliconfBase, enable_mode + + +class Cliconf(CliconfBase): + + def get_device_info(self): + device_info = {} + + device_info['network_os'] = 'dellos9' + reply = self.get('show version') + data = to_text(reply, errors='surrogate_or_strict').strip() + + match = re.search(r'Software Version (\S+)', data) + if match: + device_info['network_os_version'] = match.group(1) + + match = re.search(r'System Type (\S+)', data, re.M) + if match: + device_info['network_os_model'] = match.group(1) + + reply = self.get('show running-config | grep hostname') + data = to_text(reply, errors='surrogate_or_strict').strip() + match = re.search(r'^hostname (.+)', data, re.M) + if match: + device_info['network_os_hostname'] = match.group(1) + + return device_info + + @enable_mode + def get_config(self, source='running', format='text', flags=None): + if source not in ('running', 'startup'): + return self.invalid_params("fetching configuration from %s is not supported" % source) +# if source == 'running': +# cmd = 'show running-config all' + else: + cmd = 'show startup-config' + return self.send_command(cmd) + + @enable_mode + def edit_config(self, command): + for cmd in chain(['configure terminal'], to_list(command), ['end']): + self.send_command(cmd) + + def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False): + return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all) + + def get_capabilities(self): + result = super(Cliconf, self).get_capabilities() + return json.dumps(result) + + def run_commands(self, commands=None, check_rc=True): + if commands is None: + raise ValueError("'commands' value is required") + + responses = list() + for cmd in to_list(commands): + if not isinstance(cmd, Mapping): + cmd = {'command': cmd} + + output = cmd.pop('output', None) + if output: + raise ValueError("'output' value %s is not supported for run_commands" % output) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, 'err', to_text(e)) + + responses.append(out) + + return responses + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context(config_context=')#') diff --git a/plugins/doc_fragments/__init__.py b/plugins/doc_fragments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/doc_fragments/dellos9.py b/plugins/doc_fragments/dellos9.py new file mode 100644 index 0000000..e65e53c --- /dev/null +++ b/plugins/doc_fragments/dellos9.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com> +# Copyright: (c) 2016, Dell Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + provider: + description: + - A dict object containing connection details. + type: dict + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + type: str + required: true + port: + description: + - Specifies the port to use when building the connection to the remote + device. + type: int + default: 22 + username: + description: + - User to authenticate the SSH session to the remote device. If the + value is not specified in the task, the value of environment variable + C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - Password to authenticate the SSH session to the remote device. If the + value is not specified in the task, the value of environment variable + C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + ssh_keyfile: + description: + - Path to an ssh key used to authenticate the SSH session to the remote + device. If the value is not specified in the task, the value of + environment variable C(ANSIBLE_NET_SSH_KEYFILE) will be used instead. + type: path + timeout: + description: + - Specifies idle timeout (in seconds) for the connection. Useful if the + console freezes before continuing. For example when saving + configurations. + type: int + default: 10 +notes: + - For more information on using Ansible to manage Dell EMC Network devices see U(https://www.ansible.com/ansible-dell-networking). +''' diff --git a/plugins/module_utils/__init__.py b/plugins/module_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/dellos9/dellos9.py b/plugins/module_utils/network/dellos9/dellos9.py new file mode 100644 index 0000000..cdbd1ad --- /dev/null +++ b/plugins/module_utils/network/dellos9/dellos9.py @@ -0,0 +1,166 @@ +# +# (c) 2015 Peter Sprygada, <psprygada@ansible.com> +# (c) 2017 Red Hat, Inc +# +# Copyright (c) 2016 Dell Inc. +# +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +import json + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, ConfigLine + +_DEVICE_CONFIGS = {} + +WARNING_PROMPTS_RE = [ + r"[\r\n]?\[confirm yes/no\]:\s?$", + r"[\r\n]?\[y/n\]:\s?$", + r"[\r\n]?\[yes/no\]:\s?$" +] + +dellos9_provider_spec = { + 'host': dict(), + 'port': dict(type='int'), + 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])), + 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True), + 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'), + 'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'), + 'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True), + 'timeout': dict(type='int'), +} +dellos9_argument_spec = { + 'provider': dict(type='dict', options=dellos9_provider_spec), +} +dellos9_top_spec = { + 'host': dict(removed_in_version=2.9), + 'port': dict(removed_in_version=2.9, type='int'), + 'username': dict(removed_in_version=2.9), + 'password': dict(removed_in_version=2.9, no_log=True), + 'ssh_keyfile': dict(removed_in_version=2.9, type='path'), + 'authorize': dict(removed_in_version=2.9, type='bool'), + 'auth_pass': dict(removed_in_version=2.9, no_log=True), + 'timeout': dict(removed_in_version=2.9, type='int'), +} +dellos9_argument_spec.update(dellos9_top_spec) + + +def get_provider_argspec(): + return dellos9_provider_spec + + +def get_connection(module): + if hasattr(module, '_dellos9_connection'): + return module._dellos9_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._dellos9_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._dellos9_connection + + +def get_capabilities(module): + if hasattr(module, '_dellos9_capabilities'): + return module._dellos9_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._dellos9_capabilities = json.loads(capabilities) + return module._dellos9_capabilities + + +def check_args(module, warnings): + pass + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + cmd = 'show running-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + rc, out, err = exec_command(module, cmd) + if rc != 0: + module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_or_strict')) + cfg = to_text(out, errors='surrogate_or_strict').strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def load_config(module, commands): + rc, out, err = exec_command(module, 'configure terminal') + if rc != 0: + module.fail_json(msg='unable to enter configuration mode', err=to_text(err, errors='surrogate_or_strict')) + + for command in to_list(commands): + if command == 'end': + continue + rc, out, err = exec_command(module, command) + if rc != 0: + module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), command=command, rc=rc) + + exec_command(module, 'end') + + +def get_sublevel_config(running_config, module): + contents = list() + current_config_contents = list() + running_config = NetworkConfig(contents=running_config, indent=1) + obj = running_config.get_object(module.params['parents']) + if obj: + contents = obj.children + contents[:0] = module.params['parents'] + + indent = 0 + for c in contents: + if isinstance(c, str): + current_config_contents.append(c.rjust(len(c) + indent, ' ')) + if isinstance(c, ConfigLine): + current_config_contents.append(c.raw) + indent = 1 + sublevel_config = '\n'.join(current_config_contents) + + return sublevel_config diff --git a/plugins/modules/__init__.py b/plugins/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/modules/dellos9_command.py b/plugins/modules/dellos9_command.py new file mode 100644 index 0000000..9b4d77f --- /dev/null +++ b/plugins/modules/dellos9_command.py @@ -0,0 +1,239 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com> +# Copyright: (c) 2016, Dell Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: dellos9_command +author: "Dhivya P (@dhivyap)" +short_description: Run commands on remote devices running Dell OS9 +description: + - Sends arbitrary commands to a Dell OS9 node and returns the results + read from the device. This module includes an + argument that will cause the module to wait for a specific condition + before returning or timing out if the condition is not met. + - This module does not support running commands in configuration mode. + Please use M(dellos9_config) to configure Dell OS9 devices. +extends_documentation_fragment: +- dellemc_networking.os9.dellos9 + +options: + commands: + description: + - List of commands to send to the remote dellos9 device over the + configured provider. The resulting output from the command + is returned. If the I(wait_for) argument is provided, the + module is not returned until the condition is satisfied or + the number of retries has expired. If a command sent to the + device requires answering a prompt, it is possible to pass + a dict containing I(command), I(answer) and I(prompt). + Common answers are 'yes' or "\r" (carriage return, must be + double quotes). See examples. + type: list + required: true + wait_for: + description: + - List of conditions to evaluate against the output of the + command. The task will wait for each condition to be true + before moving forward. If the conditional is not true + within the configured number of retries, the task fails. + See examples. + type: list + match: + description: + - The I(match) argument is used in conjunction with the + I(wait_for) argument to specify the match policy. Valid + values are C(all) or C(any). If the value is set to C(all) + then all conditionals in the wait_for must be satisfied. If + the value is set to C(any) then only one of the values must be + satisfied. + type: str + default: all + choices: [ 'all', 'any' ] + retries: + description: + - Specifies the number of retries a command should be tried + before it is considered failed. The command is run on the + target device every retry and evaluated against the + I(wait_for) conditions. + type: int + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries + of the command. If the command does not pass the specified + conditions, the interval indicates how long to wait before + trying the command again. + type: int + default: 1 + +notes: + - This module requires Dell OS9 version 9.10.0.1P13 or above. + + - This module requires to increase the ssh connection rate limit. + Use the following command I(ip ssh connection-rate-limit 60) + to configure the same. This can be done via M(dellos9_config) module + as well. + +''' + +EXAMPLES = """ +tasks: + - name: run show version on remote devices + dellos9_command: + commands: show version + + - name: run show version and check to see if output contains OS9 + dellos9_command: + commands: show version + wait_for: result[0] contains OS9 + + - name: run multiple commands on remote nodes + dellos9_command: + commands: + - show version + - show interfaces + + - name: run multiple commands and evaluate the output + dellos9_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains OS9 + - result[1] contains Loopback + + - name: run commands that require answering a prompt + dellos9_command: + commands: + - command: 'copy running-config startup-config' + prompt: '[confirm yes/no]: ?$' + answer: 'yes' +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +warnings: + description: The list of warnings (if any) generated by module based on arguments + returned: always + type: list + sample: ['...', '...'] +""" +import time + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import transform_commands, to_lines +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import run_commands +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args + + +def parse_commands(module, warnings): + commands = transform_commands(module) + + if module.check_mode: + for item in list(commands): + if not item['command'].startswith('show'): + warnings.append( + 'Only show commands are supported when using check mode, not ' + 'executing %s' % item['command'] + ) + commands.remove(item) + + return commands + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + # { command: <str>, prompt: <str>, response: <str> } + commands=dict(type='list', required=True), + + wait_for=dict(type='list'), + match=dict(default='all', choices=['all', 'any']), + + retries=dict(default=10, type='int'), + interval=dict(default=1, type='int') + ) + + argument_spec.update(dellos9_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = {'changed': False} + + warnings = list() + check_args(module, warnings) + commands = parse_commands(module, warnings) + result['warnings'] = warnings + + wait_for = module.params['wait_for'] or list() + + try: + conditionals = [Conditional(c) for c in wait_for] + except AttributeError as exc: + module.fail_json(msg=to_text(exc)) + retries = module.params['retries'] + interval = module.params['interval'] + match = module.params['match'] + + while retries > 0: + responses = run_commands(module, commands) + + for item in list(conditionals): + if item(responses): + if match == 'any': + conditionals = list() + break + conditionals.remove(item) + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = 'One or more conditional statements have not been satisfied' + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result.update({ + 'stdout': responses, + 'stdout_lines': list(to_lines(responses)) + }) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dellos9_config.py b/plugins/modules/dellos9_config.py new file mode 100644 index 0000000..16d92ef --- /dev/null +++ b/plugins/modules/dellos9_config.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# +# (c) 2015 Peter Sprygada, <psprygada@ansible.com> +# Copyright (c) 2016 Dell Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: dellos9_config +author: "Dhivya P (@dhivyap)" +short_description: Manage Dell EMC Networking OS9 configuration sections +description: + - OS9 configurations use a simple block indent file syntax + for segmenting configuration into sections. This module provides + an implementation for working with OS9 configuration sections in + a deterministic way. +extends_documentation_fragment: +- dellemc_networking.os9.dellos9 + +options: + lines: + description: + - The ordered set of commands that should be configured in the + section. The commands must be the exact same commands as found + in the device running-config. Be sure to note the configuration + command syntax as some commands are automatically modified by the + device config parser. This argument is mutually exclusive with I(src). + aliases: ['commands'] + parents: + description: + - The ordered set of parents that uniquely identify the section or hierarchy + the commands should be checked against. If the parents argument + is omitted, the commands are checked against the set of top + level or global commands. + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is + mutually exclusive with I(lines). + before: + description: + - The ordered set of commands to push on to the command stack if + a change needs to be made. This allows the playbook designer + the opportunity to perform configuration commands prior to pushing + any changes without affecting how the set of commands are matched + against the system. + after: + description: + - The ordered set of commands to append to the end of the command + stack if a change needs to be made. Just like with I(before) this + allows the playbook designer to append a set of commands to be + executed after the command set. + match: + description: + - Instructs the module on the way to perform the matching of + the set of commands against the current device config. If + match is set to I(line), commands are matched line by line. If + match is set to I(strict), command lines are matched with respect + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. + default: line + choices: ['line', 'strict', 'exact', 'none'] + replace: + description: + - Instructs the module on the way to perform the configuration + on the device. If the replace argument is set to I(line) then + the modified lines are pushed to the device in configuration + mode. If the replace argument is set to I(block) then the entire + command block is pushed to the device in configuration mode if any + line is not correct. + default: line + choices: ['line', 'block'] + update: + description: + - The I(update) argument controls how the configuration statements + are processed on the remote device. Valid choices for the I(update) + argument are I(merge) and I(check). When you set this argument to + I(merge), the configuration changes merge with the current + device running configuration. When you set this argument to I(check) + the configuration updates are determined but not actually configured + on the remote device. + default: merge + choices: ['merge', 'check'] + save: + description: + - The C(save) argument instructs the module to save the running- + config to the startup-config at the conclusion of the module + running. If check mode is specified, this argument is ignored. + type: bool + default: no + config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuration to use as the base + config for comparison. + backup: + description: + - This argument will cause the module to create a full backup of + the current C(running-config) from the remote device before any + changes are made. If the C(backup_options) value is not given, + the backup file is written to the C(backup) folder in the playbook + root directory. If the directory does not exist, it is created. + type: bool + default: 'no' + backup_options: + description: + - This is a dict object containing configurable options related to backup file path. + The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set + to I(no) this option will be silently ignored. + suboptions: + filename: + description: + - The filename to be used to store the backup configuration. If the filename + is not given it will be generated based on the hostname, current time and date + in format defined by <hostname>_config.<current-date>@<current-time> + dir_path: + description: + - This option provides the path ending with directory name in which the backup + configuration file will be stored. If the directory does not exist it will be first + created and the filename is either the value of C(filename) or default filename + as described in C(filename) options description. If the path value is not given + in that case a I(backup) directory will be created in the current working directory + and backup configuration will be copied in C(filename) within I(backup) directory. + type: path + type: dict +notes: + - This module requires Dell OS9 version 9.10.0.1P13 or above. + + - This module requires to increase the ssh connection rate limit. + Use the following command I(ip ssh connection-rate-limit 60) + to configure the same. This can also be done with the + M(dellos9_config) module. +''' + +EXAMPLES = """ +- dellos9_config: + lines: ['hostname {{ inventory_hostname }}'] + provider: "{{ cli }}" + +- dellos9_config: + lines: + - 10 permit ip host 1.1.1.1 any log + - 20 permit ip host 2.2.2.2 any log + - 30 permit ip host 3.3.3.3 any log + - 40 permit ip host 4.4.4.4 any log + - 50 permit ip host 5.5.5.5 any log + parents: ['ip access-list extended test'] + before: ['no ip access-list extended test'] + match: exact + +- dellos9_config: + lines: + - 10 permit ip host 1.1.1.1 any log + - 20 permit ip host 2.2.2.2 any log + - 30 permit ip host 3.3.3.3 any log + - 40 permit ip host 4.4.4.4 any log + parents: ['ip access-list extended test'] + before: ['no ip access-list extended test'] + replace: block + +- dellos9_config: + lines: ['hostname {{ inventory_hostname }}'] + provider: "{{ cli }}" + backup: yes + backup_options: + filename: backup.cfg + dir_path: /home/user +""" + +RETURN = """ +updates: + description: The set of commands that will be pushed to the remote device. + returned: always + type: list + sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1'] +commands: + description: The set of commands that will be pushed to the remote device + returned: always + type: list + sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1'] +saved: + description: Returns whether the configuration is saved to the startup + configuration or not. + returned: When not check_mode. + type: bool + sample: True +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: str + sample: /playbooks/ansible/backup/dellos9_config.2016-07-16@22:28:34 +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import get_config, get_sublevel_config +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import load_config, run_commands +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import WARNING_PROMPTS_RE +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps + + +def get_candidate(module): + candidate = NetworkConfig(indent=1) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + commands = module.params['lines'][0] + if (isinstance(commands, dict)) and (isinstance(commands['command'], list)): + candidate.add(commands['command'], parents=parents) + elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)): + candidate.add([commands['command']], parents=parents) + else: + candidate.add(module.params['lines'], parents=parents) + return candidate + + +def get_running_config(module): + contents = module.params['config'] + if not contents: + contents = get_config(module) + return contents + + +def main(): + + backup_spec = dict( + filename=dict(), + dir_path=dict(type='path') + ) + argument_spec = dict( + lines=dict(aliases=['commands'], type='list'), + parents=dict(type='list'), + + src=dict(type='path'), + + before=dict(type='list'), + after=dict(type='list'), + + match=dict(default='line', + choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + update=dict(choices=['merge', 'check'], default='merge'), + save=dict(type='bool', default=False), + config=dict(), + backup=dict(type='bool', default=False), + backup_options=dict(type='dict', options=backup_spec) + ) + + argument_spec.update(dellos9_argument_spec) + + mutually_exclusive = [('lines', 'src'), + ('parents', 'src')] + module = AnsibleModule(argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + parents = module.params['parents'] or list() + + match = module.params['match'] + replace = module.params['replace'] + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, saved=False, warnings=warnings) + + candidate = get_candidate(module) + + if module.params['backup']: + if not module.check_mode: + result['__backup__'] = get_config(module) + commands = list() + + if any((module.params['lines'], module.params['src'])): + if match != 'none': + config = get_running_config(module) + if parents: + contents = get_sublevel_config(config, module) + config = NetworkConfig(contents=contents, indent=1) + else: + config = NetworkConfig(contents=config, indent=1) + configobjs = candidate.difference(config, match=match, replace=replace) + else: + configobjs = candidate.items + + if configobjs: + commands = dumps(configobjs, 'commands') + if ((isinstance(module.params['lines'], list)) and + (isinstance(module.params['lines'][0], dict)) and + set(['prompt', 'answer']).issubset(module.params['lines'][0])): + + cmd = {'command': commands, + 'prompt': module.params['lines'][0]['prompt'], + 'answer': module.params['lines'][0]['answer']} + commands = [module.jsonify(cmd)] + else: + commands = commands.split('\n') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + if not module.check_mode and module.params['update'] == 'merge': + load_config(module, commands) + + result['changed'] = True + result['commands'] = commands + result['updates'] = commands + + if module.params['save']: + result['changed'] = True + if not module.check_mode: + cmd = {'command': 'copy running-config startup-config', + 'prompt': r'\[confirm yes/no\]:\s?$', 'answer': 'yes'} + run_commands(module, [cmd]) + result['saved'] = True + else: + module.warn('Skipping command `copy running-config startup-config`' + 'due to check_mode. Configuration not copied to ' + 'non-volatile storage') + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dellos9_facts.py b/plugins/modules/dellos9_facts.py new file mode 100644 index 0000000..98dea4c --- /dev/null +++ b/plugins/modules/dellos9_facts.py @@ -0,0 +1,575 @@ +#!/usr/bin/python +# +# (c) 2015 Peter Sprygada, <psprygada@ansible.com> +# Copyright (c) 2016 Dell Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: dellos9_facts +author: "Dhivya P (@dhivyap)" +short_description: Collect facts from remote devices running Dell EMC Networking OS9 +description: + - Collects a base set of device facts from a remote device that + is running OS9. This module prepends all of the + base network fact keys with C(ansible_net_<fact>). The facts + module will always collect a base set of facts from the device + and can enable or disable collection of additional facts. +extends_documentation_fragment: +- dellemc_networking.os9.dellos9 + +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, hardware, config, and interfaces. Can specify a list of + values to include a larger subset. Values can also be used + with an initial C(M(!)) to specify that a specific subset should + not be collected. + default: [ '!config' ] +notes: + - This module requires OS9 version 9.10.0.1P13 or above. + + - This module requires an increase of the SSH connection rate limit. + Use the following command I(ip ssh connection-rate-limit 60) + to configure the same. This can be also be done with the M(dellos9_config) module. +''' + +EXAMPLES = """ +# Collect all facts from the device +- dellos9_facts: + gather_subset: all + +# Collect only the config and default facts +- dellos9_facts: + gather_subset: + - config + +# Do not collect hardware facts +- dellos9_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_model: + description: The model name returned from the device + returned: always + type: str +ansible_net_serialnum: + description: The serial number of the remote device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_image: + description: The image file the device is running + returned: always + type: str + +# hardware +ansible_net_filesystems: + description: All file system names available on the device + returned: when hardware is configured + type: list +ansible_net_memfree_mb: + description: The available free memory on the remote device in Mb + returned: when hardware is configured + type: int +ansible_net_memtotal_mb: + description: The total memory on the remote device in Mb + returned: when hardware is configured + type: int + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_neighbors: + description: The list of LLDP neighbors from the remote device + returned: when interfaces is configured + type: dict +""" +import re +try: + from itertools import izip +except ImportError: + izip = zip + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import run_commands +from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands(self.module, self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(self.module, cmd, check_rc=False) + + +class Default(FactsBase): + + COMMANDS = [ + 'show version', + 'show inventory', + 'show running-config | grep hostname' + ] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + self.facts['version'] = self.parse_version(data) + self.facts['model'] = self.parse_model(data) + self.facts['image'] = self.parse_image(data) + + data = self.responses[1] + self.facts['serialnum'] = self.parse_serialnum(data) + + data = self.responses[2] + self.facts['hostname'] = self.parse_hostname(data) + + def parse_version(self, data): + match = re.search(r'Software Version:\s*(.+)', data) + if match: + return match.group(1) + + def parse_hostname(self, data): + match = re.search(r'^hostname (.+)', data, re.M) + if match: + return match.group(1) + + def parse_model(self, data): + match = re.search(r'^System Type:\s*(.+)', data, re.M) + if match: + return match.group(1) + + def parse_image(self, data): + match = re.search(r'image file is "(.+)"', data) + if match: + return match.group(1) + + def parse_serialnum(self, data): + for line in data.split('\n'): + if line.startswith('*'): + match = re.search( + r'\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', line, re.M) + if match: + return match.group(3) + + +class Hardware(FactsBase): + + COMMANDS = [ + 'show file-systems', + 'show memory | except Processor' + ] + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + self.facts['filesystems'] = self.parse_filesystems(data) + + data = self.responses[1] + match = re.findall(r'\s(\d+)\s', data) + if match: + self.facts['memtotal_mb'] = int(match[0]) // 1024 + self.facts['memfree_mb'] = int(match[2]) // 1024 + + def parse_filesystems(self, data): + return re.findall(r'\s(\S+):$', data, re.M) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + self.facts['config'] = self.responses[0] + + +class Interfaces(FactsBase): + + COMMANDS = [ + 'show interfaces', + 'show ipv6 interface', + 'show lldp neighbors detail', + 'show inventory' + ] + + def populate(self): + super(Interfaces, self).populate() + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + data = self.responses[0] + interfaces = self.parse_interfaces(data) + + for key in list(interfaces.keys()): + if "ManagementEthernet" in key: + temp_parsed = interfaces[key] + del interfaces[key] + interfaces.update(self.parse_mgmt_interfaces(temp_parsed)) + + for key in list(interfaces.keys()): + if "Vlan" in key: + temp_parsed = interfaces[key] + del interfaces[key] + interfaces.update(self.parse_vlan_interfaces(temp_parsed)) + + self.facts['interfaces'] = self.populate_interfaces(interfaces) + + data = self.responses[1] + if len(data) > 0: + data = self.parse_ipv6_interfaces(data) + self.populate_ipv6_interfaces(data) + + data = self.responses[3] + if 'LLDP' in self.get_protocol_list(data): + neighbors = self.responses[2] + self.facts['neighbors'] = self.parse_neighbors(neighbors) + + def get_protocol_list(self, data): + start = False + protocol_list = list() + for line in data.split('\n'): + match = re.search(r'Software Protocol Configured\s*', line) + if match: + start = True + continue + if start: + line = line.strip() + if line.isalnum(): + protocol_list.append(line) + return protocol_list + + def populate_interfaces(self, interfaces): + facts = dict() + for key, value in interfaces.items(): + intf = dict() + intf['description'] = self.parse_description(value) + intf['macaddress'] = self.parse_macaddress(value) + ipv4 = self.parse_ipv4(value) + intf['ipv4'] = self.parse_ipv4(value) + if ipv4: + self.add_ip_address(ipv4['address'], 'ipv4') + + intf['mtu'] = self.parse_mtu(value) + intf['bandwidth'] = self.parse_bandwidth(value) + intf['mediatype'] = self.parse_mediatype(value) + intf['duplex'] = self.parse_duplex(value) + intf['lineprotocol'] = self.parse_lineprotocol(value) + intf['operstatus'] = self.parse_operstatus(value) + intf['type'] = self.parse_type(value) + + facts[key] = intf + return facts + + def populate_ipv6_interfaces(self, data): + for key, value in data.items(): + if key in self.facts['interfaces']: + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'\s+(.+), subnet', value, re.M) + subnets = re.findall(r', subnet is (\S+)', value, re.M) + for addr, subnet in izip(addresses, subnets): + ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def parse_neighbors(self, neighbors): + facts = dict() + + for entry in neighbors.split( + '========================================================================'): + if entry == '': + continue + + intf = self.parse_lldp_intf(entry) + if intf not in facts: + facts[intf] = list() + fact = dict() + fact['host'] = self.parse_lldp_host(entry) + fact['port'] = self.parse_lldp_port(entry) + facts[intf].append(fact) + return facts + + def parse_interfaces(self, data): + parsed = dict() + newline_count = 0 + interface_start = True + + for line in data.split('\n'): + if interface_start: + newline_count = 0 + if len(line) == 0: + newline_count += 1 + if newline_count == 2: + interface_start = True + continue + else: + match = re.match(r'^(\S+) (\S+)', line) + if match and interface_start: + interface_start = False + key = match.group(0) + parsed[key] = line + else: + parsed[key] += '\n%s' % line + return parsed + + def parse_mgmt_interfaces(self, data): + parsed = dict() + interface_start = True + for line in data.split('\n'): + match = re.match(r'^(\S+) (\S+)', line) + if "Time since" in line: + interface_start = True + parsed[key] += '\n%s' % line + continue + elif match and interface_start: + interface_start = False + key = match.group(0) + parsed[key] = line + else: + parsed[key] += '\n%s' % line + return parsed + + def parse_vlan_interfaces(self, data): + parsed = dict() + interface_start = True + line_before_end = False + for line in data.split('\n'): + match = re.match(r'^(\S+) (\S+)', line) + match_endline = re.match(r'^\s*\d+ packets, \d+ bytes$', line) + + if "Output Statistics" in line: + line_before_end = True + parsed[key] += '\n%s' % line + elif match_endline and line_before_end: + line_before_end = False + interface_start = True + parsed[key] += '\n%s' % line + elif match and interface_start: + interface_start = False + key = match.group(0) + parsed[key] = line + else: + parsed[key] += '\n%s' % line + return parsed + + def parse_ipv6_interfaces(self, data): + parsed = dict() + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^(\S+) (\S+)', line) + if match: + key = match.group(0) + parsed[key] = line + return parsed + + def parse_description(self, data): + match = re.search(r'Description: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_macaddress(self, data): + match = re.search(r'address is (\S+)', data) + if match: + if match.group(1) != "not": + return match.group(1) + + def parse_ipv4(self, data): + match = re.search(r'Internet address is (\S+)', data) + if match: + if match.group(1) != "not": + addr, masklen = match.group(1).split('/') + return dict(address=addr, masklen=int(masklen)) + + def parse_mtu(self, data): + match = re.search(r'MTU (\d+)', data) + if match: + return int(match.group(1)) + + def parse_bandwidth(self, data): + match = re.search(r'LineSpeed (\d+)', data) + if match: + return int(match.group(1)) + + def parse_duplex(self, data): + match = re.search(r'(\w+) duplex', data, re.M) + if match: + return match.group(1) + + def parse_mediatype(self, data): + media = re.search(r'(.+) media present, (.+)', data, re.M) + if media: + match = re.search(r'type is (.+)$', media.group(0), re.M) + return match.group(1) + + def parse_type(self, data): + match = re.search(r'Hardware is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lineprotocol(self, data): + match = re.search(r'line protocol is (\w+[ ]?\w*)\(?.*\)?$', data, re.M) + if match: + return match.group(1) + + def parse_operstatus(self, data): + match = re.search(r'^(?:.+) is (.+),', data, re.M) + if match: + return match.group(1) + + def parse_lldp_intf(self, data): + match = re.search(r'^\sLocal Interface (\S+\s\S+)', data, re.M) + if match: + return match.group(1) + + def parse_lldp_host(self, data): + match = re.search(r'Remote System Name: (.+)$', data, re.M) + if match: + return match.group(1) + + def parse_lldp_port(self, data): + match = re.search(r'Remote Port ID: (.+)$', data, re.M) + if match: + return match.group(1) + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + argument_spec.update(dellos9_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['gather_subset'] + + runable_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + + warnings = list() + check_args(module, warnings) + + module.exit_json(ansible_facts=ansible_facts, warnings=warnings) + + +if __name__ == '__main__': + main() diff --git a/plugins/terminal/__init__.py b/plugins/terminal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/terminal/dellos9.py b/plugins/terminal/dellos9.py new file mode 100644 index 0000000..ceb20f8 --- /dev/null +++ b/plugins/terminal/dellos9.py @@ -0,0 +1,83 @@ +# +# (c) 2016 Red Hat Inc. +# +# Copyright (c) 2017 Dell Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.module_utils._text import to_text, to_bytes +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_stdout_re = [ + re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_stderr_re = [ + re.compile(br"% ?Error: (?:(?!\bdoes not exist\b)(?!\balready exists\b)(?!\bHost not found\b)(?!\bnot active\b).)*\n"), + re.compile(br"% ?Bad secret"), + re.compile(br"invalid input", re.I), + re.compile(br"(?:incomplete|ambiguous) command", re.I), + re.compile(br"connection timed out", re.I), + re.compile(br"'[^']' +returned error code: ?\d+"), + ] + + terminal_initial_prompt = br"\[y/n\]:" + + terminal_initial_answer = b"y" + + def on_open_shell(self): + try: + self._exec_cli_command(b'terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_become(self, passwd=None): + if self._get_prompt().endswith(b'#'): + return + + cmd = {u'command': u'enable'} + if passwd: + cmd[u'prompt'] = to_text(r"[\r\n]?password: $", errors='surrogate_or_strict') + cmd[u'answer'] = passwd + + try: + self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode') + + def on_unbecome(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if prompt.strip().endswith(b')#'): + self._exec_cli_command(b'end') + self._exec_cli_command(b'disable') + + elif prompt.endswith(b'#'): + self._exec_cli_command(b'disable') diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..ea1472e --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/tests/integration/targets/__init__.py b/tests/integration/targets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/targets/dellos9_command/defaults/main.yaml b/tests/integration/targets/dellos9_command/defaults/main.yaml new file mode 100644 index 0000000..5f709c5 --- /dev/null +++ b/tests/integration/targets/dellos9_command/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/dellos9_command/tasks/cli.yaml b/tests/integration/targets/dellos9_command/tasks/cli.yaml new file mode 100644 index 0000000..8c11e10 --- /dev/null +++ b/tests/integration/targets/dellos9_command/tasks/cli.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/dellos9_command/tasks/main.yaml b/tests/integration/targets/dellos9_command/tasks/main.yaml new file mode 100644 index 0000000..415c99d --- /dev/null +++ b/tests/integration/targets/dellos9_command/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/tests/integration/targets/dellos9_command/tests/cli/bad_operator.yaml b/tests/integration/targets/dellos9_command/tests/cli/bad_operator.yaml new file mode 100644 index 0000000..0ec627f --- /dev/null +++ b/tests/integration/targets/dellos9_command/tests/cli/bad_operator.yaml @@ -0,0 +1,20 @@ +--- +- debug: msg="START cli/bad_operator.yaml" + +- name: test bad operator + dellos9_command: + commands: + - show version + - show interfaces TenGigabitEthernet 0/0 + wait_for: + - "result[0] contains 'Description : blah'" + provider: "{{ cli }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.failed == true" + - "result.msg is defined" + +- debug: msg="END cli/bad_operator.yaml" diff --git a/tests/integration/targets/dellos9_command/tests/cli/contains.yaml b/tests/integration/targets/dellos9_command/tests/cli/contains.yaml new file mode 100644 index 0000000..4082cdd --- /dev/null +++ b/tests/integration/targets/dellos9_command/tests/cli/contains.yaml @@ -0,0 +1,20 @@ +--- +- debug: msg="START cli/contains.yaml" + +- name: test contains operator + dellos9_command: + commands: + - show version + - show interface TenGigabitEthernet 0/0 + wait_for: + - "result[0] contains 2.0" + - "result[1] contains TenGigabitEthernet " + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- debug: msg="END cli/contains.yaml" diff --git a/tests/integration/targets/dellos9_command/tests/cli/invalid.yaml b/tests/integration/targets/dellos9_command/tests/cli/invalid.yaml new file mode 100644 index 0000000..6e9e8e3 --- /dev/null +++ b/tests/integration/targets/dellos9_command/tests/cli/invalid.yaml @@ -0,0 +1,28 @@ +--- +- debug: msg="START cli/invalid.yaml" + +- name: run invalid command + dellos9_command: + commands: ['show foo'] + provider: "{{ cli }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.failed" + +- name: run commands that include invalid command + dellos9_command: + commands: + - show version + - show foo + provider: "{{ cli }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.failed" + +- debug: msg="END cli/invalid.yaml" diff --git a/tests/integration/targets/dellos9_command/tests/cli/output.yaml b/tests/integration/targets/dellos9_command/tests/cli/output.yaml new file mode 100644 index 0000000..151c1b8 --- /dev/null +++ b/tests/integration/targets/dellos9_command/tests/cli/output.yaml @@ -0,0 +1,29 @@ +--- +- debug: msg="START cli/output.yaml" + +- name: get output for single command + dellos9_command: + commands: ['show version'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.stdout is defined" + +- name: get output for multiple commands + dellos9_command: + commands: + - show version + - show interfaces + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.stdout is defined" + - "result.stdout | length == 2" + +- debug: msg="END cli/output.yaml" diff --git a/tests/integration/targets/dellos9_command/tests/cli/timeout.yaml b/tests/integration/targets/dellos9_command/tests/cli/timeout.yaml new file mode 100644 index 0000000..530ca67 --- /dev/null +++ b/tests/integration/targets/dellos9_command/tests/cli/timeout.yaml @@ -0,0 +1,19 @@ +--- +- debug: msg="START cli/timeout.yaml" + +- name: test bad condition + dellos9_command: + commands: + - show version + wait_for: + - "result[0] contains bad_value_string" + provider: "{{ cli }}" + register: result + ignore_errors: yes + +- assert: + that: + - "result.failed == true" + - "result.msg is defined" + +- debug: msg="END cli/timeout.yaml" diff --git a/tests/integration/targets/dellos9_config/defaults/main.yaml b/tests/integration/targets/dellos9_config/defaults/main.yaml new file mode 100644 index 0000000..5f709c5 --- /dev/null +++ b/tests/integration/targets/dellos9_config/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/dellos9_config/tasks/cli.yaml b/tests/integration/targets/dellos9_config/tasks/cli.yaml new file mode 100644 index 0000000..d675462 --- /dev/null +++ b/tests/integration/targets/dellos9_config/tasks/cli.yaml @@ -0,0 +1,15 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/dellos9_config/tasks/main.yaml b/tests/integration/targets/dellos9_config/tasks/main.yaml new file mode 100644 index 0000000..415c99d --- /dev/null +++ b/tests/integration/targets/dellos9_config/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/tests/integration/targets/dellos9_config/tests/cli/sublevel.yaml b/tests/integration/targets/dellos9_config/tests/cli/sublevel.yaml new file mode 100644 index 0000000..b8e95d3 --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/sublevel.yaml @@ -0,0 +1,43 @@ +--- +- debug: msg="START cli/sublevel.yaml" + +- name: setup test + dellos9_config: + lines: + - 'no ip access-list extended test' + - 'no ip access-list standard test' + provider: "{{ cli }}" + match: none + +- name: configure sub level command + dellos9_config: + lines: ['seq 5 permit ip any any log threshold-in-msgs 10 interval 5'] + parents: ['ip access-list extended test'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'ip access-list extended test' in result.updates" + - "'seq 5 permit ip any any log threshold-in-msgs 10 interval 5' in result.updates" + +- name: configure sub level command idempotent check + dellos9_config: + lines: ['seq 5 permit ip any any log threshold-in-msgs 10 interval 5'] + parents: ['ip access-list extended test'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: teardown + dellos9_config: + lines: + - 'no ip access-list extended test' + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/sublevel.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/sublevel_block.yaml b/tests/integration/targets/dellos9_config/tests/cli/sublevel_block.yaml new file mode 100644 index 0000000..f7b2983 --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/sublevel_block.yaml @@ -0,0 +1,62 @@ +--- +- debug: msg="START cli/sublevel_block.yaml" + +- name: setup + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + before: ['no ip access-list extended test'] + after: ['exit'] + provider: "{{ cli }}" + match: none + +- name: configure sub level command using block replace + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + replace: block + after: ['exit'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'ip access-list extended test' in result.updates" + - "'seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5' in result.updates" + +- name: check sub level command using block replace + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + replace: block + after: ['exit'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: teardown + dellos9_config: + lines: + - no ip access-list extended test + match: none + provider: "{{ cli }}" + +- debug: msg="END cli/sublevel_block.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/sublevel_exact.yaml b/tests/integration/targets/dellos9_config/tests/cli/sublevel_exact.yaml new file mode 100644 index 0000000..9c80d3f --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/sublevel_exact.yaml @@ -0,0 +1,66 @@ +--- +- debug: msg="START cli/sublevel_exact.yaml" + +- name: setup + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + - seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + before: ['no ip access-list extended test'] + after: ['exit'] + provider: "{{ cli }}" + match: none + +- name: configure sub level command using exact match + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + after: ['exit'] + match: exact + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'ip access-list extended test' in result.updates" + - "'seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5' not in result.updates" + +- name: check sub level command using exact match + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + - seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + after: ['exit'] + match: exact + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: teardown + dellos9_config: + lines: + - no ip access-list extended test + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/sublevel_exact.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/sublevel_strict.yaml b/tests/integration/targets/dellos9_config/tests/cli/sublevel_strict.yaml new file mode 100644 index 0000000..3cca5ef --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/sublevel_strict.yaml @@ -0,0 +1,63 @@ +--- +- debug: msg="START cli/sublevel_strict.yaml" + +- name: setup + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + - seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + before: ['no ip access-list extended test'] + after: ['exit'] + provider: "{{ cli }}" + match: none + +- name: configure sub level command using strict match + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + match: strict + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: check sub level command using strict match + dellos9_config: + lines: + - seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5 + - seq 10 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5 + - seq 15 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5 + parents: ['ip access-list extended test'] + after: ['exit'] + match: strict + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'ip access-list extended test' in result.updates" + - "'seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5' not in result.updates" + - "'seq 15 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 10 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5' in result.updates" + - "'seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5' not in result.updates" + - "'seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5' not in result.updates" + +- name: teardown + dellos9_config: + lines: + - no ip access-list extended test + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/sublevel_strict.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/toplevel.yaml b/tests/integration/targets/dellos9_config/tests/cli/toplevel.yaml new file mode 100644 index 0000000..22669a4 --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/toplevel.yaml @@ -0,0 +1,37 @@ +--- +- debug: msg="START cli/toplevel.yaml" + +- name: setup + dellos9_config: + lines: ['hostname {{ inventory_hostname_short }}'] + provider: "{{ cli }}" + match: none + +- name: configure top level command + dellos9_config: + lines: ['hostname foo'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'hostname foo' in result.updates" + +- name: configure top level command idempotent check + dellos9_config: + lines: ['hostname foo'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: teardown + dellos9_config: + lines: ['hostname {{ inventory_hostname_short }}'] + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/toplevel.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/toplevel_after.yaml b/tests/integration/targets/dellos9_config/tests/cli/toplevel_after.yaml new file mode 100644 index 0000000..6b5ee8e --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/toplevel_after.yaml @@ -0,0 +1,44 @@ +--- +- debug: msg="START cli/toplevel_after.yaml" + +- name: setup + dellos9_config: + lines: + - "snmp-server contact ansible" + - "hostname {{ inventory_hostname_short }}" + provider: "{{ cli }}" + match: none + +- name: configure top level command with after + dellos9_config: + lines: ['hostname foo'] + after: ['snmp-server contact bar'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'hostname foo' in result.updates" + - "'snmp-server contact bar' in result.updates" + +- name: configure top level command with after idempotent check + dellos9_config: + lines: ['hostname foo'] + after: ['snmp-server contact bar'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: teardown + dellos9_config: + lines: + - "no snmp-server contact" + - "hostname {{ inventory_hostname_short }}" + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/toplevel_after.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/toplevel_before.yaml b/tests/integration/targets/dellos9_config/tests/cli/toplevel_before.yaml new file mode 100644 index 0000000..79b01e4 --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/toplevel_before.yaml @@ -0,0 +1,44 @@ +--- +- debug: msg="START cli/toplevel_before.yaml" + +- name: setup + dellos9_config: + lines: + - "snmp-server contact ansible" + - "hostname {{ inventory_hostname_short }}" + provider: "{{ cli }}" + match: none + +- name: configure top level command with before + dellos9_config: + lines: ['hostname foo'] + before: ['snmp-server contact bar'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'hostname foo' in result.updates" + - "'snmp-server contact bar' in result.updates" + +- name: configure top level command with before idempotent check + dellos9_config: + lines: ['hostname foo'] + before: ['snmp-server contact bar'] + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + +- name: teardown + dellos9_config: + lines: + - "no snmp-server contact" + - "hostname {{ inventory_hostname_short }}" + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/toplevel_before.yaml" diff --git a/tests/integration/targets/dellos9_config/tests/cli/toplevel_nonidempotent.yaml b/tests/integration/targets/dellos9_config/tests/cli/toplevel_nonidempotent.yaml new file mode 100644 index 0000000..142d5d7 --- /dev/null +++ b/tests/integration/targets/dellos9_config/tests/cli/toplevel_nonidempotent.yaml @@ -0,0 +1,39 @@ +--- +- debug: msg="START cli/toplevel_nonidempotent.yaml" + +- name: setup + dellos9_config: + lines: ['hostname {{ inventory_hostname_short }}'] + provider: "{{ cli }}" + match: none + +- name: configure top level command + dellos9_config: + lines: ['hostname foo'] + provider: "{{ cli }}" + match: strict + register: result + +- assert: + that: + - "result.changed == true" + - "'hostname foo' in result.updates" + +- name: configure top level command idempotent check + dellos9_config: + lines: ['hostname foo'] + provider: "{{ cli }}" + match: strict + register: result + +- assert: + that: + - "result.changed == true" + +- name: teardown + dellos9_config: + lines: ['hostname {{ inventory_hostname_short }}'] + provider: "{{ cli }}" + match: none + +- debug: msg="END cli/toplevel_nonidempotent.yaml" diff --git a/tests/integration/targets/dellos9_facts/defaults/main.yaml b/tests/integration/targets/dellos9_facts/defaults/main.yaml new file mode 100644 index 0000000..5f709c5 --- /dev/null +++ b/tests/integration/targets/dellos9_facts/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/dellos9_facts/tasks/cli.yaml b/tests/integration/targets/dellos9_facts/tasks/cli.yaml new file mode 100644 index 0000000..8c11e10 --- /dev/null +++ b/tests/integration/targets/dellos9_facts/tasks/cli.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/dellos9_facts/tasks/main.yaml b/tests/integration/targets/dellos9_facts/tasks/main.yaml new file mode 100644 index 0000000..415c99d --- /dev/null +++ b/tests/integration/targets/dellos9_facts/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/tests/integration/targets/dellos9_facts/tests/cli/facts.yaml b/tests/integration/targets/dellos9_facts/tests/cli/facts.yaml new file mode 100644 index 0000000..acea51c --- /dev/null +++ b/tests/integration/targets/dellos9_facts/tests/cli/facts.yaml @@ -0,0 +1,42 @@ +--- +- debug: msg="START cli/facts.yaml" + +- name: test all facts + dellos9_facts: + gather_subset: + - all + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.ansible_facts is defined" + +- name: test all facts except hardware + dellos9_facts: + gather_subset: + - "!hardware" + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.ansible_facts.ansible_net_filesystems is not defined" + +- name: test interface facts + dellos9_facts: + gather_subset: + - interfaces + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.ansible_facts.ansible_net_interfaces is defined" + - "result.ansible_facts.ansible_net_filesystems is not defined" + + +- debug: msg="END cli/facts.yaml" diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt new file mode 100644 index 0000000..03b3631 --- /dev/null +++ b/tests/sanity/ignore-2.10.txt @@ -0,0 +1,31 @@ +plugins/module_utils/network/dellos9/dellos9.py future-import-boilerplate +plugins/module_utils/network/dellos9/dellos9.py metaclass-boilerplate +plugins/modules/dellos9_command.py validate-modules:doc-default-does-not-match-spec +plugins/modules/dellos9_command.py validate-modules:doc-missing-type +plugins/modules/dellos9_command.py validate-modules:doc-required-mismatch +plugins/modules/dellos9_command.py validate-modules:parameter-list-no-elements +plugins/modules/dellos9_command.py validate-modules:parameter-type-not-in-doc +plugins/modules/dellos9_command.py validate-modules:undocumented-parameter +plugins/modules/dellos9_config.py validate-modules:doc-default-does-not-match-spec +plugins/modules/dellos9_config.py validate-modules:doc-missing-type +plugins/modules/dellos9_config.py validate-modules:doc-required-mismatch +plugins/modules/dellos9_config.py validate-modules:parameter-list-no-elements +plugins/modules/dellos9_config.py validate-modules:parameter-type-not-in-doc +plugins/modules/dellos9_config.py validate-modules:undocumented-parameter +plugins/modules/dellos9_facts.py validate-modules:doc-default-does-not-match-spec +plugins/modules/dellos9_facts.py validate-modules:doc-missing-type +plugins/modules/dellos9_facts.py validate-modules:doc-required-mismatch +plugins/modules/dellos9_facts.py validate-modules:parameter-list-no-elements +plugins/modules/dellos9_facts.py validate-modules:parameter-type-not-in-doc +plugins/modules/dellos9_facts.py validate-modules:undocumented-parameter +plugins/action/dellos9.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/doc_fragments/dellos9.py future-import-boilerplate +plugins/doc_fragments/dellos9.py metaclass-boilerplate +tests/unit/mock/path.py future-import-boilerplate +tests/unit/mock/path.py metaclass-boilerplate +tests/unit/mock/yaml_helper.py future-import-boilerplate +tests/unit/mock/yaml_helper.py metaclass-boilerplate +tests/unit/modules/conftest.py future-import-boilerplate +tests/unit/modules/conftest.py metaclass-boilerplate +tests/unit/modules/utils.py future-import-boilerplate +tests/unit/modules/utils.py metaclass-boilerplate \ No newline at end of file diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt new file mode 100644 index 0000000..03b3631 --- /dev/null +++ b/tests/sanity/ignore-2.9.txt @@ -0,0 +1,31 @@ +plugins/module_utils/network/dellos9/dellos9.py future-import-boilerplate +plugins/module_utils/network/dellos9/dellos9.py metaclass-boilerplate +plugins/modules/dellos9_command.py validate-modules:doc-default-does-not-match-spec +plugins/modules/dellos9_command.py validate-modules:doc-missing-type +plugins/modules/dellos9_command.py validate-modules:doc-required-mismatch +plugins/modules/dellos9_command.py validate-modules:parameter-list-no-elements +plugins/modules/dellos9_command.py validate-modules:parameter-type-not-in-doc +plugins/modules/dellos9_command.py validate-modules:undocumented-parameter +plugins/modules/dellos9_config.py validate-modules:doc-default-does-not-match-spec +plugins/modules/dellos9_config.py validate-modules:doc-missing-type +plugins/modules/dellos9_config.py validate-modules:doc-required-mismatch +plugins/modules/dellos9_config.py validate-modules:parameter-list-no-elements +plugins/modules/dellos9_config.py validate-modules:parameter-type-not-in-doc +plugins/modules/dellos9_config.py validate-modules:undocumented-parameter +plugins/modules/dellos9_facts.py validate-modules:doc-default-does-not-match-spec +plugins/modules/dellos9_facts.py validate-modules:doc-missing-type +plugins/modules/dellos9_facts.py validate-modules:doc-required-mismatch +plugins/modules/dellos9_facts.py validate-modules:parameter-list-no-elements +plugins/modules/dellos9_facts.py validate-modules:parameter-type-not-in-doc +plugins/modules/dellos9_facts.py validate-modules:undocumented-parameter +plugins/action/dellos9.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` +plugins/doc_fragments/dellos9.py future-import-boilerplate +plugins/doc_fragments/dellos9.py metaclass-boilerplate +tests/unit/mock/path.py future-import-boilerplate +tests/unit/mock/path.py metaclass-boilerplate +tests/unit/mock/yaml_helper.py future-import-boilerplate +tests/unit/mock/yaml_helper.py metaclass-boilerplate +tests/unit/modules/conftest.py future-import-boilerplate +tests/unit/modules/conftest.py metaclass-boilerplate +tests/unit/modules/utils.py future-import-boilerplate +tests/unit/modules/utils.py metaclass-boilerplate \ No newline at end of file diff --git a/tests/sanity/requirements.txt b/tests/sanity/requirements.txt new file mode 100644 index 0000000..3e3a966 --- /dev/null +++ b/tests/sanity/requirements.txt @@ -0,0 +1,4 @@ +packaging # needed for update-bundled and changelog +sphinx ; python_version >= '3.5' # docs build requires python 3+ +sphinx-notfound-page ; python_version >= '3.5' # docs build requires python 3+ +straight.plugin ; python_version >= '3.5' # needed for hacking/build-ansible.py which will host changelog generation and requires python 3+ diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/compat/__init__.py b/tests/unit/compat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/compat/builtins.py b/tests/unit/compat/builtins.py new file mode 100644 index 0000000..f60ee67 --- /dev/null +++ b/tests/unit/compat/builtins.py @@ -0,0 +1,33 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/tests/unit/compat/mock.py b/tests/unit/compat/mock.py new file mode 100644 index 0000000..0972cd2 --- /dev/null +++ b/tests/unit/compat/mock.py @@ -0,0 +1,122 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/tests/unit/compat/unittest.py b/tests/unit/compat/unittest.py new file mode 100644 index 0000000..98f08ad --- /dev/null +++ b/tests/unit/compat/unittest.py @@ -0,0 +1,38 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * diff --git a/tests/unit/mock/__init__.py b/tests/unit/mock/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/mock/loader.py b/tests/unit/mock/loader.py new file mode 100644 index 0000000..0ee47fb --- /dev/null +++ b/tests/unit/mock/loader.py @@ -0,0 +1,116 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.errors import AnsibleParserError +from ansible.parsing.dataloader import DataLoader +from ansible.module_utils._text import to_bytes, to_text + + +class DictDataLoader(DataLoader): + + def __init__(self, file_mapping=None): + file_mapping = {} if file_mapping is None else file_mapping + assert type(file_mapping) == dict + + super(DictDataLoader, self).__init__() + + self._file_mapping = file_mapping + self._build_known_directories() + self._vault_secrets = None + + def load_from_file(self, path, cache=True, unsafe=False): + path = to_text(path) + if path in self._file_mapping: + return self.load(self._file_mapping[path], path) + return None + + # TODO: the real _get_file_contents returns a bytestring, so we actually convert the + # unicode/text it's created with to utf-8 + def _get_file_contents(self, path): + path = to_text(path) + if path in self._file_mapping: + return (to_bytes(self._file_mapping[path]), False) + else: + raise AnsibleParserError("file not found: %s" % path) + + def path_exists(self, path): + path = to_text(path) + return path in self._file_mapping or path in self._known_directories + + def is_file(self, path): + path = to_text(path) + return path in self._file_mapping + + def is_directory(self, path): + path = to_text(path) + return path in self._known_directories + + def list_directory(self, path): + ret = [] + path = to_text(path) + for x in (list(self._file_mapping.keys()) + self._known_directories): + if x.startswith(path): + if os.path.dirname(x) == path: + ret.append(os.path.basename(x)) + return ret + + def is_executable(self, path): + # FIXME: figure out a way to make paths return true for this + return False + + def _add_known_directory(self, directory): + if directory not in self._known_directories: + self._known_directories.append(directory) + + def _build_known_directories(self): + self._known_directories = [] + for path in self._file_mapping: + dirname = os.path.dirname(path) + while dirname not in ('/', ''): + self._add_known_directory(dirname) + dirname = os.path.dirname(dirname) + + def push(self, path, content): + rebuild_dirs = False + if path not in self._file_mapping: + rebuild_dirs = True + + self._file_mapping[path] = content + + if rebuild_dirs: + self._build_known_directories() + + def pop(self, path): + if path in self._file_mapping: + del self._file_mapping[path] + self._build_known_directories() + + def clear(self): + self._file_mapping = dict() + self._known_directories = [] + + def get_basedir(self): + return os.getcwd() + + def set_vault_secrets(self, vault_secrets): + self._vault_secrets = vault_secrets diff --git a/tests/unit/mock/path.py b/tests/unit/mock/path.py new file mode 100644 index 0000000..ebe2760 --- /dev/null +++ b/tests/unit/mock/path.py @@ -0,0 +1,5 @@ +from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import MagicMock +from ansible.utils.path import unfrackpath + + +mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) diff --git a/tests/unit/mock/procenv.py b/tests/unit/mock/procenv.py new file mode 100644 index 0000000..d5ff79f --- /dev/null +++ b/tests/unit/mock/procenv.py @@ -0,0 +1,90 @@ +# (c) 2016, Matt Davis <mdavis@ansible.com> +# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import json + +from contextlib import contextmanager +from io import BytesIO, StringIO +from ansible_collections.dellemc_networking.os9.tests.unit.compat import unittest +from ansible.module_utils.six import PY3 +from ansible.module_utils._text import to_bytes + + +@contextmanager +def swap_stdin_and_argv(stdin_data='', argv_data=tuple()): + """ + context manager that temporarily masks the test runner's values for stdin and argv + """ + real_stdin = sys.stdin + real_argv = sys.argv + + if PY3: + fake_stream = StringIO(stdin_data) + fake_stream.buffer = BytesIO(to_bytes(stdin_data)) + else: + fake_stream = BytesIO(to_bytes(stdin_data)) + + try: + sys.stdin = fake_stream + sys.argv = argv_data + + yield + finally: + sys.stdin = real_stdin + sys.argv = real_argv + + +@contextmanager +def swap_stdout(): + """ + context manager that temporarily replaces stdout for tests that need to verify output + """ + old_stdout = sys.stdout + + if PY3: + fake_stream = StringIO() + else: + fake_stream = BytesIO() + + try: + sys.stdout = fake_stream + + yield fake_stream + finally: + sys.stdout = old_stdout + + +class ModuleTestCase(unittest.TestCase): + def setUp(self, module_args=None): + if module_args is None: + module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} + + args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) + + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap = swap_stdin_and_argv(stdin_data=args) + self.stdin_swap.__enter__() + + def tearDown(self): + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap.__exit__(None, None, None) diff --git a/tests/unit/mock/vault_helper.py b/tests/unit/mock/vault_helper.py new file mode 100644 index 0000000..dcce9c7 --- /dev/null +++ b/tests/unit/mock/vault_helper.py @@ -0,0 +1,39 @@ +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_bytes + +from ansible.parsing.vault import VaultSecret + + +class TextVaultSecret(VaultSecret): + '''A secret piece of text. ie, a password. Tracks text encoding. + + The text encoding of the text may not be the default text encoding so + we keep track of the encoding so we encode it to the same bytes.''' + + def __init__(self, text, encoding=None, errors=None, _bytes=None): + super(TextVaultSecret, self).__init__() + self.text = text + self.encoding = encoding or 'utf-8' + self._bytes = _bytes + self.errors = errors or 'strict' + + @property + def bytes(self): + '''The text encoded with encoding, unless we specifically set _bytes.''' + return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) diff --git a/tests/unit/mock/yaml_helper.py b/tests/unit/mock/yaml_helper.py new file mode 100644 index 0000000..cc095fe --- /dev/null +++ b/tests/unit/mock/yaml_helper.py @@ -0,0 +1,121 @@ +import io +import yaml + +from ansible.module_utils.six import PY3 +from ansible.parsing.yaml.loader import AnsibleLoader +from ansible.parsing.yaml.dumper import AnsibleDumper + + +class YamlTestUtils(object): + """Mixin class to combine with a unittest.TestCase subclass.""" + def _loader(self, stream): + """Vault related tests will want to override this. + + Vault cases should setup a AnsibleLoader that has the vault password.""" + return AnsibleLoader(stream) + + def _dump_stream(self, obj, stream, dumper=None): + """Dump to a py2-unicode or py3-string stream.""" + if PY3: + return yaml.dump(obj, stream, Dumper=dumper) + else: + return yaml.dump(obj, stream, Dumper=dumper, encoding=None) + + def _dump_string(self, obj, dumper=None): + """Dump to a py2-unicode or py3-string""" + if PY3: + return yaml.dump(obj, Dumper=dumper) + else: + return yaml.dump(obj, Dumper=dumper, encoding=None) + + def _dump_load_cycle(self, obj): + # Each pass though a dump or load revs the 'generation' + # obj to yaml string + string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) + + # wrap a stream/file like StringIO around that yaml + stream_from_object_dump = io.StringIO(string_from_object_dump) + loader = self._loader(stream_from_object_dump) + # load the yaml stream to create a new instance of the object (gen 2) + obj_2 = loader.get_data() + + # dump the gen 2 objects directory to strings + string_from_object_dump_2 = self._dump_string(obj_2, + dumper=AnsibleDumper) + + # The gen 1 and gen 2 yaml strings + self.assertEqual(string_from_object_dump, string_from_object_dump_2) + # the gen 1 (orig) and gen 2 py object + self.assertEqual(obj, obj_2) + + # again! gen 3... load strings into py objects + stream_3 = io.StringIO(string_from_object_dump_2) + loader_3 = self._loader(stream_3) + obj_3 = loader_3.get_data() + + string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) + + self.assertEqual(obj, obj_3) + # should be transitive, but... + self.assertEqual(obj_2, obj_3) + self.assertEqual(string_from_object_dump, string_from_object_dump_3) + + def _old_dump_load_cycle(self, obj): + '''Dump the passed in object to yaml, load it back up, dump again, compare.''' + stream = io.StringIO() + + yaml_string = self._dump_string(obj, dumper=AnsibleDumper) + self._dump_stream(obj, stream, dumper=AnsibleDumper) + + yaml_string_from_stream = stream.getvalue() + + # reset stream + stream.seek(0) + + loader = self._loader(stream) + # loader = AnsibleLoader(stream, vault_password=self.vault_password) + obj_from_stream = loader.get_data() + + stream_from_string = io.StringIO(yaml_string) + loader2 = self._loader(stream_from_string) + # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) + obj_from_string = loader2.get_data() + + stream_obj_from_stream = io.StringIO() + stream_obj_from_string = io.StringIO() + + if PY3: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) + else: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None) + + yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() + yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() + + stream_obj_from_stream.seek(0) + stream_obj_from_string.seek(0) + + if PY3: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) + else: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None) + + assert yaml_string == yaml_string_obj_from_stream + assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream == + yaml_string_stream_obj_from_string) + assert obj == obj_from_stream + assert obj == obj_from_string + assert obj == yaml_string_obj_from_stream + assert obj == yaml_string_obj_from_string + assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + return {'obj': obj, + 'yaml_string': yaml_string, + 'yaml_string_from_stream': yaml_string_from_stream, + 'obj_from_stream': obj_from_stream, + 'obj_from_string': obj_from_string, + 'yaml_string_obj_from_string': yaml_string_obj_from_string} diff --git a/tests/unit/modules/__init__.py b/tests/unit/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/modules/conftest.py b/tests/unit/modules/conftest.py new file mode 100644 index 0000000..3bbfe0b --- /dev/null +++ b/tests/unit/modules/conftest.py @@ -0,0 +1,28 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import json + +import pytest + +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def patch_ansible_module(request, mocker): + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if 'ANSIBLE_MODULE_ARGS' not in request.param: + request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False + args = json.dumps(request.param) + else: + raise Exception('Malformed data to the patch_ansible_module pytest fixture') + + mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args)) diff --git a/tests/unit/modules/network/__init__.py b/tests/unit/modules/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/modules/network/dellos9/__init__.py b/tests/unit/modules/network/dellos9/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/modules/network/dellos9/dellos9_module.py b/tests/unit/modules/network/dellos9/dellos9_module.py new file mode 100644 index 0000000..0b85946 --- /dev/null +++ b/tests/unit/modules/network/dellos9/dellos9_module.py @@ -0,0 +1,90 @@ +# (c) 2016 Red Hat Inc. +# +# (c) 2017 Dell EMC. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json + +from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase + + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except Exception: + pass + + fixture_data[path] = data + return data + + +class TestDellos9Module(ModuleTestCase): + + def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False): + + self.load_fixtures(commands) + + if failed: + result = self.failed() + self.assertTrue(result['failed'], result) + else: + result = self.changed(changed) + self.assertEqual(result['changed'], changed, result) + + if commands is not None: + if sort: + self.assertEqual(sorted(commands), sorted(result['updates']), result['updates']) + else: + self.assertEqual(commands, result['updates'], result['updates']) + + return result + + def failed(self): + with self.assertRaises(AnsibleFailJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertTrue(result['failed'], result) + return result + + def changed(self, changed=False): + with self.assertRaises(AnsibleExitJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], changed, result) + return result + + def load_fixtures(self, commands=None): + pass diff --git a/tests/unit/modules/network/dellos9/fixtures/dellos9_config_config.cfg b/tests/unit/modules/network/dellos9/fixtures/dellos9_config_config.cfg new file mode 100644 index 0000000..b8f62da --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/dellos9_config_config.cfg @@ -0,0 +1,13 @@ +! +hostname router +! +interface fortyGigE 1/6 + ip address 1.2.3.4/24 + description test string +! +interface fortyGigE 1/7 + ip address 6.7.8.9/24 + description test string + shutdown +! + diff --git a/tests/unit/modules/network/dellos9/fixtures/dellos9_config_src.cfg b/tests/unit/modules/network/dellos9/fixtures/dellos9_config_src.cfg new file mode 100644 index 0000000..7ab3338 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/dellos9_config_src.cfg @@ -0,0 +1,12 @@ +! +hostname foo +! +interface fortyGigE 1/6 + no ip address +! +interface fortyGigE 1/7 + ip address 6.7.8.9/24 + description test string + shutdown +! + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_file-systems b/tests/unit/modules/network/dellos9/fixtures/show_file-systems new file mode 100644 index 0000000..1c02bb6 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_file-systems @@ -0,0 +1,10 @@ + Size(b) Free(b) Feature Type Flags Prefixes + 6429872128 5582319616 FAT32 USERFLASH rw flash: + - - unformatted USERFLASH rw fcmfs: + 241172480 91893760 Unknown NFSMOUNT rw nfsmount: + - - - network rw ftp: + - - - network rw tftp: + - - - network rw scp: + - - - network rw http: + - - - network rw https: + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_interfaces b/tests/unit/modules/network/dellos9/fixtures/show_interfaces new file mode 100644 index 0000000..5f19f38 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_interfaces @@ -0,0 +1,1259 @@ +TenGigabitEthernet 0/0 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1048580 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 10000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:13:21 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:16:47 + + +TenGigabitEthernet 0/1 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1048708 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 10000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:17:48 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:16:49 + + +TenGigabitEthernet 0/2 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1048836 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 10000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:18:30 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:17:31 + + +TenGigabitEthernet 0/3 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1048964 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 10000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:18:33 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:17:35 + + +fortyGigE 0/4 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1049093 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:38:08 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:37:09 + + +fortyGigE 0/8 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1049605 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:38:08 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:40:18 + + +fortyGigE 0/12 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1050117 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:41:18 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:40:20 + + +fortyGigE 0/16 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1050629 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:42:41 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:41:43 + + +fortyGigE 0/20 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1051141 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:43:10 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:42:12 + + +fortyGigE 0/24 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1051653 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:43:45 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:42:47 + + +fortyGigE 0/28 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1052165 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:44:35 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:43:37 + + +fortyGigE 0/32 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1052677 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:44:53 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:43:54 + + +fortyGigE 0/36 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1053189 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:46:20 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:45:21 + + +fortyGigE 0/40 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1053701 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:46:32 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:45:33 + + +fortyGigE 0/44 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1054213 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:46:56 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:45:58 + + +fortyGigE 0/48 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1054725 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:47:10 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:46:11 + + +fortyGigE 0/52 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1055237 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:47:22 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:46:24 + + +fortyGigE 0/56 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1055749 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:47:47 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:46:48 + + +fortyGigE 0/60 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1056261 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:47:58 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:47:00 + + +fortyGigE 0/64 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1056773 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:48:26 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:47:28 + + +fortyGigE 0/68 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1057285 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:48:38 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:47:40 + + +fortyGigE 0/72 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1057797 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:49:05 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:48:07 + + +fortyGigE 0/76 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1058309 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:49:17 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:48:18 + + +fortyGigE 0/80 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1058821 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:49:36 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:48:37 + + +fortyGigE 0/84 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1059333 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:49:58 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:49:00 + + +fortyGigE 0/88 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1059845 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:50:12 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:49:14 + + +fortyGigE 0/92 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1060357 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:50:36 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:49:37 + + +fortyGigE 0/96 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1060869 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:50:50 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:49:52 + + +fortyGigE 0/100 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1061381 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:51:16 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:50:17 + + +fortyGigE 0/104 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1061893 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:51:26 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:50:28 + + +fortyGigE 0/108 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1062405 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:51:50 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:50:52 + + +fortyGigE 0/112 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1062917 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:52:02 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:51:04 + + +fortyGigE 0/116 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1063429 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:52:14 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:51:15 + + +fortyGigE 0/120 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1063941 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:52:44 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:51:45 + + +fortyGigE 0/124 is down, line protocol is down +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 1064453 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 40000 Mbit +Flowcontrol rx off tx off +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:52:55 +Queueing strategy: fifo +Input Statistics: + 0 packets, 0 bytes + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 runts, 0 giants, 0 throttles + 0 CRC, 0 overrun, 0 discarded +Output Statistics: + 0 packets, 0 bytes, 0 underruns + 0 64-byte pkts, 0 over 64-byte pkts, 0 over 127-byte pkts + 0 over 255-byte pkts, 0 over 511-byte pkts, 0 over 1023-byte pkts + 0 Multicasts, 0 Broadcasts, 0 Unicasts + 0 throttles, 0 discarded, 0 collisions, 0 wreddrops +Rate info (interval 299 seconds): + Input 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate + Output 00.00 Mbits/sec, 0 packets/sec, 0.00% of line-rate +Time since last interface status change: 13:51:56 + + +ManagementEthernet 0/0 is up, line protocol is up +Hardware is DellEth, address is 90:b1:1c:f4:a2:8f + Current address is 90:b1:1c:f4:a2:8f +Pluggable media not present +Interface index is 7340033 +Internet address is 10.16.148.71/16 +Mode of IPv4 Address Assignment : MANUAL +DHCP Client-ID(61): 90b11cf4a28f +Virtual-IP is not set +Virtual-IP IPv6 address is not set +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed 1000 Mbit, Mode full duplex +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:52:17 +Queueing strategy: fifo + Input 111338 packets, 7239813 bytes, 96163 multicast + Received 0 errors, 0 discarded + Output 8316 packets, 1491845 bytes, 0 multicast + Output 0 errors, 0 invalid protocol +Time since last interface status change: 13:52:13 + + +ManagementEthernet 1/0 is up, line protocol is not present +Hardware is DellEth, address is not set +Interface index is 8388609 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed auto, Mode full duplex +ARP type: ARPA, ARP Timeout 04:00:00 +Queueing strategy: fifo +Time since last interface status change: 13:52:33 + + +ManagementEthernet 2/0 is up, line protocol is not present +Hardware is DellEth, address is not set +Interface index is 9437185 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed auto, Mode full duplex +ARP type: ARPA, ARP Timeout 04:00:00 +Queueing strategy: fifo +Time since last interface status change: 13:52:33 + + +ManagementEthernet 3/0 is up, line protocol is not present +Hardware is DellEth, address is not set +Interface index is 10485761 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed auto, Mode full duplex +ARP type: ARPA, ARP Timeout 04:00:00 +Queueing strategy: fifo +Time since last interface status change: 13:52:43 + + +ManagementEthernet 4/0 is up, line protocol is not present +Hardware is DellEth, address is not set +Interface index is 11534337 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed auto, Mode full duplex +ARP type: ARPA, ARP Timeout 04:00:00 +Queueing strategy: fifo +Time since last interface status change: 13:52:43 + + +ManagementEthernet 5/0 is up, line protocol is not present +Hardware is DellEth, address is not set +Interface index is 12582913 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed auto, Mode full duplex +ARP type: ARPA, ARP Timeout 04:00:00 +Queueing strategy: fifo +Time since last interface status change: 13:52:53 + + +Vlan 1 is down, line protocol is down +Address is 90:b1:1c:f4:a2:8f, Current address is 90:b1:1c:f4:a2:8f +Interface index is 1275068928 +Internet address is not set +Mode of IPv4 Address Assignment : NONE +DHCP Client-ID :90b11cf4a28f +MTU 1554 bytes, IP MTU 1500 bytes +LineSpeed auto +ARP type: ARPA, ARP Timeout 04:00:00 +Last clearing of "show interface" counters 13:53:06 +Queueing strategy: fifo +Time since last interface status change: 13:53:06 +Input Statistics: + 0 packets, 0 bytes +Output Statistics: + 0 packets, 0 bytes + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_inventory b/tests/unit/modules/network/dellos9/fixtures/show_inventory new file mode 100644 index 0000000..90c0295 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_inventory @@ -0,0 +1,19 @@ +System Type : S6000 +System Mode : 1.0 +Software Version : 9.12(0.0) + +Unit Type Serial Number Part Number Rev Piece Part ID Rev Svc Tag Exprs Svc Code +-------------------------------------------------------------------------------------------------------------- +* 0 S6000-01-FE-32T NA 08YWFG A00 CN-08YWFG-28298-3AG-0031 A00 6BJ8VS1 137 581 490 89 + 0 S6000-PWR-AC NA 0T9FNW A00 CN-0T9FNW-28298-3AG-0119 A00 NA NA + 0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0094 A00 NA NA + 0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0096 A00 NA NA + 0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0095 A00 NA NA + + * - Management Unit + + +Software Protocol Configured +-------------------------------------------------------------- + LLDP + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_ipv6_interface b/tests/unit/modules/network/dellos9/fixtures/show_ipv6_interface new file mode 100644 index 0000000..0cc43da --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_ipv6_interface @@ -0,0 +1,26 @@ +fortyGigE 0/16 is down, line protocol is down + IPV6 is enabled + Link Local address: fe80::92b1:1cff:fef4:a28f + Global Unicast address(es): + 2001:4898:5808:ffa2::5, subnet is 2001:4898:5808:ffa2::4/126 (MANUAL) + Remaining lifetime: infinite + Global Anycast address(es): + Joined Group address(es): + ff02::1 + ff02::2 + ff02::1:ff00:5 + ff02::1:fff4:a28f + IP MTU is 1500 bytes + ND MTU is 0 + ICMP redirects are not sent + DAD is enabled, number of DAD attempts: 3 + ND reachable time is 35780 milliseconds + ND base reachable time is 30000 milliseconds + ND advertised reachable time is 0 milliseconds + ND advertised retransmit interval is 0 milliseconds + ND router advertisements are sent every 198 to 600 seconds + ND router advertisements live for 1800 seconds + ND advertised hop limit is 64 + IPv6 hop limit for originated packets is 64 + IPv6 unicast RPF check is not supported + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_lldp_neighbors_detail b/tests/unit/modules/network/dellos9/fixtures/show_lldp_neighbors_detail new file mode 100644 index 0000000..a868571 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_lldp_neighbors_detail @@ -0,0 +1,35 @@ +======================================================================== + Local Interface Ma 0/0 has 1 neighbor + Total Frames Out: 1747 + Total Frames In: 10333 + Total Neighbor information Age outs: 0 + Total Multiple Neighbors Detected: 0 + Total Frames Discarded: 0 + Total In Error Frames: 0 + Total Unrecognized TLVs: 0 + Total TLVs Discarded: 0 + Next packet will be sent after 17 seconds + The neighbors are given below: + ----------------------------------------------------------------------- + + Remote Chassis ID Subtype: Mac address (4) + Remote Chassis ID: 90:b1:1c:f4:2f:6d + Remote Port Subtype: Interface name (5) + Remote Port ID: TenGigabitEthernet 0/33 + Remote Port Description: TenGigabitEthernet 0/33 + Local Port ID: ManagementEthernet 0/0 + Locally assigned remote Neighbor Index: 1 + Remote TTL: 20 + Information valid for next 17 seconds + Time since last information change of this neighbor: 14:54:48 + Remote System Name: swlab1-maa-tor-A2 + Remote System Desc: Dell Real Time Operating System Software. Dell + Operating System Version: 2.0. Dell Application Software Version: + 9.11(2.0) Copyright (c) 1999-2017Dell Inc. All Rights Reserved.Build + Time: Tue Apr 25 21:22:59 2017 + Existing System Capabilities: Repeater Bridge Router + Enabled System Capabilities: Repeater Bridge Router + Remote Port Vlan ID: 148 + Port and Protocol Vlan ID: 148, Capability: Supported, Status: Enabled + --------------------------------------------------------------------------- + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_memory__except_Processor b/tests/unit/modules/network/dellos9/fixtures/show_memory__except_Processor new file mode 100644 index 0000000..c2f6541 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_memory__except_Processor @@ -0,0 +1,4 @@ + =========================== + Total(b) Used(b) Free(b) Lowest(b) Largest(b) + 3203911680 3172120 3200739560 3200673304 3200739560 + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_running-config b/tests/unit/modules/network/dellos9/fixtures/show_running-config new file mode 100644 index 0000000..4804ebb --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_running-config @@ -0,0 +1,238 @@ +Current Configuration ... +! Version 9.12(0.0) +! Last configuration change at Thu Jan 11 06:53:29 2018 by admin +! +! +logging coredump stack-unit 0 +logging coredump stack-unit 1 +logging coredump stack-unit 2 +logging coredump stack-unit 3 +logging coredump stack-unit 4 +logging coredump stack-unit 5 +! +hostname Dell +! +protocol lldp +! +redundancy auto-synchronize full +! +enable password 7 b125455cf679b208e79b910e85789edf +! +username admin password 7 1d28e9f33f99cf5c +! +stack-unit 0 quad-port-profile 0,8,16,24,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,100,108,116,124 +! +stack-unit 0 provision S6000 +! +stack-unit 0 port 0 portmode quad +! +interface TenGigabitEthernet 0/0 + no ip address + shutdown +! +interface TenGigabitEthernet 0/1 + no ip address + shutdown +! +interface TenGigabitEthernet 0/2 + no ip address + shutdown +! +interface TenGigabitEthernet 0/3 + no ip address + shutdown +! +interface fortyGigE 0/4 + no ip address + shutdown +! +interface fortyGigE 0/8 + no ip address + shutdown +! +interface fortyGigE 0/12 + no ip address + shutdown +! +interface fortyGigE 0/16 + no ip address + ipv6 address 2001:4898:5808:ffa2::5/126 + shutdown +! +interface fortyGigE 0/20 + no ip address + switchport + ip access-group ipv6-ssh-only in + shutdown +! +interface fortyGigE 0/24 + no ip address + switchport + mac access-group ssh-only-mac in + mac access-group ssh-only-mac out + shutdown +! +interface fortyGigE 0/28 + no ip address + switchport + mac access-group ssh-only-mac in + mac access-group ssh-only-mac out + shutdown +! +interface fortyGigE 0/32 + no ip address + switchport + ip access-group ipv6-ssh-only out + shutdown +! +interface fortyGigE 0/36 + no ip address + shutdown +! +interface fortyGigE 0/40 + no ip address + shutdown +! +interface fortyGigE 0/44 + no ip address + shutdown +! +interface fortyGigE 0/48 + no ip address + shutdown +! +interface fortyGigE 0/52 + no ip address + shutdown +! +interface fortyGigE 0/56 + no ip address + shutdown +! +interface fortyGigE 0/60 + no ip address + shutdown +! +interface fortyGigE 0/64 + no ip address + shutdown +! +interface fortyGigE 0/68 + no ip address + shutdown +! +interface fortyGigE 0/72 + no ip address + shutdown +! +interface fortyGigE 0/76 + no ip address + shutdown +! +interface fortyGigE 0/80 + no ip address + shutdown +! +interface fortyGigE 0/84 + no ip address + shutdown +! +interface fortyGigE 0/88 + no ip address + shutdown +! +interface fortyGigE 0/92 + no ip address + shutdown +! +interface fortyGigE 0/96 + no ip address + shutdown +! +interface fortyGigE 0/100 + no ip address + shutdown +! +interface fortyGigE 0/104 + no ip address + shutdown +! +interface fortyGigE 0/108 + no ip address + shutdown +! +interface fortyGigE 0/112 + no ip address + shutdown +! +interface fortyGigE 0/116 + no ip address + shutdown +! +interface fortyGigE 0/120 + no ip address + shutdown +! +interface fortyGigE 0/124 + no ip address + shutdown +! +interface ManagementEthernet 0/0 + ip address 10.16.148.71/16 + no shutdown +! +interface ManagementEthernet 1/0 + no shutdown +! +interface ManagementEthernet 2/0 + no shutdown +! +interface ManagementEthernet 3/0 + no shutdown +! +interface ManagementEthernet 4/0 + no shutdown +! +interface ManagementEthernet 5/0 + no shutdown +! +interface Vlan 1 +!untagged fortyGigE 0/20-32 +! +ipv6 access-list ipv6-ssh-only + description ipv6acl + remark 1 ipv6 + seq 10 permit ipv6 2001:4898::/32 any + seq 20 permit tcp any eq 2 2404:f801::/32 + seq 30 permit tcp any 2a01:110::/31 ack + seq 40 permit tcp any any +! +mac access-list extended ssh-only-mac + description macacl + remark 1 mac + seq 5 permit any any count + seq 6 deny any any +! +ip ssh server enable +! +line console 0 +line vty 0 +line vty 1 + access-class ipv6-ssh-only ipv6 +line vty 2 + access-class ipv6-ssh-only ipv6 +line vty 3 + access-class ipv6-ssh-only ipv6 +line vty 4 +line vty 5 +line vty 6 +line vty 7 +line vty 8 +line vty 9 +! +reload-type + boot-type normal-reload + config-scr-download enable +! +end + diff --git a/tests/unit/modules/network/dellos9/fixtures/show_running-config__grep_hostname b/tests/unit/modules/network/dellos9/fixtures/show_running-config__grep_hostname new file mode 100644 index 0000000..7c4137c --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_running-config__grep_hostname @@ -0,0 +1 @@ +hostname dellos9_sw1 diff --git a/tests/unit/modules/network/dellos9/fixtures/show_version b/tests/unit/modules/network/dellos9/fixtures/show_version new file mode 100644 index 0000000..e385cf3 --- /dev/null +++ b/tests/unit/modules/network/dellos9/fixtures/show_version @@ -0,0 +1,18 @@ +Dell Real Time Operating System Software +Dell Operating System Version: 2.0 +Dell Application Software Version: 9.10(0.1P13) +Copyright (c) 1999-2016 by Dell Inc. All Rights Reserved. +Build Time: Wed Sep 7 23:48:35 2016 +Build Path: /sites/eqx/work/swbuild01_1/build01/E9-10-0/SW/SRC +Dell Networking OS uptime is 12 week(s), 6 day(s), 9 hour(s), 20 minute(s) + +System image file is "system://A" + +System Type: S6000-ON +Control Processor: Intel Centerton with 3 Gbytes (3203911680 bytes) of memory, core(s) 2. + +16G bytes of boot flash memory. + + 1 32-port TE/FG (SI-ON) + 32 Forty GigabitEthernet/IEEE 802.3 interface(s) + diff --git a/tests/unit/modules/network/dellos9/test_dellos9_command.py b/tests/unit/modules/network/dellos9/test_dellos9_command.py new file mode 100644 index 0000000..f9959ba --- /dev/null +++ b/tests/unit/modules/network/dellos9/test_dellos9_command.py @@ -0,0 +1,110 @@ +# (c) 2016 Red Hat Inc. +# +# (c) 2017 Dell EMC. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch +from ansible_collections.dellemc_networking.os9.plugins.modules import dellos9_command +from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import set_module_args +from ..dellos9_module import TestDellos9Module, load_fixture + + +class TestDellos9CommandModule(TestDellos9Module): + + module = dellos9_command + + def setUp(self): + super(TestDellos9CommandModule, self).setUp() + + self.mock_run_commands = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_command.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestDellos9CommandModule, self).tearDown() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item['command']) + command = obj['command'] + except ValueError: + command = item['command'] + filename = str(command).replace(' ', '_') + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_dellos9_command_simple(self): + set_module_args(dict(commands=['show version'])) + result = self.execute_module() + self.assertEqual(len(result['stdout']), 1) + self.assertTrue(result['stdout'][0].startswith('Dell Real Time')) + + def test_dellos9_command_multiple(self): + set_module_args(dict(commands=['show version', 'show version'])) + result = self.execute_module() + self.assertEqual(len(result['stdout']), 2) + self.assertTrue(result['stdout'][0].startswith('Dell Real Time')) + + def test_dellos9_command_wait_for(self): + wait_for = 'result[0] contains "Dell Real"' + set_module_args(dict(commands=['show version'], wait_for=wait_for)) + self.execute_module() + + def test_dellos9_command_wait_for_fails(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=['show version'], wait_for=wait_for)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 10) + + def test_dellos9_command_retries(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=['show version'], wait_for=wait_for, retries=2)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 2) + + def test_dellos9_command_match_any(self): + wait_for = ['result[0] contains "Dell Real"', + 'result[0] contains "test string"'] + set_module_args(dict(commands=['show version'], wait_for=wait_for, match='any')) + self.execute_module() + + def test_dellos9_command_match_all(self): + wait_for = ['result[0] contains "Dell Real"', + 'result[0] contains "Operating System"'] + set_module_args(dict(commands=['show version'], wait_for=wait_for, match='all')) + self.execute_module() + + def test_dellos9_command_match_all_failure(self): + wait_for = ['result[0] contains "Dell Real"', + 'result[0] contains "test string"'] + commands = ['show version', 'show version'] + set_module_args(dict(commands=commands, wait_for=wait_for, match='all')) + self.execute_module(failed=True) diff --git a/tests/unit/modules/network/dellos9/test_dellos9_config.py b/tests/unit/modules/network/dellos9/test_dellos9_config.py new file mode 100644 index 0000000..3319686 --- /dev/null +++ b/tests/unit/modules/network/dellos9/test_dellos9_config.py @@ -0,0 +1,150 @@ +# +# (c) 2016 Red Hat Inc. +# +# (c) 2017 Dell EMC. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch +from ansible_collections.dellemc_networking.os9.plugins.modules import dellos9_config +from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import set_module_args +from ..dellos9_module import TestDellos9Module, load_fixture + + +class TestDellos9ConfigModule(TestDellos9Module): + + module = dellos9_config + + def setUp(self): + super(TestDellos9ConfigModule, self).setUp() + + self.mock_get_config = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_config.get_config') + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_config.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_run_commands = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_config.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestDellos9ConfigModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + config_file = 'dellos9_config_config.cfg' + self.get_config.return_value = load_fixture(config_file) + self.load_config.return_value = None + + def test_dellos9_config_unchanged(self): + src = load_fixture('dellos9_config_config.cfg') + set_module_args(dict(src=src)) + self.execute_module() + + def test_dellos9_config_src(self): + src = load_fixture('dellos9_config_src.cfg') + set_module_args(dict(src=src)) + commands = ['hostname foo', 'interface fortyGigE 1/6', + 'no ip address'] + self.execute_module(changed=True, commands=commands) + + def test_dellos9_config_backup(self): + set_module_args(dict(backup=True)) + result = self.execute_module() + self.assertIn('__backup__', result) + + def test_dellos9_config_save(self): + set_module_args(dict(save=True)) + self.execute_module(changed=True) + self.assertEqual(self.run_commands.call_count, 1) + self.assertEqual(self.get_config.call_count, 0) + self.assertEqual(self.load_config.call_count, 0) + args = self.run_commands.call_args[0][1] + self.assertDictContainsSubset({'command': 'copy running-config startup-config'}, args[0]) +# self.assertIn('copy running-config startup-config\r', args) + + def test_dellos9_config_lines_wo_parents(self): + set_module_args(dict(lines=['hostname foo'])) + commands = ['hostname foo'] + self.execute_module(changed=True, commands=commands) + + def test_dellos9_config_lines_w_parents(self): + set_module_args(dict(lines=['shutdown'], parents=['interface fortyGigE 1/6'])) + commands = ['interface fortyGigE 1/6', 'shutdown'] + self.execute_module(changed=True, commands=commands) + + def test_dellos9_config_before(self): + set_module_args(dict(lines=['hostname foo'], before=['snmp-server contact bar'])) + commands = ['snmp-server contact bar', 'hostname foo'] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_dellos9_config_after(self): + set_module_args(dict(lines=['hostname foo'], after=['snmp-server contact bar'])) + commands = ['hostname foo', 'snmp-server contact bar'] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_dellos9_config_before_after_no_change(self): + set_module_args(dict(lines=['hostname router'], + before=['snmp-server contact bar'], + after=['snmp-server location chennai'])) + self.execute_module() + + def test_dellos9_config_config(self): + config = 'hostname localhost' + set_module_args(dict(lines=['hostname router'], config=config)) + commands = ['hostname router'] + self.execute_module(changed=True, commands=commands) + + def test_dellos9_config_replace_block(self): + lines = ['description test string', 'test string'] + parents = ['interface fortyGigE 1/6'] + set_module_args(dict(lines=lines, replace='block', parents=parents)) + commands = parents + lines + self.execute_module(changed=True, commands=commands) + + def test_dellos9_config_match_none(self): + lines = ['hostname router'] + set_module_args(dict(lines=lines, match='none')) + self.execute_module(changed=True, commands=lines) + + def test_dellos9_config_match_none(self): + lines = ['ip address 1.2.3.4/24', 'description test string'] + parents = ['interface fortyGigE 1/6'] + set_module_args(dict(lines=lines, parents=parents, match='none')) + commands = parents + lines + self.execute_module(changed=True, commands=commands, sort=False) + + def test_dellos9_config_match_strict(self): + lines = ['ip address 1.2.3.4/24', 'description test string', + 'shutdown'] + parents = ['interface fortyGigE 1/6'] + set_module_args(dict(lines=lines, parents=parents, match='strict')) + commands = parents + ['shutdown'] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_dellos9_config_match_exact(self): + lines = ['ip address 1.2.3.4/24', 'description test string', + 'shutdown'] + parents = ['interface fortyGigE 1/6'] + set_module_args(dict(lines=lines, parents=parents, match='exact')) + commands = parents + lines + self.execute_module(changed=True, commands=commands, sort=False) diff --git a/tests/unit/modules/network/dellos9/test_dellos9_facts.py b/tests/unit/modules/network/dellos9/test_dellos9_facts.py new file mode 100644 index 0000000..2a375d1 --- /dev/null +++ b/tests/unit/modules/network/dellos9/test_dellos9_facts.py @@ -0,0 +1,106 @@ +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch +from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import set_module_args +from ..dellos9_module import TestDellos9Module, load_fixture +from ansible_collections.dellemc_networking.os9.plugins.modules import dellos9_facts + + +class TestDellos9Facts(TestDellos9Module): + + module = dellos9_facts + + def setUp(self): + super(TestDellos9Facts, self).setUp() + + self.mock_run_command = patch( + 'ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_facts.run_commands') + self.run_command = self.mock_run_command.start() + + def tearDown(self): + super(TestDellos9Facts, self).tearDown() + + self.mock_run_command.stop() + + def load_fixtures(self, commands=None): + + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item) + command = obj['command'] + except ValueError: + command = item + if '|' in command: + command = str(command).replace('|', '') + filename = str(command).replace(' ', '_') + filename = filename.replace('/', '7') + output.append(load_fixture(filename)) + return output + + self.run_command.side_effect = load_from_file + + def test_dellos9_facts_gather_subset_default(self): + set_module_args(dict()) + result = self.execute_module() + ansible_facts = result['ansible_facts'] + self.assertIn('hardware', ansible_facts['ansible_net_gather_subset']) + self.assertIn('default', ansible_facts['ansible_net_gather_subset']) + self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset']) + self.assertEqual('dellos9_sw1', ansible_facts['ansible_net_hostname']) + self.assertIn('fortyGigE 0/24', ansible_facts['ansible_net_interfaces'].keys()) + self.assertEqual(3128820, ansible_facts['ansible_net_memtotal_mb']) + self.assertEqual(3125722, ansible_facts['ansible_net_memfree_mb']) + + def test_dellos9_facts_gather_subset_config(self): + set_module_args({'gather_subset': 'config'}) + result = self.execute_module() + ansible_facts = result['ansible_facts'] + self.assertIn('default', ansible_facts['ansible_net_gather_subset']) + self.assertIn('config', ansible_facts['ansible_net_gather_subset']) + self.assertEqual('dellos9_sw1', ansible_facts['ansible_net_hostname']) + self.assertIn('ansible_net_config', ansible_facts) + + def test_dellos9_facts_gather_subset_hardware(self): + set_module_args({'gather_subset': 'hardware'}) + result = self.execute_module() + ansible_facts = result['ansible_facts'] + self.assertIn('default', ansible_facts['ansible_net_gather_subset']) + self.assertIn('hardware', ansible_facts['ansible_net_gather_subset']) + self.assertEqual(['flash', 'fcmfs', 'nfsmount', 'ftp', 'tftp', 'scp', 'http', 'https'], ansible_facts['ansible_net_filesystems']) + self.assertEqual(3128820, ansible_facts['ansible_net_memtotal_mb']) + self.assertEqual(3125722, ansible_facts['ansible_net_memfree_mb']) + + def test_dellos9_facts_gather_subset_interfaces(self): + set_module_args({'gather_subset': 'interfaces'}) + result = self.execute_module() + ansible_facts = result['ansible_facts'] + self.assertIn('default', ansible_facts['ansible_net_gather_subset']) + self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset']) + self.assertIn('fortyGigE 0/24', ansible_facts['ansible_net_interfaces'].keys()) + self.assertEqual(['Ma 0/0'], list(ansible_facts['ansible_net_neighbors'].keys())) + self.assertIn('ansible_net_interfaces', ansible_facts) diff --git a/tests/unit/modules/utils.py b/tests/unit/modules/utils.py new file mode 100644 index 0000000..86ea270 --- /dev/null +++ b/tests/unit/modules/utils.py @@ -0,0 +1,47 @@ +import json + +from ansible_collections.dellemc_networking.os9.tests.unit.compat import unittest +from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +def set_module_args(args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase(unittest.TestCase): + + def setUp(self): + self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) + self.mock_module.start() + self.mock_sleep = patch('time.sleep') + self.mock_sleep.start() + set_module_args({}) + self.addCleanup(self.mock_module.stop) + self.addCleanup(self.mock_sleep.stop) diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt new file mode 100644 index 0000000..a9772be --- /dev/null +++ b/tests/unit/requirements.txt @@ -0,0 +1,42 @@ +boto3 +placebo +pycrypto +passlib +pypsrp +python-memcached +pytz +pyvmomi +redis +requests +setuptools > 0.6 # pytest-xdist installed via requirements does not work with very old setuptools (sanity_ok) +unittest2 ; python_version < '2.7' +importlib ; python_version < '2.7' +netaddr +ipaddress +netapp-lib +solidfire-sdk-python + +# requirements for F5 specific modules +f5-sdk ; python_version >= '2.7' +f5-icontrol-rest ; python_version >= '2.7' +deepdiff + +# requirement for Fortinet specific modules +pyFMG + +# requirement for aci_rest module +xmljson + +# requirement for winrm connection plugin tests +pexpect + +# requirement for the linode module +linode-python # APIv3 +linode_api4 ; python_version > '2.6' # APIv4 + +# requirement for the gitlab module +python-gitlab +httmock + +# requirment for kubevirt modules +openshift ; python_version >= '2.7'