diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/mso | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/mso')
238 files changed, 62167 insertions, 0 deletions
diff --git a/ansible_collections/cisco/mso/.DS_Store b/ansible_collections/cisco/mso/.DS_Store Binary files differnew file mode 100644 index 000000000..9538dd02f --- /dev/null +++ b/ansible_collections/cisco/mso/.DS_Store diff --git a/ansible_collections/cisco/mso/.github/.DS_Store b/ansible_collections/cisco/mso/.github/.DS_Store Binary files differnew file mode 100644 index 000000000..1d3c85241 --- /dev/null +++ b/ansible_collections/cisco/mso/.github/.DS_Store diff --git a/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md new file mode 100644 index 000000000..f914a17c5 --- /dev/null +++ b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -0,0 +1,80 @@ +--- +name: 🐛 Bug Report +about: If something isn't working as expected 🤔. +labels: bug +--- + +<!--- Please keep this note for the community ---> + +### Community Note + +* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request +* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request +* If you are interested in working on this issue or have submitted a pull request, please leave a comment + +<!--- Thank you for keeping this note for the community ---> + +### Description +<!--- Please specify additional labels in the labels section if applicable to the issue. +Possible additional labels: documentation/question ---> + +<!--- Please leave a helpful description of the feature request here. ---> + +* xxxx + +### Affected Module Name(s): + +<!--- Please list the affected module name(s). ---> + +* mso_XXXXX + +### MSO version and MSO Platform + +* V x.x.x and Docker Swarm OVA-based/SE-based/ND-based/all. + + +### APIC version and APIC Platform for Site Level Resources + +* V x.x.x and on-prem/cloud-aws/cloud-azure/all. + + +### Collection versions + +* cisco.mso x.x.x + +### Output/ Error message + +<!--- +Please provide the generated error message. +---> +* + +### Expected Behavior + +<!--- What should have happened? ---> +* + +### Actual Behavior + +<!--- What actually happened? ---> +* + +### Playbook tasks to Reproduce + +<!--- Please list the playbook tasks required to reproduce the issue. ---> + +* + +### Important Factoids + +<!--- Are there anything atypical about your setup that we should know? For example: Same task runs on different version of MSO? ---> + +### References + +<!--- +Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests + +Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor documentation? For example: +---> + +* #0000
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md new file mode 100644 index 000000000..c1bb98962 --- /dev/null +++ b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md @@ -0,0 +1,54 @@ +--- +name: 🚀 Feature Request +about: I have a suggestion (might want to implement it myself 🙂)! +labels: enhancement +--- + +<!--- Please keep this note for the community ---> + +### Community Note + +* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request +* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request +* If you are interested in working on this issue or have submitted a pull request, please leave a comment + +<!--- Thank you for keeping this note for the community ---> + +### Description +<!--- Please specify additional labels in the labels section if applicable to the issue. +Possible additional labels: new_module/new_plugin/documentation/feature ---> + + +<!--- Please leave a helpful description of the feature request here. ---> + +* xxxx + +### New or Affected Module(s): + +<!--- Please list the new or affected module(s). ---> + +* mso_XXXXX + +### MSO version and MSO Platform + +* V x.x.x and Docker Swarm OVA-based/SE-based/ND-based/all. + + +### APIC version and APIC Platform for Site Level Resources + +* V x.x.x and on-prem/cloud-aws/cloud-azure/all. + +### Collection versions + +* cisco.mso x.x.x + +### References + +<!--- +Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests + +Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation? For example: + +---> + +* #0000
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..ec4bb386b --- /dev/null +++ b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/.github/workflows/ansible-test.yml b/ansible_collections/cisco/mso/.github/workflows/ansible-test.yml new file mode 100644 index 000000000..08ed31bba --- /dev/null +++ b/ansible_collections/cisco/mso/.github/workflows/ansible-test.yml @@ -0,0 +1,249 @@ +name: CI +on: + push: + branches: master + pull_request: + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '0 7 * * *' +env: + python_version: 3.9 +jobs: + build: + name: Build collection + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ansible: [v2.9.27, v2.10.17, v2.11.12, v2.12.10, stable-2.13, stable-2.14] + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Set up Python ${{ env.python_version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ env.python_version }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Build a collection tarball + run: ansible-galaxy collection build --output-path "${GITHUB_WORKSPACE}/.cache/collection-tarballs" + + - name: Store migrated collection artifacts + uses: actions/upload-artifact@v3 + with: + name: collection + path: .cache/collection-tarballs + + black-formating: + name: Using Black to check formating + runs-on: ubuntu-latest + continue-on-error: true + strategy: + fail-fast: false + matrix: + experimental: [true] + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Run black against code + uses: psf/black@stable + with: + options: "--check --diff --color -l 159" + + importer: + name: Galaxy-importer check + needs: + - build + runs-on: ubuntu-latest + steps: + - name: Set up Python ${{ env.python_version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ env.python_version }} + + - name: Install ansible-base (stable-2.14) + run: pip install https://github.com/ansible/ansible/archive/stable-2.14.tar.gz --disable-pip-version-check + + - name: Download migrated collection artifacts + uses: actions/download-artifact@v3 + with: + name: collection + path: .cache/collection-tarballs + + - name: Install the collection tarball + run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz + + - name: Install galaxy-importer + run: pip install galaxy-importer + + - name: Create galaxy-importer directory + run: sudo mkdir -p /etc/galaxy-importer + + - name: Create galaxy-importer.cfg + run: sudo cp /home/runner/.ansible/collections/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg /etc/galaxy-importer/galaxy-importer.cfg + + - name: Run galaxy-importer check + run: python -m galaxy_importer.main .cache/collection-tarballs/cisco-*.tar.gz | tee .cache/collection-tarballs/log.txt && sudo cp ./importer_result.json .cache/collection-tarballs/importer_result.json + + - name: Check warnings and errors + run: if grep -E 'WARNING|ERROR' .cache/collection-tarballs/log.txt; then exit 1; else exit 0; fi + + - name: Store galaxy_importer check log file + uses: actions/upload-artifact@v3 + with: + name: galaxy-importer-log + path: .cache/collection-tarballs/importer_result.json + + + sanity: + name: Sanity in ubuntu-latest + needs: + - build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ansible: [v2.9.27, v2.10.17, v2.11.12, v2.12.10, stable-2.13, stable-2.14] + steps: + - name: Set up Python ${{ env.python_version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ env.python_version }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Install coverage (v4.5.4) + run: pip install coverage==4.5.4 + + - name: Download migrated collection artifacts + uses: actions/download-artifact@v3 + with: + name: collection + path: .cache/collection-tarballs + + - name: Install the collection tarball + run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz + + - name: Run sanity tests + run: ansible-test sanity --docker -v --color --truncate 0 --coverage + working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + - name: Generate coverage report + run: ansible-test coverage xml -v --requirements --group-by command --group-by version + working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + - name: Push coverate report to codecov.io + run: bash <(curl -s https://codecov.io/bash) -s 'tests/output/reports/' -F sanity + working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + units: + name: Units in ubuntu-latest + needs: + - build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ansible: [v2.10.17, v2.11.12, v2.12.10, stable-2.13, stable-2.14] + python-version: [3.9] + include: + - ansible: v2.9.27 + python-version: 3.8 + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install ansible-base (${{ matrix.ansible }}) + run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + + - name: Install coverage (v4.5.4) + run: pip install coverage==4.5.4 + + - name: Download migrated collection artifacts + uses: actions/download-artifact@v3 + with: + name: collection + path: .cache/collection-tarballs + + - name: Install the collection tarball + run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz + + # - name: Run unit tests + # run: ansible-test units --docker -v --color --truncate 0 --python ${{ matrix.python-version }} --coverage + # working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + # - name: Generate coverage report. + # run: ansible-test coverage xml -v --requirements --group-by command --group-by version + # working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + # - name: Push coverate report to codecov.io + # run: bash <(curl -s https://codecov.io/bash) -s 'tests/output/reports/' -F unit + # working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + integration: + name: Integration in ubuntu-latest + needs: + - build + runs-on: ubuntu-latest + steps: + - name: Set up Python ${{ env.python_version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ env.python_version }} + + - name: Install ansible-base (stable-2.13) + run: pip install https://github.com/ansible/ansible/archive/stable-2.13.tar.gz --disable-pip-version-check + + - name: Install coverage (v4.5.4) + run: pip install coverage==4.5.4 + + - name: Download migrated collection artifacts + uses: actions/download-artifact@v3 + with: + name: collection + path: .cache/collection-tarballs + + - name: Install the collection tarball + run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz + + - name: Install the ND collection (NDO dependency) + run: ansible-galaxy collection install cisco.nd + + - name: Install the ACI collection (test case dependency) + run: ansible-galaxy collection install cisco.aci + + - name: Requesting integration mutex + uses: nev7n/wait_for_response@v1 + with: + url: ${{ format('https://8v7s765ibh.execute-api.us-west-1.amazonaws.com/v1/ansible-mso?repo={0}&run_id={1}', github.repository, github.run_id) }} + responseCode: 200 + timeout: 2000000 + interval: 5000 + + - name: Run integration tests on Python ${{ env.python_version }} + run: ansible-test network-integration --docker -v --color --retry-on-error --python ${{ env.python_version }} --truncate 0 --continue-on-error --coverage + working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + - name: Releasing integration mutex + uses: nev7n/wait_for_response@v1 + if: always() + with: + url: ${{ format('https://8v7s765ibh.execute-api.us-west-1.amazonaws.com/v1/ansible-mso/release?repo={0}&run_id={1}', github.repository, github.run_id) }} + responseCode: 200 + + - name: Generate coverage report + if: always() + run: ansible-test coverage xml -v --requirements --group-by command --group-by version + working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso + + - name: Push coverate report to codecov.io + if: always() + run: bash <(curl -s https://codecov.io/bash) -s 'tests/output/reports/' -F integration + working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso diff --git a/ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml b/ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml new file mode 100644 index 000000000..a6342e23b --- /dev/null +++ b/ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml @@ -0,0 +1,32 @@ +name: Generate Changelog +on: + push: + branches: master +jobs: + generate-changelog: + name: Run automation script + runs-on: ubuntu-latest + steps: + - name: Check out script code + uses: actions/checkout@v3 + with: + repository: ciscoecosystem/release_script + path: ./release_script + + - name: Check out collection code + uses: actions/checkout@v3 + with: + path: ./collection + + - name: Set up Python 3.9 + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install release script requirements + run: pip install -r ./release_script/requirements.txt + + - name: Run automation script + run: python ./release_script/script.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml b/ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..76aa96c9b --- /dev/null +++ b/ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml @@ -0,0 +1,43 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + + schedule: + - cron: '40 8 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg b/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg new file mode 100644 index 000000000..631359cf4 --- /dev/null +++ b/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg @@ -0,0 +1,9 @@ +[galaxy-importer] +LOG_LEVEL_MAIN = INFO +RUN_FLAKE8 = True +RUN_ANSIBLE_DOC = True +RUN_ANSIBLE_LINT = True +RUN_ANSIBLE_TEST = False +ANSIBLE_TEST_LOCAL_IMAGE = False +LOCAL_IMAGE_DOCKER = False +INFRA_OSD = False
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/.gitignore b/ansible_collections/cisco/mso/.gitignore new file mode 100644 index 000000000..d984fa808 --- /dev/null +++ b/ansible_collections/cisco/mso/.gitignore @@ -0,0 +1,394 @@ + +# 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 +.DS_Store + +# 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 + +# vsCode +.vscode + +# Ansible Collection tarball +cisco-mso-*.tar.gz diff --git a/ansible_collections/cisco/mso/.vscode/settings.json b/ansible_collections/cisco/mso/.vscode/settings.json new file mode 100644 index 000000000..1d0d5ec8f --- /dev/null +++ b/ansible_collections/cisco/mso/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "python.linting.flake8Enabled": true, + "python.linting.enabled": true, + "python.linting.pylintArgs": [ + "--max-line-length=160" + ], + "python.formatting.autopep8Args": [ + "--max-line-length=160" + ], + "python.linting.flake8Args": [ + "--max-line-length=160" + ], + "python.linting.pycodestyleArgs": [ + "--max-line-length=160" + ], + "python.linting.pycodestyleEnabled": true, + "terminal.integrated.scrollback": 100000, +}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/CHANGELOG.rst b/ansible_collections/cisco/mso/CHANGELOG.rst new file mode 100644 index 000000000..b7b32a9e4 --- /dev/null +++ b/ansible_collections/cisco/mso/CHANGELOG.rst @@ -0,0 +1,481 @@ +========================================== +Cisco MSO Ansible Collection Release Notes +========================================== + +.. contents:: Topics + +This changelog describes changes after version 0.0.4. + +v2.4.0 +====== + +Release Summary +--------------- + +Release v2.4.0 of the ``ansible-mso`` collection on 2023-04-19. +This changelog describes all changes made to the modules and plugins included in this collection since v2.3.0. + + +Minor Changes +------------- + +- Add ip_data_plane_learning and preferred_group arguments to mso_schema_template_vrf module (#358) + +Bugfixes +-------- + +- Add attributes to payload for changed schema behaviour of deploymentImmediacy (deployImmediacy) and vmmDomainProperties (properties at domain level in payload) (#362) +- Fix mso_backup for NDO and ND-based MSO v3.2+ (#333) +- Fix validation condition for path in mso_schema_site_anp_epg_bulk_staticport module (#360) + +v2.3.0 +====== + +Release Summary +--------------- + +Release v2.3.0 of the ``ansible-mso`` collection on 2023-03-30. +This changelog describes all changes made to the modules and plugins included in this collection since v2.2.1. + + +Minor Changes +------------- + +- Add module mso_schema_site_anp_epg_bulk_staticport (#330) +- Add route_reachability attribute to mso_schema_site_external_epg module (#335) + +Bugfixes +-------- + +- Fix idempotency for mso_schema_site_bd_l3out + +v2.2.1 +====== + +Release Summary +--------------- + +Release v2.2.1 of the ``ansible-mso`` collection on 2023-01-31. +This changelog describes all changes made to the modules and plugins included in this collection since v2.2.0. + + +Bugfixes +-------- + +- Fix datetime support for python2.7 in mso_backup_schedule (#323) + +v2.2.0 +====== + +Release Summary +--------------- + +Release v2.2.0 of the ``ansible-mso`` collection on 2023-01-29. +This changelog describes all changes made to the modules and plugins included in this collection since v2.1.0. + + +Minor Changes +------------- + +- Add automatic creation of site bd when not existing in mso_schema_site_bd_subnet module (#263) +- Add automatic schema validation functionality to mso_schema_template_deploy and ndo_schema_template_deploy (#318) +- Add ndo_schema_template_deploy to support NDO 4+ deploy functionality (#305) +- Add support for l3out from different template or schema in mso_schema_site_bd_l3out (#304) +- Add support for orchestrator_only attribute for mso_tenant with state absent (#268) + +Bugfixes +-------- + +- Fix MSO HTTPAPI plugin login domain issue (#317) +- Fix deploymentImmediacy key inconsistency in the API used by mso_schema_site_anp and mso_schema_site_anp_epg (#283) +- Fix mso_schema_template_bd issue when created with unicast_routing as false (#278) +- Fix to be able to add multiple filter and filters with "-" in their names (#306) + +v2.1.0 +====== + +Release Summary +--------------- + +Release v2.1.0 of the ``ansible-mso`` collection on 2022-10-14. +This changelog describes all changes made to the modules and plugins included in this collection since v1.4.0. +The version was bumped directly to 2.1.0 due to a previous collection upload issue on galaxy. + + +Minor Changes +------------- + +- Add aci_remote_location module (#259) +- Add mso_backup_schedule module (#250) +- Add mso_chema_template_contract_service_graph module (#257) +- Add mso_schema_template_service_graph, mso_schema_site_service_graph and mso_service_node_type modules (#243) +- Add primary attribute to mso_schema_site_bd_subnet (#254) + +Deprecated Features +------------------- + +- The mso_schema_template_contract_filter contract_filter_type attribute is deprecated. The value is now deduced from filter_type. + +Bugfixes +-------- + +- Fix time issue when host running ansible is in a different timezone then NDO +- Remove mso_guide from notes + +v1.4.0 +====== + +Release Summary +--------------- + +Release v1.4.0 of the ``ansible-mso`` collection on 2022-03-15. +This changelog describes all changes made to the modules and plugins included in this collection since v1.3.0. + + +Minor Changes +------------- + +- Update mso_schema_template_clone to use new method from NDO and unrestrict it to earlier version + +Bugfixes +-------- + +- Fix arp_entry value issue in mso_schema_template_filter_entry +- Fix mso_schema_site_anp idempotency when children exists +- Fix use_ssl documentation to explain usage when used with HTTPAPI connection plugin + +v1.3.0 +====== + +Release Summary +--------------- + +Release v1.3.0 of the ``cisco.mso`` collection on 2021-12-18. +This changelog describes all changes made to the modules and plugins included in this collection since v1.2.0. + + +Minor Changes +------------- + +- Add container_overlay and underlay_context_profile support to mso_schema_site_vrf_region +- Add description support to various modules +- Add hosted_vrf support to mso_schema_site_vrf_region_cidr_subnet +- Add module mso_schema_validate to check schema validations +- Add private_link_label support to mso_schema_site_anp_epg and mso_schema_site_vrf_region_cidr_subnet +- Add qos_level and Service EPG support to mso_schema_template_anp_epg +- Add qos_level, action and priority support to mso_schema_template_contract_filter +- Add schema and template description support to mso_schema_template +- Add subnet as primary support to mso_schema_template_bd_subnet +- Add support for automatically creating anp structure at site level when using mso_schema_site_anp_epg +- Add support for encap-flood as multi_destination_flooding in mso_schema_template_bd +- Add test file for mso_schema_site_anp, mso_schema_site_anp_epg, mso_schema_template_external_epg_subnet mso_schema_template_filter_entry +- Improve scope attribute documentation in mso_schema_template_external_epg_subnet +- Update Ansible version used in automated testing to v2.9.27, v2.10.16 and addition of v2.11.7 and v2.12.1 + +Bugfixes +-------- + +- Add no_log to aws_access_key and secret_key in mso_tenant_site +- Fix MSO HTTP API to work without host, user and password module attribute +- Fix issue with unicast_routing idemptotency in mso_schema_template_bd +- Fix mso_schema_site_anp and mso_schema_site_anp_epg idempotency issue +- Remove sanity ignore files and fix sanity issues that were previously ignored + +v1.2.0 +====== + +Release Summary +--------------- + +Release v1.2.0 of the ``cisco.mso`` collection on 2021-06-02. +This changelog describes all changes made to the modules and plugins included in this collection since v1.1.0. + + +Minor Changes +------------- + +- Add Ansible common HTTPAPI dependancy in galaxy.yml +- Add HTTPAPI connection plugin support and HTTPAPI MSO connection plugin +- Add primary and unicast_routing attributes to mso_schema_template_bd +- Add requirements.txt for Ansible Environment support +- Add schema and template cloning modules mso_schema_clone and mso_schema_template_clone +- Add support cisco.nd.nd connection plugin +- Add support for multiple DCHP policies in a BD and new module mso_schema_template_bd_dhcp_policy +- Upgrade CI to latest Ansible version and Python 3.8 + +Bugfixes +-------- + +- Add test case and small fixes to mso_schema_site_bd_l3out module +- Fix documentation issues accross modules +- Fix fail_json usage accross module_utils/mso.py +- Fix mso_rest to support HTTPAPI plugin and tests to support ND platform +- Fix mso_user to due to error in v1 API in MSO 3.2 +- Fix path issue in mso_schema_template_migrate +- Fixes for site level external epgs and site level L3Outs +- Fixes to support MSO 3.3 +- Remove query of all schemas to get schema ID and only query schema ID indentity list API + +New Plugins +----------- + +Httpapi +~~~~~~~ + +- cisco.mso.mso - MSO Ansible HTTPAPI Plugin. + +v1.1.0 +====== + +Release Summary +--------------- + +Release v1.1.0 of the ``cisco.mso`` collection on 2021-01-20. +This changelog describes all changes made to the modules and plugins included in this collection since v1.0.1. + + +Minor Changes +------------- + +- Add DHCP Policy Operations +- Add SVI MAC Addreess option in mso_schema_site_bd +- Add additional test file to add tenant from templated payload file +- Add attribute virtual_ip to mso_schema_site_bd_subnet +- Add capability for restore and download backup +- Add capability to upload backup +- Add check for undeploy under MSO version +- Add error handeling test file +- Add error message to display when yaml has failed to load +- Add galaxy-importer check +- Add galaxy-importer config +- Add mso_dhcp_option_policy and mso_dhcp_option_policy_option and test files +- Add new module mso_rest and test case files to support GET api method +- Add new options to template bd and updated test file +- Add notes to use region_cidr module to create region +- Add task to undeploy the template from the site +- Add tasks in test file to remove templates for mso_schema_template_migrate +- Add test case for schema removing +- Add test cases to verify GET, PUT, POST and DELETE API methods for sites in mso_rest.py +- Add test file for mso_schema +- Add test file for mso_schema_template_anp +- Add test file for region module +- Add test files yaml_inline and yaml_string to support YAML +- Add userAssociations to tenants to resolve CI issues +- Addition of cloud setting for ext epg +- Changes made to payload of mso_schema_template_external_epg +- Changes to options in template bd +- Check warning +- Documentation Corrected +- Force arp flood to be true when l2unkwunicast is flood +- Make changes to display correct status code +- Modify mso library and updated test file +- Modify mso_rest test files to make PATCH available, and test other methods against schemas +- Move options for subnet from mso to the template_bd_subnet module +- Python lint corrected +- Redirect log to both stdout and log.txt file & Check warnings and errors +- Remove creation example in document of mso_schema_site_vrf_region +- Remove present state from mso_schema module +- Removed unused variable in mso_schema_site_vrf_region_hub_network +- Test DHCP Policy Provider added +- Test file for mso_dhcp_relay_policy added +- Test file for template_bd_subnet and new option foe module + +Bugfixes +-------- + +- Fix anp idempotency issue +- Fix crash issue when using irrelevant site-template +- Fix default value for mso_schema state parameter +- Fix examples for mso_schema +- Fix galaxy-importer check warnings +- Fix issue on mso_schema_site_vrf_region_cidr_subnet to allow an AWS subnet to be used for a TGW Attachment (Hub Network) +- Fix module name in example of mso_schema_site_vrf_region +- Fix mso_backup upload issue +- Fix sanity test error mso_schema_site_bd +- Fix some coding standard and improvements to contributed mso_dhcp_relay modules and test files +- Fix space in asssertion +- Fix space in site_anp_epg_domain +- Fix space in test file +- Remove space from template name in all modules +- Remove space in template name + +v1.0.1 +====== + +Release Summary +--------------- + +Release v1.0.1 of the ``cisco.mso`` collection on 2020-10-30. +This changelog describes all changes made to the modules and plugins included in this collection since v1.0.0. + + +Minor Changes +------------- + +- Add delete capability to mso_schema_site +- Add env_fallback for mso_argument_spec params +- Add non existing template deletion test +- Add test file for mso_schema_template +- Add test file for site_bd_subnet +- Bump module to v1.0.1 +- Extent mso_tenant test case coverage + +Bugfixes +-------- + +- Fix default value for l2Stretch in mso_schema_template_bd module +- Fix deletion of schema when wrong template is provided in single template schema +- Fix examples in documentation for mso_schema_template_l3out and mso_user +- Fix naming issue in deploy module +- Remove author emails due to length restriction +- Remove dead code branch in mso_schema_template + +v1.0.0 +====== + +Release Summary +--------------- + +This is the first official release of the ``cisco.mso`` collection on 2020-08-18. +This changelog describes all changes made to the modules and plugins included in this collection since Ansible 2.9.0. + + +Minor Changes +------------- + +- Add changelog +- Fix M() and module to use FQCN +- Update Ansible version in CI and add 2.10.0 to sanity in CI. +- Update Readme with supported versions + +Bugfixes +-------- + +- Fix sanity issues to support 2.10.0 + +v0.0.8 +====== + +Release Summary +--------------- + +New release v0.0.8 + +Minor Changes +------------- + +- Add Login Domain support to mso_site +- Add aliases file for contract_filter module +- Add contract information in current and previous part +- Add new module and test file to query MSO version +- New backup module and test file (https://github.com/CiscoDevNet/ansible-mso/pull/80) +- Renaming mso_schema_template_externalepg module to mso_schema_template_external_epg while keeping both working. +- Update cidr module, udpate attributes in hub network module and its test file +- Use a function to reuuse duplicate part + +Bugfixes +-------- + +- Add login_domain to existing test. +- Add missing tests for VRF settings and changing those settings. +- Add test for specifying read-only roles and increase overall test coverage of mso_user (https://github.com/CiscoDevNet/ansible-mso/pull/77) +- Add test to mso_schema_template_vrf, mso_schema_template_external_epg and mso_schema_template_anp_epg to check for API error when pushing changes to object with existing contract. +- Cleanup unused imports, unused variables and branches and change a variable from ambiguous name to reduce warnings at Ansible Galaxy import +- Fix API error when pushing EPG with existing contracts +- Fix role tests to work with pre/post 2.2.4 and re-enable them +- Fix site issue if no site present and fix test issues with MSO v3.0 +- Fixing External EPG renaming for 2.9 and later +- Fixing L3MCast test to pass on 2.2.4 +- Fixing wrong removal of schemas +- Test hub network module after creating region manually +- Updating Azure site IP in inventory and add second MSO version to inventory + +v0.0.7 +====== + +Release Summary +--------------- + +New release v0.0.7 + +Minor Changes +------------- + +- Add l3out, preferred_group and test file for mso_schema_template_externalepg +- Add mso_schema_template_vrf_contract module and test file +- Add new attribute choice "policy_compression" to mso_Schema_template_contract_filter +- Add new functionality - Direct Port Channel (dpc), micro-seg-vlan and default values +- Add new module for anp-epg-selector in site level +- Add new module mso_schema_template_anp_epg_selector and its test file +- Add new module mso_schema_vrf_contract +- Add new module mso_tenant_site to support cloud and non-cloud sites association with a tenant and test file (https://github.com/CiscoDevNet/ansible-mso/pull/62) +- Add new mso_site_external_epg_selector module and test file +- Add site external epg and contract filter test +- Add support for VGW attribute in mso_schema_site_vrf_region_cidr_subnet +- Add support to set account as inactive using account_status attribute in mso_user +- Add test for mso_schema_site_vrf_region_cidr module +- Add test for mso_schema_site_vrf_region_cidr_subnet module +- Add vzAny attribute in mso_schema_template_vrf +- Automatically add ANP and EPG at site level and new test file for mso_schema_site_anp_epg_staticport (https://github.com/CiscoDevNet/ansible-mso/pull/55) +- Modified External EPG module and addition of new Selector module + +Bugfixes +-------- + +- Fix mso_schema_site_vrf_region_cidr to automatically create VRF and Region if not present at site level +- Fix query condition when VRF or Region do not exist at site level +- Remove unused regions attribute from mso_schema_template_vrf + +v0.0.6 +====== + +Release Summary +--------------- + +New release v0.0.6 + +Minor Changes +------------- + +- ACI/MSO - Use get() dict lookups (https://github.com/ansible/ansible/pull/63074) +- Add EPG and ANP at site level when needed +- Add github action CI pipeline with test coverage +- Add login domain support for authentication in all modules +- Add support for DHCP querier to all subnet objects. Add partial test in mso_schema_template_bd integration test. +- Add support for clean output if needed for debuging +- Add test file for mso_schema_template_anp_epg +- Added DHCP relay options and scope options to MSO schema template bd +- Added ability to bind epg to static fex port +- Added module to manage contracts for external EPG in Cisco MSO (https://github.com/ansible/ansible/pull/63550) +- Added module to manage template external epg subnet for Cisco MSO (https://github.com/ansible/ansible/pull/63542) +- Disabling tests for the role modules as API is not supported after 2.2.3i until further notice +- Increased test coverage for existing module integration tests. +- Modified fail messages for site and updated documentation +- Moving test to Ansible v2.9.9 and increasing timelimit for mutex to 30+ min +- Update authors. +- Update mso_schema_site_anp.py (https://github.com/ansible/ansible/pull/67099) +- Updated Test File Covering all conditions +- mso_schema_site_anp_epg_staticport - Add VPC support (https://github.com/ansible/ansible/pull/62803) + +Bugfixes +-------- + +- Add aliases for backward support of permissions in role module. +- Add integration test for mso_schema_template_db and fix un-needed push to API found by integration test. +- Consistent object output on domain_associations +- Fix EPG / External EPG Contract issue and create test for mso_schema_template_anp_epg_contract and mso_schema_template_external_epg_contract +- Fix contract filter issue and add contract-filter test file +- Fix duplicate user, add admin user to associated user list and update tenant test file +- Fix intersite_multicast_source attribute issue in mso_schema_template_anp_epg and add the proxy_arp argument. +- Fix mso_schema_template_anp_epg idempotancy for both EPG and EPG with contracts +- Remove label with test domain before create it +- Send context instead of vrf when vrf parameter is used +- Update mso_schema_template_bd.py example for BD in another schema + +v0.0.5 +====== + +Release Summary +--------------- + +New release v0.0.5 diff --git a/ansible_collections/cisco/mso/FILES.json b/ansible_collections/cisco/mso/FILES.json new file mode 100644 index 000000000..1d891e57b --- /dev/null +++ b/ansible_collections/cisco/mso/FILES.json @@ -0,0 +1,2637 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "codecov.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e55a41874408d15b899f7df85fa9f4c6c3c476cf4a9e919b9a396c3f1aa1a1f0", + "format": 1 + }, + { + "name": ".DS_Store", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f675a3307852ecd3abb2cce8e8f2f39c607937538cc9a55749ddaac6154a4d9a", + "format": 1 + }, + { + "name": "LICENSE", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c38eb11b6b8cd591aed90793ca80bd3c499393f207b96640ddfa571f61f7b4f", + "format": 1 + }, + { + "name": "requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f78777e211f01bcaa37835d89fd0a8de3b9c1bf37d0a43be57991643a0ab7bcc", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/.DS_Store", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cac03b3d027d06cc80e205126f9229303c5947e78f05fe29be390740df07f88", + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/modules.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "05e4cbb55e517d30ceb130dcc7c920b3ead10d31b3ae993d018c1364b7f17dfc", + "format": 1 + }, + { + "name": "plugins/httpapi", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/httpapi/mso.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e31f980274cce2fd620e627313d88c0b848e616956c747772f768c99a002e9b2", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/mso.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c6ee1a1c7fe26b4ebbf7d11442a429eee577cc59e6340b96e119dba10961bfdf", + "format": 1 + }, + { + "name": "plugins/module_utils/constants.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9521675e783e3ec3ae79f42c20225a79fdfd295840ef38aca2f7579f401dec12", + "format": 1 + }, + { + "name": "plugins/module_utils/schema.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "faf68907d23381a03c29a50a12dd24243cfec96e3830b2bd477d445356b3d3dc", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_contract_filter.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f1cd0ad89ea16105dedec1ee73865c3039c0d440f51835c01bea909088200e1", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg_staticport.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24c0bbb68eddf4b2dbd1920ad0dbb8f487318ed2897467c4b357878ba4a27313", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "53b683bfe3797188a7114b2f7fd32c80f20c82ab75af2cbb52c65ef86ab34b6c", + "format": 1 + }, + { + "name": "plugins/modules/mso_dhcp_option_policy_option.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d2486a946afc26f3bb7b7a8797670747848c844b9109f1782564a2ded687a1a", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_vrf_region_hub_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7a43523bd0dc4cfffe22440d971b3f89e85d2d9e0a50aeb39389c3c2830c4bd9", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_clone.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "467bbd942d0e26497d51bb70c33338eb07079e4743700c1c256e7e2e4c5aa4ca", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_external_epg_selector.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4363f0f76203e827914ee92e71976ddd77281b00a132e0ac3ead2b92afd00674", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b0ee7e4bcb554da6cd227ebb70187b63d1bcccd68b1dcd7bc7e18c785b942b5", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_bd_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "548b14f399f547ccfefc18346676a0e24cce3a7826557e8988d6a392813b138f", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_anp.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "563af9420bd43908af689c4f8931a4ea5ce461008e08d45ac0ba1578d398388a", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_bd_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a57db7b1e169ac4cd985f8c754ccf15df5654b7527cc5234b3980fcd83ab3ec9", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_service_graph.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f61b1039f2822f8e5bc0d520e429821f67b220847a66553d7a2424680ff89c7e", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_deploy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4675f4f2242f139b0198e240e39bc0033d7807dcde46b4e5fe5cce4ab00f7a43", + "format": 1 + }, + { + "name": "plugins/modules/mso_tenant.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f6cfabf979e75755aba9b5de7485a55512bb02d1083d7c0afa97aadf6bfde44b", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bb9d0be9896f46fa894f4ee2242683cba38141c1ad826aa7fc9720b4a752143b", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_vrf.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cfc09a18b5f1ddb2598e9bb69241f35b4c3a967f84f529266ce8b610400f3939", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg_staticleaf.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "517fe0774f3853668ba3fa20a27db581974292e6a068eeaeba266fd13e7a2130", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_contract_service_graph.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d5f117924e42571f83fb0fd7e3006938d6c7a03535c2b9b1d0e8fef1187e9d43", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_external_epg_contract.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1f977a4e86c0133551d4885a8f2e738be26ff169f84bf6e75eda627e2237e8ef", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_external_epg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cca13445dac9275a27359ba8f7904208056934c22019553342a5bd81a5927541", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_bd_dhcp_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ccc7584c47937ca7a2759f4bd83766d0589d5ecbf83fa2beb75683719e739458", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_vrf_region_cidr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "537ea2679378ebab08599813a7e89de4866baed013c8816499f081b440b80c8b", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_anp_epg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbd12de54e5336dc265f43bf76c59215d9583c8e09d590ccfd63acd5e346f543", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_anp_epg_contract.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dac857729a99d0a1d6d778c94565ac54def3c1a548d949643288f3ae0d7ff047", + "format": 1 + }, + { + "name": "plugins/modules/mso_dhcp_relay_policy_provider.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "39748852f207b8d1a2f0572c8204326bc70c16799e6d414c44d70562781d76f1", + "format": 1 + }, + { + "name": "plugins/modules/mso_tenant_site.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ab681658fcdc71ce3e0dee372959d3e3c816833cbc64f78e2ca64e406495876", + "format": 1 + }, + { + "name": "plugins/modules/mso_backup.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "023cdd8db111487656d214b5664691b2524b93b8792f6833cd6e44ff24d33775", + "format": 1 + }, + { + "name": "plugins/modules/mso_site.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2dc848085c68033f3b8ef93d4c82e7a4791074736507c69b5b7e630108e1494e", + "format": 1 + }, + { + "name": "plugins/modules/mso_remote_location.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbc21ccb26b9b6a4a206b218c24d05996917ad200160195ac5a3bd23ce2b10fa", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg_selector.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f759e142e699d3013a1a625563a0bf561ac7b1a78e6a0fb511ac952a68ef7ce0", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_vrf.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92bf0092c6f6e72ef1d5748b2316c368b99839ab172caea71668fa44ea2a08c8", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_clone.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d72660f2a0bb86fd807259b2f4046ea75bc7775abd2659a1ad4301bbce3b5de4", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_external_epg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c42b50d3217281fab3bdbdb63831973ffa0abb7b427059338b24ec9ead91111", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7fa1e1ba14119d99731281f5edb1c29e9a00ce8e9e68dc885c14f057e8611a5a", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb277796d3882cd5af04d61252d31d73bd6bcbbe37bd378169fb7e9b9ca17a1d", + "format": 1 + }, + { + "name": "plugins/modules/mso_service_node_type.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f8b2c340e2a19be2b2a3993555500a8a07254a4fd6bbd424f787c59e50ddadd3", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_bd_l3out.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a673bd1f3b2be1b8e290b0aca6b71b784ee6dcbc3b7c83e6688a69b374ac3859", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eea4baa850dee443f4763f8821171b7a9018ad32863fcc9419c711b794cbaeaf", + "format": 1 + }, + { + "name": "plugins/modules/mso_role.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "676bb9102ab59c395bc74b1e7ae88188ec2d759d243b563dc6f7c40e7954fcc5", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_externalepg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cca13445dac9275a27359ba8f7904208056934c22019553342a5bd81a5927541", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_external_epg_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ce326dbc4882ff9fb12721721c84184dfee85c35a31f88eed7ea4f1ee15ce95f", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_filter_entry.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0bcd5ec3532d03574c2116dd8235fb60fa23aa60212a8f2d1d8fb8cf54ae66f1", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_service_graph.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1758c7b2153df304380c61c29c5861e83e6dbabc447bb738c474755128bc4c78", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_bd.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "efa09b8140a5e1deb8ad7ee81e32d610b75475bdb510d3118cac7928443b7700", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_anp_epg_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a255127f9740b65860e5106edb61c5265c31e841d104328324bffb109116450", + "format": 1 + }, + { + "name": "plugins/modules/mso_rest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "716c55c4720e7717076cd4c94d6fd14696ff7bbc95f8e4638be9b6cd43eacf94", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_vrf_contract.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c4c30b7dbc29b7b17217bab52b48d9f2763266ee64f059dc91a193b7f713fb24", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_validate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b09b020a522340cefe979ae6d7dde26c16d57723f9c4d98568791d1bbd1009e1", + "format": 1 + }, + { + "name": "plugins/modules/mso_label.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "91f3e18abb64d487e24698602aa2c8122275077b76f7ce3e44c9c72615769a6b", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_vrf_region.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9d1d6ac938b724f90bee0389ba5449bd39009470c2470b491cbb9c6fa9476f7e", + "format": 1 + }, + { + "name": "plugins/modules/ndo_schema_template_deploy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "245a3f6f5127f6bce09cf8be06aa8705c70f03b9cc09d9a068053a34a9ffae36", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_external_epg_selector.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c3c4b25e653b0ffe17b3855a110ff5183b45a7c131deeb720e37bf0e3999512d", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_migrate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e02912efe8eb9f53827a2843d0ab91aecfc57d5d11fb2b2b535d593fc0b1874", + "format": 1 + }, + { + "name": "plugins/modules/mso_version.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a91a748dc001172e3a0c485848fa0fdd41831b79a0f0862a55625c9c6d78133", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_l3out.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e0bb66ebb85fd043104a8cbd14338e3014d16ba49a349eb39a4eacf6319ea21", + "format": 1 + }, + { + "name": "plugins/modules/mso_dhcp_relay_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3eb53debf9a3c8ee3bfed778e538a73e03dac02371f77ad8046e9be200f04806", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_anp_epg_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d3eb9cfffe291d742bd95c98e2222696b47b9532de5960d405390823f4195c6", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_deploy_status.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c9017bda8f5590b60047f9654db54e4608064f97abf2cbae45ce455e2cde815f", + "format": 1 + }, + { + "name": "plugins/modules/mso_dhcp_option_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "80d5de0dec0b967c33be6f0a882ed477d774e38823f082ed928bd99acbda77e0", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_anp_epg_selector.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3fd399a8567a0f217d09cd52bc98233bc66d8f95198cce9d3dca1d3c9605e2d3", + "format": 1 + }, + { + "name": "plugins/modules/mso_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5a1253f212aaef3c7f1f0a6f0d782db05d56b5943a71a3ef22f170d2b180436a", + "format": 1 + }, + { + "name": "plugins/modules/mso_backup_schedule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ffcb686b57665f77ba98e5c9da90371bd375651ee14a872946a6ef410795d028", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template_bd.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bea197a237bc35514301873858b1b089474b68859e73755c97fbeef8bd9cf022", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site_l3out.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "640901827b0efe076fc738afbb8909d60bfbbfd23ee11116f397a80ef2567e1a", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_site.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a06f86812347eaddbc5b39836e8cc96703e4207a327673876883550b8109277a", + "format": 1 + }, + { + "name": "plugins/modules/mso_schema_template.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fd7d0b52e775c6607e2c2da18e655f9ae5f760b616f51629f3dda9dcc95bebb5", + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/.DS_Store", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c43950b0d43cfc92663c93c4106c8265af0878a180281af56a27b402dbe04258", + "format": 1 + }, + { + "name": "tests/integration", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9ef7c98101c518cc0689cd0c3052c7a965924473d468d9f28cf94616035f9f3", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_service_graph", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_service_graph/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "827c0652a8363fa1585bc3087517cdcc9622f62d6937cc8e4325162c8d1f977b", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_service_graph/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0c70364503504b44437583c853a4dd70dfb8ed09c3dcb60d254790cefbb0a6c5", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_l3out", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_l3out/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_l3out/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "329a355a98ac22dd8e1e8b7548d054b3610dc834d8485cdd8830f415f5214106", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_l3out/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "95b8ad80d173f5a830b41a0b4c6cdb523a89dadc12413e4d4f4f956a82244c04", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69e1eb1e15a1af3fe9e08e301c4173e32f26592337ec169896a202e914747440", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/json_template.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8cc983a7d84493d9f41cbe38b197727174afec4426f78c376bce540a4b4a4fec", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/tenant.json.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6636b61fb38e341e4520e1f158bbce619e6b70e6e6d4bd243177ec86069f50dd", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/json_inline.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "79a502e927c89c5968a435bf2c2418ab416cfa47a6b41d48079eef0c36149f29", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/json_string.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "db213807aee3f02d301fea223f60d9e54dff6ad8ea8bdf787d20c03bf7ca98c9", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/yaml_inline.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d3b9e0b8414481027b4b3fc2ac7b06002e835bd1af89a05a2d7800007b12af87", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/yaml_string.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de348a80485987b8ccd22ced6deb28150d79e81947929908a395fd8019fab605", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "52f1fb7f60fafa3735b17352ca4e1da195810b0edf3f2680738092e4d52fb1b1", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/tasks/error_handling.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "833535c5837bd384a895d6741629bae58d64af7b2b8f5af78d288ca9ad601947", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_rest/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "38221f3e70c4a94b31aa03a33e8da81ad05164ba82afbf2ecdb6c3e77fd3977f", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/pki", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "627a0a5fd0d48dd796488fc3c6581beb656d0781730d759205158d7cfc256361", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/pki/rsa", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ea356fc3f12a611c5508051db0d3ec4e8f0945f806ac9b7645cc72f8342639a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/pki/rsa-passphrase", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a66131fd3b769bd4dd960daa74a619b7786e546841f3b113ec012c2e9a0498ee", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_remote_location/pki/rsa.pub", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f33804de57beeb48f784be435cdf7484886f1d227ab6bd016ab2e03f1150e78", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_subnet", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_subnet/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5836b610052cfbdc33f4603cac14419dd92c3c64f3c56f17d557f89198055ccb", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_subnet/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe21b23b905888b210fb78e8102768cdb990ab083ff7de03e6c6ef519e947c59", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c1312bf7a3945d1864d7a94e5a5224ebdfcad2076570a76f32a211f351aae11f", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_migrate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_migrate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_migrate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "071adadb5df6a35002c55292ca8c7435e5893be90318e66f3defe6b124345cde", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_migrate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_l3out", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_l3out/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_l3out/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c254fa26dea680790cff05b10a996729ab1d4ae6d58a22f9e0870ded774c0ad1", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_l3out/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "39d7a4e61d89fcf18b1dc760e208d6f2e7293987033a87432f380c7497dcc92a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/ndo_schema_template_deploy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ndo_schema_template_deploy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9dc44d7e2603c394f4d0d28305b233bc2b00b0f3bc6d21f79d8f59914acc053", + "format": 1 + }, + { + "name": "tests/integration/targets/ndo_schema_template_deploy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_selector", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_selector/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8eaa3bd0ba02460cf49baad9c987294915aeb9dd70086caf5a5d742ef2e450f0", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_selector/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_selector", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_selector/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2270c9de7e7ccae07b323d7f4fa7779faa468a8bdabcf7318746dd8966a67e0e", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_selector/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_clone", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_clone/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_clone/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "514d4e40cfb3e4dcff5bbfb105cefd20fbaeb0ea0620450eff89ac48311486d7", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_clone/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_service_graph", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_service_graph/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "578c3318a14ef60ec71adaadcb725f767b60ab223d177aa19965ed1421b08799", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_service_graph/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_contract", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_contract/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "82a0285141349272dba64176a122887414245415ba23ce9e7edb68f2f24a5768", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg_contract/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "77efce495c27cf34aee29ad0d23857ee75862a8285cf28c7d2e36b1f7d5a21e5", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_filter", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_filter/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3f2ee3ce9770c0a1e82f0359641f889c34e21f1a1a357256818762ff7c34baf4", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_contract_filter/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy_option", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy_option/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "51f135acdfc12bd620051460c18d84c139adeca91bcfbf53e44ed5c1a3879213", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy_option/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg_selector", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg_selector/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e493d7a02b30e3933cdde6112452b3d8848718fd7cdbad91c02cf9ead974824c", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg_selector/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup_schedule", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup_schedule/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup_schedule/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07a0e9ba07f2daa25c9fba4c9ea0ddf30f8a7c8742d23db43c606d418502fcdd", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup_schedule/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9d33506200b0c453f2d6d810772e4d4df6f4bf2cafa60728196bf2e0f69e6229", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "333679f0f0e047c4923b582594ef248ade0ea86781386548e12fecbb3abe071c", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6901d10171226d330516388d635afe119a679289eef9ef4035ac4de6353c3b94", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ff9797ef3ae1245f5f8c22f8b690f2da40ee624029286bec15391a83ca808574", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_external_epg/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_site", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_site/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_site/tasks/connectivity.j2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5ad7198616f8396d761ddfff46c03886cf49c8af64decc1439cd0b893e2e8645", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_site/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b1069b4a82af6ab3a3f5c48a3e1ccd7934914ce4a01986527c8abd1b9644f60f", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_site/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "18d20201dae1300b207fb2a4aca24df285f859edb393c5f86f773bb8df08e7eb", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_backup/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_validate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_validate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_validate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1af77eaab77c5a6b06eda3a60b2bd918b84dfa5750f6288aaad1d002d069bc6b", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_validate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6bdfe858336ec0d8a3fb0d5d09142ef1c2a4a0cbd99fcc389e6784ac8ddd2b41", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_external_epg/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_l3out", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_l3out/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7b158c1ec848377082954461ef32fb48016b0986ac9d9a7e8b5be178410df282", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_l3out/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c7674afec5735c25d6728fe1ad92d3c18fbeb86285c8ab671d0feca2f2f1c16", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy_provider", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy_provider/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5d51e2b02f8d1e76e11de62f429d8ef56d434d5b2cdb69ad0b919889ff2f5936", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_relay_policy_provider/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_version", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_version/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_version/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "82ab83bd4bea802e0816502ad436051701ebf7ad413979fafaf48a89a9f373a6", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_version/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c8a3d5702a82e14a426180a493c3479435a0e19d46bd02c8493b035296d958c0", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_dhcp_option_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "418ff7da34a7ad01685d47cc802cedff5ba29cbe769c954ee9083f682dad7ae1", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e1a5fd0bbf1f1adf0d4da159ccfe96f57b5c10f4e8fa48c6c76da8339b10c530", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ffa9f5e1ba49276fbf6d6cb24151327d6901a5a778d57e4ddb1c8ac0c722487e", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f4cd4ae7386ed895636f750b632a5001c004d8a8805c21de5059cfbbad3c04c4", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b28d23e8386bd853d29a9544995258c438bec534f7abd563642f0c08de3d9784", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_clone", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_clone/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_clone/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d844a496be851967b5ad5b12b7e87c97cfeb43fb9aea9a017fe1ff5eef8872c4", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_clone/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "78b2e112fa8a4f86ae598358e9230f87e97032cac5c42cce6e2225b2d214684e", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_subnet", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_subnet/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "13b1b2addac18340cc8e491f123f1bfd6eb036b1bc9436ab49e95fb723bb652c", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_bd_subnet/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_service_graph", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_service_graph/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "484c7ee52533c8a3945e0895aea08fc8ce43f4cbeb214f09382da2b94f8be366", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_service_graph/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant_site", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant_site/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant_site/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "62050f4750b7759d9ffe9fb05eb907652902632e60bab409dd5046ed295aa32e", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant_site/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_subnet", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_subnet/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ff362dc5f1bc93ef2020db1ec557092607256e917d8ddd430c323d6f5d8ec5f", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_bd_subnet/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_filter_entry", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_filter_entry/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fa3311bf4f0a496d1f4fb2ced7e15eba2fc91f94dc9d3ff03fcc90d914edfef6", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_filter_entry/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "54fb49cc6419f67dbbad1d40262ad3b5d51c4397a4051dc1bf1517b9a0cc2649", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "947a71a53705d001c273db32700e5068456b9c4485e965ce74f6f7a1b9deac21", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_tenant/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_user", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_user/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_user/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4da63f32ce74b3890e7ee8d4c3c075036bbf41c12a05993ae0149e200802a69f", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_user/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_label", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_label/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_label/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "480fe2aaf7ae221186ad81b486471d176476c07347e005255d0a399ac14c2086", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_label/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_contract", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_contract/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1c4ffb17ea697e5f4084e51af650ea8926d15db223e0e0ae010a7954def3c6ff", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_contract/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_selector", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_selector/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b076386dec1bbce7c25deeb36f6c674d6ba271e2d22d87241db43dd8fbf60913", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg_selector/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4745d027cba442156d8f6c5c441695cf796bd7a3dc2f3db5addffcff340c1268", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_anp_epg/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_domain", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_domain/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ee1fb2b9a12e1cf8279a729d0e1931f8428bd9ebba10437d919b33e5c68bc7ea", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_site_anp_epg_domain/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf_contract", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf_contract/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c1639c0d1bda672778a81703b34dfde366b560c7e075fc78abfd42b0aadae844", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_vrf_contract/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_role", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_role/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_role/tasks/role-ro.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e6bd02045da50a15d5637fa33f5fd81cf2be74a7169ad5f3d3bb3f8bb5ddbf04", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_role/tasks/role-rw.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "71801c0349fceb078b940dd5fcec5cdf2009698f1217dffd56742ac102d21f8f", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_role/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "65a2f97fa530d35fa0f1c647b8b3052d42d2dee2d6a9e6ea766e9eae531ff177", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_role/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_service_node_type", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_service_node_type/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_service_node_type/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f1f0fc9edff212a859e9a1a92cabdc157a25b5458d9dd9f429cc3e867ebfaa3", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_service_node_type/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy_status", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy_status/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb8bc653c1850980ecdef451053e466effa70eec2482c9b62d997ed7a131a036", + "format": 1 + }, + { + "name": "tests/integration/targets/mso_schema_template_deploy_status/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a", + "format": 1 + }, + { + "name": "tests/integration/network-integration.requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "26d3ea388bb679036608d912734c5369bb63f362795a62fd5c9e0469c9abaeb8", + "format": 1 + }, + { + "name": "tests/integration/target-prefixes.network", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a751644aed44198c1a29a15bccfe9d4db59a5dabaf118620623579ce0caf0f89", + "format": 1 + }, + { + "name": "tests/integration/inventory.networking", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8279ccd9d9f069814c4799fe859517982de7fdef62f6844c86ce3d43f1eb75b1", + "format": 1 + }, + { + "name": "tests/sanity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "99b118eeccecd08f68761335ef6a9ea2ace42095d52512c6e575a1d017362a2d", + "format": 1 + }, + { + "name": "tests/.gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b5726d3ec9335a09c124469eca039523847a6b0f08a083efaefd002b83326600", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d6216481ba4478b00a1c7dd50630465ebdfeb69257a3203cb1bd78243b57e5e2", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "43206e71b00b28f61464ff729ee7c096a6babe481924585846dc0ad75f4e5ae0", + "format": 1 + }, + { + "name": "changelogs/.gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "919ef00776e7d2ff349950ac4b806132aa9faf006e214d5285de54533e443b33", + "format": 1 + }, + { + "name": "changelogs/.plugin-cache.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63e4189122f605bc252cee64b0e2a606ba71624e4efeef2f0b345aba2b137d52", + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "add2fb84a8fccaa2a670024fd29718afab7e6001ffebbbbf5e8a5fd48b50c3e7", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "613db67fc4c860791ee0bc02b1e0ace99d9a0daae54db9cc8309fdfbf061d239", + "format": 1 + }, + { + "name": ".gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "611ba6325f4ab58aa157eb91fbe3edf77f2285f854e612f6ec433d73ae4291cc", + "format": 1 + }, + { + "name": ".github", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/.DS_Store", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c61e10aac987566d2e1f152dcd3c4b4b410d6aa298fc8a35fde20ebf91925f82", + "format": 1 + }, + { + "name": ".github/workflows", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/workflows/galaxy-importer.cfg", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "edc6f2746e7b8c7b94dea479036ba246953cf69234974b89c069c10d614dcaad", + "format": 1 + }, + { + "name": ".github/workflows/codeql-analysis.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dcb0879cb0566d1f60a7e468d2d00c174dbb17de01afde105e47fb9ee834463d", + "format": 1 + }, + { + "name": ".github/workflows/ansible-test.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55a01eb1c79e977744677bca59563ff6dfbb3f12b0932130d4cc7a46d49deb04", + "format": 1 + }, + { + "name": ".github/workflows/changelog-generation.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "52da33c388cefb32ae9d7c147111ea30534fd32f8b8b546e5b0688cb49a50d33", + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE/Feature_Request.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "734f00beb33216222396e76ae426a8d5b85605b37f7c4ea5fb5899f6a81f0899", + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE/Bug_Report.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ab91fb0030575562037e71b97d5c7a011ef0068812195a1ce78bc7233cbbbc79", + "format": 1 + }, + { + "name": ".github/ISSUE_TEMPLATE/config.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "05905c8f244d51298bbd1778c286c8a6c9f7adf0d0e5a5f72f764d71ec82cc64", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e6387ce16814cecfb727532d8d1f0231849b4b1ccc11875af32502499af84ef8", + "format": 1 + }, + { + "name": ".vscode", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".vscode/settings.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d0b33897350295766ce49d5ddf87964e33e1ad285bf7713bc668c518fcd14814", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/LICENSE b/ansible_collections/cisco/mso/LICENSE new file mode 100644 index 000000000..e09a4143a --- /dev/null +++ b/ansible_collections/cisco/mso/LICENSE @@ -0,0 +1,678 @@ +Copyright (c) 2019, Cisco Systems +All rights reserved. + + 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/ansible_collections/cisco/mso/MANIFEST.json b/ansible_collections/cisco/mso/MANIFEST.json new file mode 100644 index 000000000..c795dd895 --- /dev/null +++ b/ansible_collections/cisco/mso/MANIFEST.json @@ -0,0 +1,43 @@ +{ + "collection_info": { + "namespace": "cisco", + "name": "mso", + "version": "2.4.0", + "authors": [ + "Dag Wieers (@dagwieers)", + "Nirav Katarmal (@nkatarmal-crest)", + "Lionel Hercot (@lhercot)", + "Cindy Zhao (@cizhao)", + "Shreyas Srish (@shrsr)" + ], + "readme": "README.md", + "tags": [ + "cisco", + "aci", + "cloud", + "collection", + "networking", + "sdn", + "mso", + "multisite" + ], + "description": "An Ansible collection for managing Cisco ACI Multi-Site", + "license": [], + "license_file": "LICENSE", + "dependencies": { + "ansible.netcommon": "*" + }, + "repository": "https://github.com/CiscoDevNet/ansible-mso", + "documentation": "https://docs.ansible.com/ansible/latest/scenario_guides/guide_aci.html", + "homepage": "https://cisco.com/go/aci", + "issues": "https://github.com/CiscoDevNet/ansible-mso/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "876c6bead4e943a7f2ed1b6bcc6db722a3024362587618ffccf5e7c7ec54419b", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/README.md b/ansible_collections/cisco/mso/README.md new file mode 100644 index 000000000..d83eb5297 --- /dev/null +++ b/ansible_collections/cisco/mso/README.md @@ -0,0 +1,122 @@ +# ansible-mso + +The `ansible-mso` project provides an Ansible collection for managing and automating your Cisco ACI Multi-Site environment. +It consists of a set of modules and roles for performing tasks related to ACI Multi-Site. + +This collection has been tested and supports MSO 2.1+. +Modules supporting new features introduced in MSO API in specific MSO versions might not be supported in earlier MSO releases. + +*Note: This collection is not compatible with versions of Ansible before v2.8.* +*Note: The Nexus Dashboard (ND) HTTPAPI connection plugin should be used when Cisco ACI Multi-Site is installed on Nexus Dashboard (v3.2+) or when using this collection with Nexus Dashboard Orchestrator (v3.6+).* + +## Requirements +- Ansible v2.9 or newer + +## Install +Ansible must be installed +``` +sudo pip install ansible +``` + +Install the collection +``` +ansible-galaxy collection install cisco.mso +``` + +Install the Nexus Dashboard (ND) collection when Cisco ACI Multi-Site is installed on Nexus Dashboard (v3.2+) or when using this collection with Nexus Dashboard Orchestrator (v3.6+) +``` +ansible-galaxy collection install cisco.nd +``` + +## Usage +Once the collection is installed, you can use it in a playbook by specifying the full namespace path to the module, plugin and/or role. +```yaml +- hosts: mso + gather_facts: no + + tasks: + - name: Add a new site EPG + cisco.mso.mso_schema_site_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + state: present + delegate_to: localhost +``` + +You can also use the MSO HTTPAPI connection plugin by setting the following variables in your inventory file (cisco.mso collection v1.2+). +```yaml +ansible_connection=ansible.netcommon.httpapi +ansible_network_os=cisco.mso.mso +``` + +The HTTPAPI connection plugin will also allow you to specify additional parameters as variable and omit them from the task itself. Module parameters will override global variables. +```yaml +ansible_host=10.0.0.1 +ansible_user=admin +ansible_ssh_pass="MySuperPassword" +ansible_httpapi_validate_certs=False +ansible_httpapi_use_ssl=True +ansible_httpapi_use_proxy=True +``` + +You should use the Nexus Dashboard (ND) collection plugin, which is available in the [cisco.nd](https://galaxy.ansible.com/cisco/nd) collection, when Cisco ACI Multi-Site is installed on Nexus Dashboard (v3.2+) or when using this collection with Nexus Dashboard Orchestrator (v3.6+) by changing the following variables. +```yaml +ansible_connection=ansible.netcommon.httpapi +ansible_network_os=cisco.nd.nd +ansible_httpapi_use_ssl=True +``` + +## Update +Getting the latest/nightly collection build + +### First Approach +Clone the ansible-mso repository. +``` +git clone https://github.com/CiscoDevNet/ansible-mso.git +``` + +Go to the ansible-mso directory +``` +cd ansible-mso +``` + +Pull the latest master on your mso +``` +git pull origin master +``` + +Build and Install a collection from source +``` +ansible-galaxy collection build --force +ansible-galaxy collection install cisco-mso-* --force +``` + +### Second Approach +Go to: https://github.com/CiscoDevNet/ansible-mso/actions + +Select the latest CI build + +Under Artifacts download collection and unzip it using Terminal or Console. + +*Note: The collection file is a zip file containing a tar.gz file. We recommend using CLI because some GUI-based unarchiver might unarchive both nested archives in one go.* + +Install the unarchived tar.gz file +``` +ansible-galaxy collection install cisco-mso-1.0.0.tar.gz —-force +``` + +### See Also: + +* [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. + +## Contributing to this collection + +Ongoing development efforts and contributions to this collection are tracked as issues in this repository. + +We welcome community contributions to this collection. If you find problems, need an enhancement or need a new module, please open an issue or create a PR against the [Cisco MSO collection repository](https://github.com/CiscoDevNet/ansible-mso/issues). diff --git a/ansible_collections/cisco/mso/changelogs/.gitignore b/ansible_collections/cisco/mso/changelogs/.gitignore new file mode 100644 index 000000000..6be6b5331 --- /dev/null +++ b/ansible_collections/cisco/mso/changelogs/.gitignore @@ -0,0 +1 @@ +/.plugin-cache.yaml diff --git a/ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml b/ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml new file mode 100644 index 000000000..c1fce4465 --- /dev/null +++ b/ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml @@ -0,0 +1,343 @@ +objects: + role: {} +plugins: + become: {} + cache: {} + callback: {} + cliconf: {} + connection: {} + httpapi: + mso: + description: MSO Ansible HTTPAPI Plugin. + name: mso + version_added: 1.2.0 + inventory: {} + lookup: {} + module: + mso_backup: + description: Manages backups + name: mso_backup + namespace: '' + version_added: null + mso_backup_schedule: + description: Manages backup schedules + name: mso_backup_schedule + namespace: '' + version_added: null + mso_dhcp_option_policy: + description: Manage DHCP Option policies. + name: mso_dhcp_option_policy + namespace: '' + version_added: null + mso_dhcp_option_policy_option: + description: Manage DHCP options in a DHCP Option policy. + name: mso_dhcp_option_policy_option + namespace: '' + version_added: null + mso_dhcp_relay_policy: + description: Manage DHCP Relay policies. + name: mso_dhcp_relay_policy + namespace: '' + version_added: null + mso_dhcp_relay_policy_provider: + description: Manage DHCP providers in a DHCP Relay policy. + name: mso_dhcp_relay_policy_provider + namespace: '' + version_added: null + mso_label: + description: Manage labels + name: mso_label + namespace: '' + version_added: null + mso_remote_location: + description: Manages remote locations + name: mso_remote_location + namespace: '' + version_added: null + mso_rest: + description: Direct access to the Cisco MSO REST API + name: mso_rest + namespace: '' + version_added: null + mso_role: + description: Manage roles + name: mso_role + namespace: '' + version_added: null + mso_schema: + description: Manage schemas + name: mso_schema + namespace: '' + version_added: null + mso_schema_clone: + description: Clone schemas + name: mso_schema_clone + namespace: '' + version_added: null + mso_schema_site: + description: Manage sites in schemas + name: mso_schema_site + namespace: '' + version_added: null + mso_schema_site_anp: + description: Manage site-local Application Network Profiles (ANPs) in schema + template + name: mso_schema_site_anp + namespace: '' + version_added: null + mso_schema_site_anp_epg: + description: Manage site-local Endpoint Groups (EPGs) in schema template + name: mso_schema_site_anp_epg + namespace: '' + version_added: null + mso_schema_site_anp_epg_domain: + description: Manage site-local EPG domains in schema template + name: mso_schema_site_anp_epg_domain + namespace: '' + version_added: null + mso_schema_site_anp_epg_selector: + description: Manage site-local EPG selector in schema templates + name: mso_schema_site_anp_epg_selector + namespace: '' + version_added: null + mso_schema_site_anp_epg_staticleaf: + description: Manage site-local EPG static leafs in schema template + name: mso_schema_site_anp_epg_staticleaf + namespace: '' + version_added: null + mso_schema_site_anp_epg_staticport: + description: Manage site-local EPG static ports in schema template + name: mso_schema_site_anp_epg_staticport + namespace: '' + version_added: null + mso_schema_site_anp_epg_subnet: + description: Manage site-local EPG subnets in schema template + name: mso_schema_site_anp_epg_subnet + namespace: '' + version_added: null + mso_schema_site_bd: + description: Manage site-local Bridge Domains (BDs) in schema template + name: mso_schema_site_bd + namespace: '' + version_added: null + mso_schema_site_bd_l3out: + description: Manage site-local BD l3out's in schema template + name: mso_schema_site_bd_l3out + namespace: '' + version_added: null + mso_schema_site_bd_subnet: + description: Manage site-local BD subnets in schema template + name: mso_schema_site_bd_subnet + namespace: '' + version_added: null + mso_schema_site_external_epg: + description: Manage External EPG in schema of sites + name: mso_schema_site_external_epg + namespace: '' + version_added: null + mso_schema_site_external_epg_selector: + description: Manage External EPG selector in schema of cloud sites + name: mso_schema_site_external_epg_selector + namespace: '' + version_added: null + mso_schema_site_l3out: + description: Manage site-local layer3 Out (L3Outs) in schema template + name: mso_schema_site_l3out + namespace: '' + version_added: null + mso_schema_site_service_graph: + description: Manage Service Graph in schema sites + name: mso_schema_site_service_graph + namespace: '' + version_added: null + mso_schema_site_vrf: + description: Manage site-local VRFs in schema template + name: mso_schema_site_vrf + namespace: '' + version_added: null + mso_schema_site_vrf_region: + description: Manage site-local VRF regions in schema template + name: mso_schema_site_vrf_region + namespace: '' + version_added: null + mso_schema_site_vrf_region_cidr: + description: Manage site-local VRF region CIDRs in schema template + name: mso_schema_site_vrf_region_cidr + namespace: '' + version_added: null + mso_schema_site_vrf_region_cidr_subnet: + description: Manage site-local VRF regions in schema template + name: mso_schema_site_vrf_region_cidr_subnet + namespace: '' + version_added: null + mso_schema_site_vrf_region_hub_network: + description: Manage site-local VRF region hub network in schema template + name: mso_schema_site_vrf_region_hub_network + namespace: '' + version_added: null + mso_schema_template: + description: Manage templates in schemas + name: mso_schema_template + namespace: '' + version_added: null + mso_schema_template_anp: + description: Manage Application Network Profiles (ANPs) in schema templates + name: mso_schema_template_anp + namespace: '' + version_added: null + mso_schema_template_anp_epg: + description: Manage Endpoint Groups (EPGs) in schema templates + name: mso_schema_template_anp_epg + namespace: '' + version_added: null + mso_schema_template_anp_epg_contract: + description: Manage EPG contracts in schema templates + name: mso_schema_template_anp_epg_contract + namespace: '' + version_added: null + mso_schema_template_anp_epg_selector: + description: Manage EPG selector in schema templates + name: mso_schema_template_anp_epg_selector + namespace: '' + version_added: null + mso_schema_template_anp_epg_subnet: + description: Manage EPG subnets in schema templates + name: mso_schema_template_anp_epg_subnet + namespace: '' + version_added: null + mso_schema_template_bd: + description: Manage Bridge Domains (BDs) in schema templates + name: mso_schema_template_bd + namespace: '' + version_added: null + mso_schema_template_bd_dhcp_policy: + description: Manage BD DHCP Policy in schema templates + name: mso_schema_template_bd_dhcp_policy + namespace: '' + version_added: null + mso_schema_template_bd_subnet: + description: Manage BD subnets in schema templates + name: mso_schema_template_bd_subnet + namespace: '' + version_added: null + mso_schema_template_clone: + description: Clone templates + name: mso_schema_template_clone + namespace: '' + version_added: null + mso_schema_template_contract_filter: + description: Manage contract filters in schema templates + name: mso_schema_template_contract_filter + namespace: '' + version_added: null + mso_schema_template_contract_service_graph: + description: Manage the service graph association with a contract in schema + template + name: mso_schema_template_contract_service_graph + namespace: '' + version_added: null + mso_schema_template_deploy: + description: Deploy schema templates to sites + name: mso_schema_template_deploy + namespace: '' + version_added: null + mso_schema_template_deploy_status: + description: Check query of objects before deployment to site + name: mso_schema_template_deploy_status + namespace: '' + version_added: null + mso_schema_template_external_epg: + description: Manage external EPGs in schema templates + name: mso_schema_template_external_epg + namespace: '' + version_added: null + mso_schema_template_external_epg_contract: + description: Manage Extrnal EPG contracts in schema templates + name: mso_schema_template_external_epg_contract + namespace: '' + version_added: 0.0.8 + mso_schema_template_external_epg_selector: + description: Manage External EPG selector in schema templates + name: mso_schema_template_external_epg_selector + namespace: '' + version_added: null + mso_schema_template_external_epg_subnet: + description: Manage External EPG subnets in schema templates + name: mso_schema_template_external_epg_subnet + namespace: '' + version_added: 0.0.8 + mso_schema_template_filter_entry: + description: Manage filter entries in schema templates + name: mso_schema_template_filter_entry + namespace: '' + version_added: null + mso_schema_template_l3out: + description: Manage l3outs in schema templates + name: mso_schema_template_l3out + namespace: '' + version_added: null + mso_schema_template_migrate: + description: Migrate Bridge Domains (BDs) and EPGs between templates + name: mso_schema_template_migrate + namespace: '' + version_added: null + mso_schema_template_service_graph: + description: Manage Service Graph in schema templates + name: mso_schema_template_service_graph + namespace: '' + version_added: null + mso_schema_template_vrf: + description: Manage VRFs in schema templates + name: mso_schema_template_vrf + namespace: '' + version_added: null + mso_schema_template_vrf_contract: + description: Manage vrf contracts in schema templates + name: mso_schema_template_vrf_contract + namespace: '' + version_added: 0.0.8 + mso_schema_validate: + description: Validate the schema before deploying it to site + name: mso_schema_validate + namespace: '' + version_added: 1.3.0 + mso_service_node_type: + description: Manage Service Node Types + name: mso_service_node_type + namespace: '' + version_added: null + mso_site: + description: Manage sites + name: mso_site + namespace: '' + version_added: null + mso_tenant: + description: Manage tenants + name: mso_tenant + namespace: '' + version_added: null + mso_tenant_site: + description: Manage tenants with cloud sites. + name: mso_tenant_site + namespace: '' + version_added: null + mso_user: + description: Manage users + name: mso_user + namespace: '' + version_added: null + mso_version: + description: Get version of MSO + name: mso_version + namespace: '' + version_added: null + ndo_schema_template_deploy: + description: Deploy schema templates to sites for NDO v3.7 and higher + name: ndo_schema_template_deploy + namespace: '' + version_added: null + netconf: {} + shell: {} + strategy: {} + vars: {} +version: 2.2.0 diff --git a/ansible_collections/cisco/mso/changelogs/changelog.yaml b/ansible_collections/cisco/mso/changelogs/changelog.yaml new file mode 100644 index 000000000..86e29d812 --- /dev/null +++ b/ansible_collections/cisco/mso/changelogs/changelog.yaml @@ -0,0 +1,404 @@ +ancestor: 0.0.4 +releases: + 0.0.5: + changes: + release_summary: New release v0.0.5 + release_date: '2020-04-07' + 0.0.6: + changes: + bugfixes: + - Add aliases for backward support of permissions in role module. + - Add integration test for mso_schema_template_db and fix un-needed push to + API found by integration test. + - Consistent object output on domain_associations + - Fix EPG / External EPG Contract issue and create test for mso_schema_template_anp_epg_contract + and mso_schema_template_external_epg_contract + - Fix contract filter issue and add contract-filter test file + - Fix duplicate user, add admin user to associated user list and update tenant + test file + - Fix intersite_multicast_source attribute issue in mso_schema_template_anp_epg + and add the proxy_arp argument. + - Fix mso_schema_template_anp_epg idempotancy for both EPG and EPG with contracts + - Remove label with test domain before create it + - Send context instead of vrf when vrf parameter is used + - Update mso_schema_template_bd.py example for BD in another schema + minor_changes: + - ACI/MSO - Use get() dict lookups (https://github.com/ansible/ansible/pull/63074) + - Add EPG and ANP at site level when needed + - Add github action CI pipeline with test coverage + - Add login domain support for authentication in all modules + - Add support for DHCP querier to all subnet objects. Add partial test in mso_schema_template_bd + integration test. + - Add support for clean output if needed for debuging + - Add test file for mso_schema_template_anp_epg + - Added DHCP relay options and scope options to MSO schema template bd + - Added ability to bind epg to static fex port + - Added module to manage contracts for external EPG in Cisco MSO (https://github.com/ansible/ansible/pull/63550) + - Added module to manage template external epg subnet for Cisco MSO (https://github.com/ansible/ansible/pull/63542) + - Disabling tests for the role modules as API is not supported after 2.2.3i + until further notice + - Increased test coverage for existing module integration tests. + - Modified fail messages for site and updated documentation + - Moving test to Ansible v2.9.9 and increasing timelimit for mutex to 30+ min + - Update authors. + - Update mso_schema_site_anp.py (https://github.com/ansible/ansible/pull/67099) + - Updated Test File Covering all conditions + - mso_schema_site_anp_epg_staticport - Add VPC support (https://github.com/ansible/ansible/pull/62803) + release_summary: New release v0.0.6 + release_date: '2020-06-13' + 0.0.7: + changes: + bugfixes: + - Fix mso_schema_site_vrf_region_cidr to automatically create VRF and Region + if not present at site level + - Fix query condition when VRF or Region do not exist at site level + - Remove unused regions attribute from mso_schema_template_vrf + minor_changes: + - Add l3out, preferred_group and test file for mso_schema_template_externalepg + - Add mso_schema_template_vrf_contract module and test file + - Add new attribute choice "policy_compression" to mso_Schema_template_contract_filter + - Add new functionality - Direct Port Channel (dpc), micro-seg-vlan and default + values + - Add new module for anp-epg-selector in site level + - Add new module mso_schema_template_anp_epg_selector and its test file + - Add new module mso_schema_vrf_contract + - Add new module mso_tenant_site to support cloud and non-cloud sites association + with a tenant and test file (https://github.com/CiscoDevNet/ansible-mso/pull/62) + - Add new mso_site_external_epg_selector module and test file + - Add site external epg and contract filter test + - Add support for VGW attribute in mso_schema_site_vrf_region_cidr_subnet + - Add support to set account as inactive using account_status attribute in mso_user + - Add test for mso_schema_site_vrf_region_cidr module + - Add test for mso_schema_site_vrf_region_cidr_subnet module + - Add vzAny attribute in mso_schema_template_vrf + - Automatically add ANP and EPG at site level and new test file for mso_schema_site_anp_epg_staticport + (https://github.com/CiscoDevNet/ansible-mso/pull/55) + - Modified External EPG module and addition of new Selector module + release_summary: New release v0.0.7 + release_date: '2020-07-08' + 0.0.8: + changes: + bugfixes: + - Add login_domain to existing test. + - Add missing tests for VRF settings and changing those settings. + - Add test for specifying read-only roles and increase overall test coverage + of mso_user (https://github.com/CiscoDevNet/ansible-mso/pull/77) + - Add test to mso_schema_template_vrf, mso_schema_template_external_epg and + mso_schema_template_anp_epg to check for API error when pushing changes to + object with existing contract. + - Cleanup unused imports, unused variables and branches and change a variable + from ambiguous name to reduce warnings at Ansible Galaxy import + - Fix API error when pushing EPG with existing contracts + - Fix role tests to work with pre/post 2.2.4 and re-enable them + - Fix site issue if no site present and fix test issues with MSO v3.0 + - Fixing External EPG renaming for 2.9 and later + - Fixing L3MCast test to pass on 2.2.4 + - Fixing wrong removal of schemas + - Test hub network module after creating region manually + - Updating Azure site IP in inventory and add second MSO version to inventory + minor_changes: + - Add Login Domain support to mso_site + - Add aliases file for contract_filter module + - Add contract information in current and previous part + - Add new module and test file to query MSO version + - New backup module and test file (https://github.com/CiscoDevNet/ansible-mso/pull/80) + - Renaming mso_schema_template_externalepg module to mso_schema_template_external_epg + while keeping both working. + - Update cidr module, udpate attributes in hub network module and its test file + - Use a function to reuuse duplicate part + release_summary: New release v0.0.8 + release_date: '2020-07-21' + 1.0.0: + changes: + bugfixes: + - Fix sanity issues to support 2.10.0 + minor_changes: + - Add changelog + - Fix M() and module to use FQCN + - Update Ansible version in CI and add 2.10.0 to sanity in CI. + - Update Readme with supported versions + release_summary: 'This is the first official release of the ``cisco.mso`` collection + on 2020-08-18. + + This changelog describes all changes made to the modules and plugins included + in this collection since Ansible 2.9.0. + + ' + release_date: '2020-08-18' + 1.0.1: + changes: + bugfixes: + - Fix default value for l2Stretch in mso_schema_template_bd module + - Fix deletion of schema when wrong template is provided in single template + schema + - Fix examples in documentation for mso_schema_template_l3out and mso_user + - Fix naming issue in deploy module + - Remove author emails due to length restriction + - Remove dead code branch in mso_schema_template + minor_changes: + - Add delete capability to mso_schema_site + - Add env_fallback for mso_argument_spec params + - Add non existing template deletion test + - Add test file for mso_schema_template + - Add test file for site_bd_subnet + - Bump module to v1.0.1 + - Extent mso_tenant test case coverage + release_summary: 'Release v1.0.1 of the ``cisco.mso`` collection on 2020-10-30. + + This changelog describes all changes made to the modules and plugins included + in this collection since v1.0.0. + + ' + release_date: '2020-10-30' + 1.1.0: + changes: + bugfixes: + - Fix anp idempotency issue + - Fix crash issue when using irrelevant site-template + - Fix default value for mso_schema state parameter + - Fix examples for mso_schema + - Fix galaxy-importer check warnings + - Fix issue on mso_schema_site_vrf_region_cidr_subnet to allow an AWS subnet + to be used for a TGW Attachment (Hub Network) + - Fix module name in example of mso_schema_site_vrf_region + - Fix mso_backup upload issue + - Fix sanity test error mso_schema_site_bd + - Fix some coding standard and improvements to contributed mso_dhcp_relay modules + and test files + - Fix space in asssertion + - Fix space in site_anp_epg_domain + - Fix space in test file + - Remove space from template name in all modules + - Remove space in template name + minor_changes: + - Add DHCP Policy Operations + - Add SVI MAC Addreess option in mso_schema_site_bd + - Add additional test file to add tenant from templated payload file + - Add attribute virtual_ip to mso_schema_site_bd_subnet + - Add capability for restore and download backup + - Add capability to upload backup + - Add check for undeploy under MSO version + - Add error handeling test file + - Add error message to display when yaml has failed to load + - Add galaxy-importer check + - Add galaxy-importer config + - Add mso_dhcp_option_policy and mso_dhcp_option_policy_option and test files + - Add new module mso_rest and test case files to support GET api method + - Add new options to template bd and updated test file + - Add notes to use region_cidr module to create region + - Add task to undeploy the template from the site + - Add tasks in test file to remove templates for mso_schema_template_migrate + - Add test case for schema removing + - Add test cases to verify GET, PUT, POST and DELETE API methods for sites in + mso_rest.py + - Add test file for mso_schema + - Add test file for mso_schema_template_anp + - Add test file for region module + - Add test files yaml_inline and yaml_string to support YAML + - Add userAssociations to tenants to resolve CI issues + - Addition of cloud setting for ext epg + - Changes made to payload of mso_schema_template_external_epg + - Changes to options in template bd + - Check warning + - Documentation Corrected + - Force arp flood to be true when l2unkwunicast is flood + - Make changes to display correct status code + - Modify mso library and updated test file + - Modify mso_rest test files to make PATCH available, and test other methods + against schemas + - Move options for subnet from mso to the template_bd_subnet module + - Python lint corrected + - Redirect log to both stdout and log.txt file & Check warnings and errors + - Remove creation example in document of mso_schema_site_vrf_region + - Remove present state from mso_schema module + - Removed unused variable in mso_schema_site_vrf_region_hub_network + - Test DHCP Policy Provider added + - Test file for mso_dhcp_relay_policy added + - Test file for template_bd_subnet and new option foe module + release_summary: 'Release v1.1.0 of the ``cisco.mso`` collection on 2021-01-20. + + This changelog describes all changes made to the modules and plugins included + in this collection since v1.0.1. + + ' + release_date: '2021-01-20' + 1.2.0: + changes: + bugfixes: + - Add test case and small fixes to mso_schema_site_bd_l3out module + - Fix documentation issues accross modules + - Fix fail_json usage accross module_utils/mso.py + - Fix mso_rest to support HTTPAPI plugin and tests to support ND platform + - Fix mso_user to due to error in v1 API in MSO 3.2 + - Fix path issue in mso_schema_template_migrate + - Fixes for site level external epgs and site level L3Outs + - Fixes to support MSO 3.3 + - Remove query of all schemas to get schema ID and only query schema ID indentity + list API + minor_changes: + - Add Ansible common HTTPAPI dependancy in galaxy.yml + - Add HTTPAPI connection plugin support and HTTPAPI MSO connection plugin + - Add primary and unicast_routing attributes to mso_schema_template_bd + - Add requirements.txt for Ansible Environment support + - Add schema and template cloning modules mso_schema_clone and mso_schema_template_clone + - Add support cisco.nd.nd connection plugin + - Add support for multiple DCHP policies in a BD and new module mso_schema_template_bd_dhcp_policy + - Upgrade CI to latest Ansible version and Python 3.8 + release_summary: 'Release v1.2.0 of the ``cisco.mso`` collection on 2021-06-02. + + This changelog describes all changes made to the modules and plugins included + in this collection since v1.1.0. + + ' + plugins: + httpapi: + - description: MSO Ansible HTTPAPI Plugin. + name: mso + namespace: null + release_date: '2021-06-02' + 1.3.0: + changes: + bugfixes: + - Add no_log to aws_access_key and secret_key in mso_tenant_site + - Fix MSO HTTP API to work without host, user and password module attribute + - Fix issue with unicast_routing idemptotency in mso_schema_template_bd + - Fix mso_schema_site_anp and mso_schema_site_anp_epg idempotency issue + - Remove sanity ignore files and fix sanity issues that were previously ignored + minor_changes: + - Add container_overlay and underlay_context_profile support to mso_schema_site_vrf_region + - Add description support to various modules + - Add hosted_vrf support to mso_schema_site_vrf_region_cidr_subnet + - Add module mso_schema_validate to check schema validations + - Add private_link_label support to mso_schema_site_anp_epg and mso_schema_site_vrf_region_cidr_subnet + - Add qos_level and Service EPG support to mso_schema_template_anp_epg + - Add qos_level, action and priority support to mso_schema_template_contract_filter + - Add schema and template description support to mso_schema_template + - Add subnet as primary support to mso_schema_template_bd_subnet + - Add support for automatically creating anp structure at site level when using + mso_schema_site_anp_epg + - Add support for encap-flood as multi_destination_flooding in mso_schema_template_bd + - Add test file for mso_schema_site_anp, mso_schema_site_anp_epg, mso_schema_template_external_epg_subnet + mso_schema_template_filter_entry + - Improve scope attribute documentation in mso_schema_template_external_epg_subnet + - Update Ansible version used in automated testing to v2.9.27, v2.10.16 and + addition of v2.11.7 and v2.12.1 + release_summary: 'Release v1.3.0 of the ``cisco.mso`` collection on 2021-12-18. + + This changelog describes all changes made to the modules and plugins included + in this collection since v1.2.0. + + ' + release_date: '2021-12-18' + 1.4.0: + changes: + bugfixes: + - Fix arp_entry value issue in mso_schema_template_filter_entry + - Fix mso_schema_site_anp idempotency when children exists + - Fix use_ssl documentation to explain usage when used with HTTPAPI connection + plugin + minor_changes: + - Update mso_schema_template_clone to use new method from NDO and unrestrict + it to earlier version + release_summary: 'Release v1.4.0 of the ``ansible-mso`` collection on 2022-03-15. + + This changelog describes all changes made to the modules and plugins included + in this collection since v1.3.0. + + ' + release_date: '2022-03-15' + 2.1.0: + changes: + bugfixes: + - Fix time issue when host running ansible is in a different timezone then NDO + - Remove mso_guide from notes + deprecated_features: + - The mso_schema_template_contract_filter contract_filter_type attribute is + deprecated. The value is now deduced from filter_type. + minor_changes: + - Add aci_remote_location module (#259) + - Add mso_backup_schedule module (#250) + - Add mso_chema_template_contract_service_graph module (#257) + - Add mso_schema_template_service_graph, mso_schema_site_service_graph and mso_service_node_type + modules (#243) + - Add primary attribute to mso_schema_site_bd_subnet (#254) + release_summary: 'Release v2.1.0 of the ``ansible-mso`` collection on 2022-10-14. + + This changelog describes all changes made to the modules and plugins included + in this collection since v1.4.0. + + The version was bumped directly to 2.1.0 due to a previous collection upload + issue on galaxy. + + ' + release_date: '2022-10-14' + 2.2.0: + changes: + bugfixes: + - Fix MSO HTTPAPI plugin login domain issue (#317) + - Fix deploymentImmediacy key inconsistency in the API used by mso_schema_site_anp + and mso_schema_site_anp_epg (#283) + - Fix mso_schema_template_bd issue when created with unicast_routing as false + (#278) + - Fix to be able to add multiple filter and filters with "-" in their names + (#306) + minor_changes: + - Add automatic creation of site bd when not existing in mso_schema_site_bd_subnet + module (#263) + - Add automatic schema validation functionality to mso_schema_template_deploy + and ndo_schema_template_deploy (#318) + - Add ndo_schema_template_deploy to support NDO 4+ deploy functionality (#305) + - Add support for l3out from different template or schema in mso_schema_site_bd_l3out + (#304) + - Add support for orchestrator_only attribute for mso_tenant with state absent + (#268) + release_summary: 'Release v2.2.0 of the ``ansible-mso`` collection on 2023-01-29. + + This changelog describes all changes made to the modules and plugins included + in this collection since v2.1.0. + + ' + release_date: '2023-01-29' + 2.2.1: + changes: + bugfixes: + - Fix datetime support for python2.7 in mso_backup_schedule (#323) + release_summary: 'Release v2.2.1 of the ``ansible-mso`` collection on 2023-01-31. + + This changelog describes all changes made to the modules and plugins included + in this collection since v2.2.0. + + ' + release_date: '2023-01-31' + 2.3.0: + changes: + bugfixes: + - Fix idempotency for mso_schema_site_bd_l3out + minor_changes: + - Add module mso_schema_site_anp_epg_bulk_staticport (#330) + - Add route_reachability attribute to mso_schema_site_external_epg module (#335) + release_summary: 'Release v2.3.0 of the ``ansible-mso`` collection on 2023-03-30. + + This changelog describes all changes made to the modules and plugins included + in this collection since v2.2.1. + + ' + release_date: '2023-03-30' + 2.4.0: + changes: + bugfixes: + - Add attributes to payload for changed schema behaviour of deploymentImmediacy + (deployImmediacy) and vmmDomainProperties (properties at domain level in payload) + (#362) + - Fix mso_backup for NDO and ND-based MSO v3.2+ (#333) + - Fix validation condition for path in mso_schema_site_anp_epg_bulk_staticport + module (#360) + minor_changes: + - Add ip_data_plane_learning and preferred_group arguments to mso_schema_template_vrf + module (#358) + release_summary: 'Release v2.4.0 of the ``ansible-mso`` collection on 2023-04-19. + + This changelog describes all changes made to the modules and plugins included + in this collection since v2.3.0. + + ' + release_date: '2023-04-19' diff --git a/ansible_collections/cisco/mso/changelogs/config.yaml b/ansible_collections/cisco/mso/changelogs/config.yaml new file mode 100644 index 000000000..d6a4c94a0 --- /dev/null +++ b/ansible_collections/cisco/mso/changelogs/config.yaml @@ -0,0 +1,31 @@ +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +ignore_other_fragment_extensions: true +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +sections: +- - major_changes + - Major Changes +- - minor_changes + - Minor Changes +- - breaking_changes + - Breaking Changes / Porting Guide +- - deprecated_features + - Deprecated Features +- - removed_features + - Removed Features (previously deprecated) +- - security_fixes + - Security Fixes +- - bugfixes + - Bugfixes +- - known_issues + - Known Issues +title: Cisco MSO Ansible Collection +trivial_section_name: trivial +use_fqcn: true diff --git a/ansible_collections/cisco/mso/codecov.yml b/ansible_collections/cisco/mso/codecov.yml new file mode 100644 index 000000000..f0f6358e6 --- /dev/null +++ b/ansible_collections/cisco/mso/codecov.yml @@ -0,0 +1,4 @@ +coverage: + precision: 2 + round: down + range: "70...100" diff --git a/ansible_collections/cisco/mso/meta/runtime.yml b/ansible_collections/cisco/mso/meta/runtime.yml new file mode 100644 index 000000000..bbc15c503 --- /dev/null +++ b/ansible_collections/cisco/mso/meta/runtime.yml @@ -0,0 +1,142 @@ +--- +requires_ansible: '>=2.9.10' +plugin_routing: + modules: + mso_schema_template_externalepg: + redirect: cisco.mso.mso_schema_template_external_epg +action_groups: + mso: + - mso_backup + - mso_backup_schedule + - mso_dhcp_option_policy + - mso_dhcp_option_policy_option + - mso_dhcp_relay_policy + - mso_dhcp_relay_policy_provider + - mso_label + - mso_remote_location + - mso_rest + - mso_role + - mso_schema + - mso_schema_clone + - mso_schema_site + - mso_schema_site_anp + - mso_schema_site_anp_epg + - mso_schema_site_anp_epg_bulk_staticport + - mso_schema_site_anp_epg_domain + - mso_schema_site_anp_epg_selector + - mso_schema_site_anp_epg_staticleaf + - mso_schema_site_anp_epg_staticport + - mso_schema_site_anp_epg_subnet + - mso_schema_site_bd + - mso_schema_site_bd_l3out + - mso_schema_site_bd_subnet + - mso_schema_site_external_epg + - mso_schema_site_external_epg_selector + - mso_schema_site_l3out + - mso_schema_site_service_graph + - mso_schema_site_vrf + - mso_schema_site_vrf_region + - mso_schema_site_vrf_region_cidr + - mso_schema_site_vrf_region_cidr_subnet + - mso_schema_site_vrf_region_hub_network + - mso_schema_template + - mso_schema_template_anp + - mso_schema_template_anp_epg + - mso_schema_template_anp_epg_contract + - mso_schema_template_anp_epg_selector + - mso_schema_template_anp_epg_subnet + - mso_schema_template_bd + - mso_schema_template_bd_dhcp_policy + - mso_schema_template_bd_subnet + - mso_schema_template_clone + - mso_schema_template_contract_filter + - mso_schema_template_contract_service_graph + - mso_schema_template_deploy + - mso_schema_template_deploy_status + - mso_schema_template_external_epg + - mso_schema_template_external_epg_contract + - mso_schema_template_external_epg_selector + - mso_schema_template_external_epg_subnet + - mso_schema_template_externalepg + - mso_schema_template_filter_entry + - mso_schema_template_l3out + - mso_schema_template_migrate + - mso_schema_template_service_graph + - mso_schema_template_vrf + - mso_schema_template_vrf_contract + - mso_schema_validate + - mso_service_node_type + - mso_site + - mso_tenant + - mso_tenant_site + - mso_user + - mso_version + ndo: + - ndo_schema_template_deploy + all: + - mso_backup + - mso_backup_schedule + - mso_dhcp_option_policy + - mso_dhcp_option_policy_option + - mso_dhcp_relay_policy + - mso_dhcp_relay_policy_provider + - mso_label + - mso_remote_location + - mso_rest + - mso_role + - mso_schema + - mso_schema_clone + - mso_schema_site + - mso_schema_site_anp + - mso_schema_site_anp_epg + - mso_schema_site_anp_epg_bulk_staticport + - mso_schema_site_anp_epg_domain + - mso_schema_site_anp_epg_selector + - mso_schema_site_anp_epg_staticleaf + - mso_schema_site_anp_epg_staticport + - mso_schema_site_anp_epg_subnet + - mso_schema_site_bd + - mso_schema_site_bd_l3out + - mso_schema_site_bd_subnet + - mso_schema_site_external_epg + - mso_schema_site_external_epg_selector + - mso_schema_site_l3out + - mso_schema_site_service_graph + - mso_schema_site_vrf + - mso_schema_site_vrf_region + - mso_schema_site_vrf_region_cidr + - mso_schema_site_vrf_region_cidr_subnet + - mso_schema_site_vrf_region_hub_network + - mso_schema_template + - mso_schema_template_anp + - mso_schema_template_anp_epg + - mso_schema_template_anp_epg_contract + - mso_schema_template_anp_epg_selector + - mso_schema_template_anp_epg_subnet + - mso_schema_template_bd + - mso_schema_template_bd_dhcp_policy + - mso_schema_template_bd_subnet + - mso_schema_template_clone + - mso_schema_template_contract_filter + - mso_schema_template_contract_service_graph + - mso_schema_template_deploy + - mso_schema_template_deploy_status + - mso_schema_template_external_epg + - mso_schema_template_external_epg_contract + - mso_schema_template_external_epg_selector + - mso_schema_template_external_epg_subnet + - mso_schema_template_externalepg + - mso_schema_template_filter_entry + - mso_schema_template_l3out + - mso_schema_template_migrate + - mso_schema_template_service_graph + - mso_schema_template_vrf + - mso_schema_template_vrf_contract + - mso_schema_validate + - mso_service_node_type + - mso_site + - mso_tenant + - mso_tenant_site + - mso_user + - mso_version + - ndo_schema_template_deploy diff --git a/ansible_collections/cisco/mso/plugins/.DS_Store b/ansible_collections/cisco/mso/plugins/.DS_Store Binary files differnew file mode 100644 index 000000000..1ffed58d7 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/.DS_Store diff --git a/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py b/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py new file mode 100644 index 000000000..c7d3d81c0 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r""" +options: + host: + description: + - IP Address or hostname of the ACI Multi Site Orchestrator host. + - If the value is not specified in the task, the value of environment variable C(MSO_HOST) will be used instead. + type: str + aliases: [ hostname ] + port: + description: + - Port number to be used for the REST connection. + - The default value depends on parameter `use_ssl`. + - If the value is not specified in the task, the value of environment variable C(MSO_PORT) will be used instead. + type: int + username: + description: + - The username to use for authentication. + - If the value is not specified in the task, the value of environment variables C(MSO_USERNAME) or C(ANSIBLE_NET_USERNAME) will be used instead. + type: str + password: + description: + - The password to use for authentication. + - If the value is not specified in the task, the value of environment variables C(MSO_PASSWORD) or C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + output_level: + description: + - Influence the output of this MSO module. + - C(normal) means the standard output, incl. C(current) dict + - C(info) adds informational output, incl. C(previous), C(proposed) and C(sent) dicts + - C(debug) adds debugging output, incl. C(filter_string), C(method), C(response), C(status) and C(url) information + - If the value is not specified in the task, the value of environment variable C(MSO_OUTPUT_LEVEL) will be used instead. + type: str + choices: [ debug, info, normal ] + default: normal + timeout: + description: + - The socket level timeout in seconds. + - If the value is not specified in the task, the value of environment variable C(MSO_TIMEOUT) will be used instead. + type: int + default: 30 + use_proxy: + description: + - If C(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts. + - If the value is not specified in the task, the value of environment variable C(MSO_USE_PROXY) will be used instead. + - The default is C(true). + type: bool + use_ssl: + description: + - If C(false), an HTTP connection will be used instead of the default HTTPS connection. + - If the value is not specified in the task, the value of environment variable C(MSO_USE_SSL) will be used instead. + - When using a HTTPAPI connection plugin the inventory variable C(ansible_httpapi_use_ssl) will be used if this attribute is not specified. + - The default is C(false) when using a HTTPAPI connection plugin (mso or nd) and C(true) when using the legacy connection method (only for mso). + type: bool + validate_certs: + description: + - If C(false), SSL certificates will not be validated. + - This should only set to C(false) when used on personally controlled sites using self-signed certificates. + - If the value is not specified in the task, the value of environment variable C(MSO_VALIDATE_CERTS) will be used instead. + - The default is C(true). + type: bool + login_domain: + description: + - The login domain name to use for authentication. + - The default value is Local. + - If the value is not specified in the task, the value of environment variable C(MSO_LOGIN_DOMAIN) will be used instead. + type: str +requirements: +- Multi Site Orchestrator v2.1 or newer +notes: +- This module was written to support Multi Site Orchestrator v2.1 or newer. Some or all functionality may not work on earlier versions. +""" diff --git a/ansible_collections/cisco/mso/plugins/httpapi/mso.py b/ansible_collections/cisco/mso/plugins/httpapi/mso.py new file mode 100644 index 000000000..5d69c8a64 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/httpapi/mso.py @@ -0,0 +1,291 @@ +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +--- +name: mso +short_description: MSO Ansible HTTPAPI Plugin. +description: + - This MSO plugin provides the HTTPAPI transport methods needed to initiate + a connection to MSO, send API requests and process the + response. +version_added: "1.2.0" +""" + +import json +import re +import pickle + +# import ipaddress +import traceback + +from ansible.module_utils.six import PY3 +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible.plugins.httpapi import HttpApiBase + + +class HttpApi(HttpApiBase): + def __init__(self, *args, **kwargs): + super(HttpApi, self).__init__(*args, **kwargs) + self.platform = "cisco.mso" + self.headers = {"Content-Type": "application/json"} + self.params = {} + self.auth = None + self.backup_hosts = None + self.host_counter = 0 + + self.error = None + self.method = "GET" + self.path = "" + self.status = -1 + self.info = {} + + def get_platform(self): + return self.platform + + def set_params(self, params): + self.params = params + + def set_backup_hosts(self): + try: + list_of_hosts = re.sub(r"[[\]]", "", self.connection.get_option("host")).split(",") + # ipaddress.ip_address(list_of_hosts[0]) + return list_of_hosts + except Exception: + return [] + + def login(self, username, password): + """Log in to MSO""" + # Perform login request + self.connection.queue_message("vvvv", "Starting Login to {0}".format(self.connection.get_option("host"))) + + method = "POST" + path = "/mso/api/v1/auth/login" + full_path = self.connection.get_option("host") + path + + if (self.params.get("login_domain") is not None) and (self.params.get("login_domain") != "Local"): + domain_id = self._get_login_domain_id(self.params.get("login_domain")) + payload = {"username": self.connection.get_option("remote_user"), "password": self.connection.get_option("password"), "domainId": domain_id} + else: + payload = {"username": self.connection.get_option("remote_user"), "password": self.connection.get_option("password")} + + # Override the global username/password with the ones specified per task + if self.params.get("username") is not None: + payload["username"] = self.params.get("username") + if self.params.get("password") is not None: + payload["password"] = self.params.get("password") + data = json.dumps(payload) + try: + self.connection.queue_message("vvvv", "login() - connection.send({0}, {1}, {2}, {3})".format(path, data, method, self.headers)) + response, response_data = self.connection.send(path, data, method=method, headers=self.headers) + # Handle MSO response + self.status = response.getcode() + if self.status != 201: + self.connection.queue_message("vvvv", "login status incorrect status={0}".format(self.status)) + json_response = self._response_to_json(response_data) + self.error = dict(code=self.status, message="Authentication failed: {0}".format(json_response)) + raise ConnectionError(json.dumps(self._verify_response(response, method, full_path, response_data))) + self.connection._auth = {"Authorization": "Bearer {0}".format(self._response_to_json(response_data).get("token"))} + + except ConnectionError: + self.connection.queue_message("vvvv", "login() - ConnectionError Exception") + raise + except Exception as e: + self.connection.queue_message("vvvv", "login() - Generic Exception") + self.error = dict(code=self.status, message="Authentication failed: Request failed: {0}".format(e)) + raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None))) + + def logout(self): + method = "DELETE" + path = "/mso/api/v1/auth/logout" + + try: + response, response_data = self.connection.send(path, {}, method=method, headers=self.headers) + except Exception as e: + self.error = dict(code=self.status, message="Error on attempt to logout from MSO. {0}".format(e)) + raise ConnectionError(json.dumps(self._verify_response(None, method, self.connection.get_option("host") + path, None))) + self.connection._auth = None + + def send_request(self, method, path, data=None): + """This method handles all MSO REST API requests other than login""" + + self.error = None + self.path = "" + self.status = -1 + self.info = {} + self.method = "GET" + + if data is None: + data = {} + + self.connection.queue_message("vvvv", "send_request method called") + # # Case1: List of hosts is provided + # self.backup_hosts = self.set_backup_hosts() + # if not self.backup_hosts: + if self.connection._connected is True and self.params.get("host") != self.connection.get_option("host"): + self.connection._connected = False + self.connection.queue_message( + "vvvv", + "send_request reseting connection as host has changed from {0} to {1}".format(self.connection.get_option("host"), self.params.get("host")), + ) + + if self.params.get("host") is not None: + self.connection.set_option("host", self.params.get("host")) + + else: + try: + with open("my_hosts.pk", "rb") as fi: + self.host_counter = pickle.load(fi) + except FileNotFoundError: + pass + try: + self.connection.set_option("host", self.backup_hosts[self.host_counter]) + except (IndexError, TypeError): + pass + + if self.params.get("port") is not None: + self.connection.set_option("port", self.params.get("port")) + + if self.params.get("username") is not None: + self.connection.set_option("remote_user", self.params.get("username")) + + if self.params.get("password") is not None: + self.connection.set_option("password", self.params.get("password")) + + if self.params.get("use_proxy") is not None: + self.connection.set_option("use_proxy", self.params.get("use_proxy")) + + if self.params.get("use_ssl") is not None: + self.connection.set_option("use_ssl", self.params.get("use_ssl")) + + if self.params.get("validate_certs") is not None: + self.connection.set_option("validate_certs", self.params.get("validate_certs")) + + # Perform some very basic path input validation. + path = str(path) + if path[0] != "/": + self.error = dict(code=self.status, message="Value of <path> does not appear to be formated properly") + raise ConnectionError(json.dumps(self._verify_response(None, method, path, None))) + full_path = self.connection.get_option("host") + path + try: + self.connection.queue_message("vvvv", "send_request() - connection.send({0}, {1}, {2}, {3})".format(path, data, method, self.headers)) + response, rdata = self.connection.send(path, data, method=method, headers=self.headers) + except ConnectionError: + self.connection.queue_message("vvvv", "login() - ConnectionError Exception") + raise + except Exception as e: + self.connection.queue_message("vvvv", "send_request() - Generic Exception") + if self.error is None: + self.error = dict(code=self.status, message="MSO HTTPAPI send_request() Exception: {0} - {1}".format(e, traceback.format_exc())) + raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None))) + return self._verify_response(response, method, full_path, rdata) + + def handle_error(self): + self.host_counter += 1 + if self.host_counter == len(self.backup_hosts): + raise ConnectionError("No hosts left in cluster to continue operation") + with open("my_hosts.pk", "wb") as host_file: + pickle.dump(self.host_counter, host_file) + try: + self.connection.set_option("host", self.backup_hosts[self.host_counter]) + except IndexError: + pass + self.login(self.connection.get_option("remote_user"), self.connection.get_option("password")) + return True + + def _verify_response(self, response, method, path, data): + """Process the return code and response object from MSO""" + response_data = None + response_code = -1 + self.info.update(dict(url=path)) + if data is not None: + response_data = self._response_to_json(data) + if response is not None: + response_code = response.getcode() + path = response.geturl() + self.info.update(self._get_formated_info(response)) + + # Handle possible MSO error information + if response_code not in [200, 201, 202, 204]: + self.error = dict(code=self.status, message=response_data) + + self.info["method"] = method + if self.error is not None: + self.info["error"] = self.error + + self.info["body"] = response_data + + return self.info + + def _response_to_json(self, response_data): + """Convert response_data to json format""" + try: + response_value = response_data.getvalue() + except Exception: + response_value = response_data + response_text = to_text(response_value) + try: + return json.loads(response_text) if response_text else {} + # JSONDecodeError only available on Python 3.5+ + except Exception as e: + # Expose RAW output for troubleshooting + self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. {0}".format(e)) + self.info["raw"] = response_text + return + + def _get_login_domain_id(self, domain_name): + """Get a domain and return its id""" + if domain_name is None: + return None + + method = "GET" + path = "/mso/api/v1/auth/login-domains" + full_path = self.connection.get_option("host") + path + + # TODO: Replace response by - + response, data = self.connection.send(path, None, method=method, headers=self.headers) + + if data is not None: + response_data = self._response_to_json(data) + domains = response_data.get("domains") + if domains is not None: + for domain in domains: + if domain.get("name") == domain_name: + if "id" in domain: + return domain.get("id") + else: + self.error = dict(code=-1, message="Login domain lookup failed for domain '{0}': {1}".format(domain_name, domain)) + raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None))) + self.error = dict(code=-1, message="Login domain '{0}' is not a valid domain name.".format(domain_name)) + raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None))) + else: + self.error = dict(code=-1, message="Key 'domains' missing from data") + raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None))) + + def _get_formated_info(self, response): + """The code in this function is based out of Ansible fetch_url code + at https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/urls.py""" + info = dict(msg="OK (%s bytes)" % response.headers.get("Content-Length", "unknown"), url=response.geturl(), status=response.getcode()) + # Lowercase keys, to conform to py2 behavior, so that py3 and py2 are predictable + info.update(dict((k.lower(), v) for k, v in response.info().items())) + + # Don't be lossy, append header values for duplicate headers + # In Py2 there is nothing that needs done, py2 does this for us + if PY3: + temp_headers = {} + for name, value in response.headers.items(): + # The same as above, lower case keys to match py2 behavior, and create more consistent results + name = name.lower() + if name in temp_headers: + temp_headers[name] = ", ".join((temp_headers[name], value)) + else: + temp_headers[name] = value + info.update(temp_headers) + return info diff --git a/ansible_collections/cisco/mso/plugins/module_utils/constants.py b/ansible_collections/cisco/mso/plugins/module_utils/constants.py new file mode 100644 index 000000000..ea461f76c --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/module_utils/constants.py @@ -0,0 +1,24 @@ +FILTER_KEY_MAP = { + "both-way": "filterRelationships", + "consumer-to-provider": "filterRelationshipsConsumerToProvider", + "provider-to-consumer": "filterRelationshipsProviderToConsumer", +} + +PRIORITY_MAP = { + "default": "default", + "lowest_priority": "level1", + "medium_priority": "level2", + "highest_priority": "level3", +} + +SERVICE_NODE_CONNECTOR_MAP = { + "bd": {"id": "bd", "connector_type": "general"} + # 'external_epg': {'id': 'externalEpg', 'connector_type': 'route-peering'} +} + +YES_OR_NO_TO_BOOL_STRING_MAP = {"yes": "true", "no": "false"} + +NDO_4_UNIQUE_IDENTIFIERS = ["templateID", "autoRouteTargetImport", "autoRouteTargetExport"] + +NDO_API_VERSION_FORMAT = "/mso/api/{api_version}" +NDO_API_VERSION_PATH_FORMAT = "/mso/api/{api_version}/{path}" diff --git a/ansible_collections/cisco/mso/plugins/module_utils/mso.py b/ansible_collections/cisco/mso/plugins/module_utils/mso.py new file mode 100644 index 000000000..91475336b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/module_utils/mso.py @@ -0,0 +1,1354 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from copy import deepcopy +import re +import os +import ast +import datetime +import shutil +import tempfile +from ansible.module_utils.basic import json +from ansible.module_utils.basic import env_fallback +from ansible.module_utils.six import PY3 +from ansible.module_utils.six.moves import filterfalse +from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin +from ansible.module_utils.urls import fetch_url +from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.connection import Connection +from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_API_VERSION_PATH_FORMAT + +try: + from requests_toolbelt.multipart.encoder import MultipartEncoder + + HAS_MULTIPART_ENCODER = True +except ImportError: + HAS_MULTIPART_ENCODER = False + + +if PY3: + + def cmp(a, b): + return (a > b) - (a < b) + + +def issubset(subset, superset): + """Recurse through nested dictionary and compare entries""" + + # Both objects are the same object + if subset is superset: + return True + + # Both objects are identical + if subset == superset: + return True + + # Both objects have a different type + if type(subset) != type(superset): + return False + + for key, value in subset.items(): + # Ignore empty values + if value is None: + return True + + # Item from subset is missing from superset + if key not in superset: + return False + + # Item has different types in subset and superset + if type(superset.get(key)) != type(value): + return False + + # Compare if item values are subset + if isinstance(value, dict): + if not issubset(superset.get(key), value): + return False + elif isinstance(value, list): + try: + # NOTE: Fails for lists of dicts + if not set(value) <= set(superset.get(key)): + return False + except TypeError: + # Fall back to exact comparison for lists of dicts + diff = list(filterfalse(lambda i: i in value, superset.get(key))) + list(filterfalse(lambda j: j in superset.get(key), value)) + if diff: + return False + elif isinstance(value, set): + if not value <= superset.get(key): + return False + else: + if not value == superset.get(key): + return False + + return True + + +def update_qs(params): + """Append key-value pairs to self.filter_string""" + accepted_params = dict((k, v) for (k, v) in params.items() if v is not None) + return "?" + urlencode(accepted_params) + + +def mso_argument_spec(): + return dict( + host=dict(type="str", required=False, aliases=["hostname"], fallback=(env_fallback, ["MSO_HOST"])), + port=dict(type="int", required=False, fallback=(env_fallback, ["MSO_PORT"])), + username=dict(type="str", required=False, fallback=(env_fallback, ["MSO_USERNAME", "ANSIBLE_NET_USERNAME"])), + password=dict(type="str", required=False, no_log=True, fallback=(env_fallback, ["MSO_PASSWORD", "ANSIBLE_NET_PASSWORD"])), + output_level=dict(type="str", default="normal", choices=["debug", "info", "normal"], fallback=(env_fallback, ["MSO_OUTPUT_LEVEL"])), + timeout=dict(type="int", default=30, fallback=(env_fallback, ["MSO_TIMEOUT"])), + use_proxy=dict(type="bool", fallback=(env_fallback, ["MSO_USE_PROXY"])), + use_ssl=dict(type="bool", fallback=(env_fallback, ["MSO_USE_SSL"])), + validate_certs=dict(type="bool", fallback=(env_fallback, ["MSO_VALIDATE_CERTS"])), + login_domain=dict(type="str", fallback=(env_fallback, ["MSO_LOGIN_DOMAIN"])), + ) + + +def mso_reference_spec(): + return dict( + name=dict(type="str", required=True), + schema=dict(type="str"), + template=dict(type="str"), + ) + + +def mso_epg_subnet_spec(): + return dict( + subnet=dict(type="str", required=True, aliases=["ip"]), + description=dict(type="str"), + scope=dict(type="str", default="private", choices=["private", "public"]), + shared=dict(type="bool", default=False), + no_default_gateway=dict(type="bool", default=False), + ) + + +def mso_subnet_spec(): + subnet_spec = mso_epg_subnet_spec() + subnet_spec.update(dict(querier=dict(type="bool", default=False))) + return subnet_spec + + +def mso_bd_subnet_spec(): + subnet_spec = mso_epg_subnet_spec() + subnet_spec.update(dict(querier=dict(type="bool", default=False))) + subnet_spec.update(dict(primary=dict(type="bool", default=False))) + subnet_spec.update(dict(virtual=dict(type="bool", default=False))) + return subnet_spec + + +def mso_dhcp_spec(): + return dict( + dhcp_option_policy=dict(type="dict", options=mso_dhcp_option_spec()), + name=dict(type="str", required=True), + version=dict(type="int", required=True), + ) + + +def mso_dhcp_option_spec(): + return dict( + name=dict(type="str", required=True), + version=dict(type="int", required=True), + ) + + +def mso_contractref_spec(): + return dict( + name=dict(type="str", required=True), + schema=dict(type="str"), + template=dict(type="str"), + type=dict(type="str", required=True, choices=["consumer", "provider"]), + ) + + +def mso_expression_spec(): + return dict( + type=dict(type="str", required=True, aliases=["tag"]), + operator=dict(type="str", choices=["not_in", "in", "equals", "not_equals", "has_key", "does_not_have_key"], required=True), + value=dict(type="str"), + ) + + +def mso_expression_spec_ext_epg(): + return dict( + type=dict(type="str", choices=["ip_address"], required=True), + operator=dict(type="str", choices=["equals"], required=True), + value=dict(type="str", required=True), + ) + + +def mso_hub_network_spec(): + return dict( + name=dict(type="str", required=True), + tenant=dict(type="str", required=True), + ) + + +def mso_object_migrate_spec(): + return dict( + epg=dict(type="str", required=True), + anp=dict(type="str", required=True), + ) + + +def mso_service_graph_node_spec(): + return dict( + type=dict(type="str", required=True), + ) + + +def mso_service_graph_node_device_spec(): + return dict( + name=dict(type="str", required=True), + ) + + +def mso_service_graph_connector_spec(): + return dict( + provider=dict(type="str", required=True), + consumer=dict(type="str", required=True), + # Only connectorType bd with value "general" is supported for now thus fixed in code + # when connectorType externalEpg is supported "route-peering" should be added + # also change SERVICE_NODE_CONNECTOR_TYPE_MAP in constants.py + # also verify if connector type is specific to provider or always same for both + connector_object_type=dict(type="str", default="bd", choices=["bd"]), + provider_schema=dict(type="str"), + provider_template=dict(type="str"), + consumer_schema=dict(type="str"), + consumer_template=dict(type="str"), + ) + + +def mso_site_anp_epg_bulk_staticport_spec(): + return dict( + type=dict(type="str", choices=["port", "vpc", "dpc"]), + pod=dict(type="str"), # This parameter is not required for querying all objects + leaf=dict(type="str"), # This parameter is not required for querying all objects + fex=dict(type="str"), # This parameter is not required for querying all objects + path=dict(type="str"), # This parameter is not required for querying all objects + vlan=dict(type="int"), # This parameter is not required for querying all objects + primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects + deployment_immediacy=dict(type="str", choices=["immediate", "lazy"]), + mode=dict(type="str", choices=["native", "regular", "untagged"]), + ) + + +# Copied from ansible's module uri.py (url): https://github.com/ansible/ansible/blob/cdf62edc65f564fff6b7e575e084026fa7faa409/lib/ansible/modules/uri.py +def write_file(module, url, dest, content, resp, tmpsrc=None): + # create a tempfile with some test content + + if tmpsrc is None and content is not None: + fd, tmpsrc = tempfile.mkstemp(dir=module.tmpdir) + f = open(tmpsrc, "wb") + try: + f.write(content) + except Exception as e: + os.remove(tmpsrc) + module.fail_json(msg="Failed to create temporary content file: {0}".format(to_native(e))) + f.close() + + checksum_src = None + checksum_dest = None + + # raise an error if there is no tmpsrc file + if not os.path.exists(tmpsrc): + os.remove(tmpsrc) + module.fail_json(msg="Source '{0}' does not exist".format(tmpsrc)) + if not os.access(tmpsrc, os.R_OK): + os.remove(tmpsrc) + module.fail_json(msg="Source '{0}' is not readable".format(tmpsrc)) + checksum_src = module.sha1(tmpsrc) + + # check if there is no dest file + if os.path.exists(dest): + # raise an error if copy has no permission on dest + if not os.access(dest, os.W_OK): + os.remove(tmpsrc) + module.fail_json(msg="Destination '{0}' not writable".format(dest)) + if not os.access(dest, os.R_OK): + os.remove(tmpsrc) + module.fail_json(msg="Destination '{0}' not readable".format(dest)) + checksum_dest = module.sha1(dest) + else: + if not os.access(os.path.dirname(dest), os.W_OK): + os.remove(tmpsrc) + module.fail_json(msg="Destination dir '{0}' not writable".format(os.path.dirname(dest))) + + if checksum_src != checksum_dest: + try: + shutil.copyfile(tmpsrc, dest) + except Exception as e: + os.remove(tmpsrc) + module.fail_json(msg="failed to copy {0} to {1}: {2}".format(tmpsrc, dest, to_native(e))) + + os.remove(tmpsrc) + + +class MSOModule(object): + def __init__(self, module): + self.module = module + self.params = module.params + self.result = dict(changed=False) + self.headers = {"Content-Type": "text/json"} + self.platform = "mso" + + # normal output + self.existing = dict() + + # mso_rest output + self.jsondata = None + self.error = dict(code=None, message=None, info=None) + + # info output + self.previous = dict() + self.proposed = dict() + self.sent = dict() + self.stdout = None + self.patch_operation = None + + # debug output + self.has_modified = False + self.filter_string = "" + self.method = None + self.path = None + self.response = None + self.status = None + self.url = None + self.httpapi_logs = list() + + if self.module._debug: + self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.") + self.params["output_level"] = "debug" + + if self.module._socket_path is None: + if self.params.get("use_ssl") is None: + self.params["use_ssl"] = True + if self.params.get("use_proxy") is None: + self.params["use_proxy"] = True + if self.params.get("validate_certs") is None: + self.params["validate_certs"] = True + + # Ensure protocol is set + self.params["protocol"] = "https" if self.params.get("use_ssl", True) else "http" + + # Set base_uri + if self.params.get("port") is not None: + self.base_only_uri = "{protocol}://{host}:{port}/".format(**self.params) + self.baseuri = "{0}api/v1/".format(self.base_only_uri) + else: + self.base_only_uri = "{protocol}://{host}/".format(**self.params) + self.baseuri = "{0}api/v1/".format(self.base_only_uri) + + if self.params.get("host") is None: + self.fail_json(msg="Parameter 'host' is required when not using the HTTP API connection plugin") + + if self.params.get("password"): + # Perform password-based authentication, log on using password + self.login() + else: + self.fail_json(msg="Parameter 'password' is required for authentication") + else: + self.connection = Connection(self.module._socket_path) + if self.connection.get_platform() == "cisco.nd": + self.platform = "nd" + + def get_login_domain_id(self, domain): + """Get a domain and return its id""" + if domain is None: + return domain + d = self.get_obj("auth/login-domains", key="domains", name=domain) + if not d: + self.fail_json(msg="Login domain '%s' is not a valid domain name." % domain) + if "id" not in d: + self.fail_json(msg="Login domain lookup failed for domain '%s': %s" % (domain, d)) + return d["id"] + + def login(self): + """Log in to MSO""" + + # Perform login request + if (self.params.get("login_domain") is not None) and (self.params.get("login_domain") != "Local"): + domain_id = self.get_login_domain_id(self.params.get("login_domain")) + payload = {"username": self.params.get("username", "admin"), "password": self.params.get("password"), "domainId": domain_id} + else: + payload = {"username": self.params.get("username", "admin"), "password": self.params.get("password")} + self.url = urljoin(self.baseuri, "auth/login") + resp, auth = fetch_url( + self.module, + self.url, + data=json.dumps(payload), + method="POST", + headers=self.headers, + timeout=self.params.get("timeout"), + use_proxy=self.params.get("use_proxy"), + ) + + # Handle MSO response + if auth.get("status") not in [200, 201]: + self.response = auth.get("msg") + self.status = auth.get("status") + self.fail_json(msg="Authentication failed: {msg}".format(**auth)) + + payload = json.loads(resp.read()) + + self.headers["Authorization"] = "Bearer {token}".format(**payload) + + def response_json(self, rawoutput): + """Handle MSO JSON response output""" + try: + self.jsondata = json.loads(rawoutput) + except Exception as e: + # Expose RAW output for troubleshooting + self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. %s" % e) + self.result["raw"] = rawoutput + return + + # Handle possible MSO error information + if self.status not in [200, 201, 202, 204]: + self.error = self.jsondata + + def request_download(self, path, destination=None, method="GET", api_version="v1"): + if self.platform != "nd": + self.url = urljoin(self.baseuri, path) + + redirected = False + redir_info = {} + redirect = {} + content = None + data = None + + src = self.params.get("src") + if src: + try: + self.headers.update({"Content-Length": os.stat(src).st_size}) + data = open(src, "rb") + except OSError: + self.fail_json(msg="Unable to open source file %s" % src, elapsed=0) + + kwargs = {} + if destination is not None and os.path.isdir(destination): + # first check if we are redirected to a file download + if self.platform == "nd": + redir_info = self.connection.get_remote_file_io_stream( + NDO_API_VERSION_PATH_FORMAT.format(api_version=api_version, path=path), self.module.tmpdir, method + ) + # In place of Content-Disposition, NDO get_remote_file_io_stream returns content-disposition. + content_disposition = redir_info.get("content-disposition") + else: + check, redir_info = fetch_url(self.module, self.url, headers=self.headers, method=method, timeout=self.params.get("timeout")) + content_disposition = check.headers.get("Content-Disposition") + + if content_disposition: + file_name = content_disposition.split("filename=")[1] + else: + self.fail_json(msg="Failed to fetch {0} backup information from MSO/NDO, response: {1}".format(self.params.get("backup"), redir_info)) + + # if we are redirected, update the url with the location header and update dest with the new url filename + if redir_info["status"] in (301, 302, 303, 307): + self.url = redir_info.get("location") + redirected = True + destination = os.path.join(destination, file_name) + + # if destination file already exist, only download if file newer + if os.path.exists(destination): + kwargs["last_mod_time"] = datetime.datetime.utcfromtimestamp(os.path.getmtime(destination)) + + if self.platform == "nd": + if redir_info["status"] == 200 and redirected is False: + info = redir_info + else: + info = self.connection.get_remote_file_io_stream("/mso/{0}".format(self.url.split("/mso/", 1)), self.module.tmpdir, method) + else: + resp, info = fetch_url( + self.module, + self.url, + data=data, + headers=self.headers, + method=method, + timeout=self.params.get("timeout"), + unix_socket=self.params.get("unix_socket"), + **kwargs + ) + + try: + content = resp.read() + except AttributeError: + # there was no content, but the error read() may have been stored in the info as 'body' + content = info.pop("body", "") + + if src: + # Try to close the open file handle + try: + data.close() + except Exception: + pass + + redirect["redirected"] = redirected or info.get("url") != self.url + redirect.update(redir_info) + redirect.update(info) + + write_file(self.module, self.url, destination, content, redirect, info.get("tmpsrc")) + + return redirect, destination + + def request_upload(self, path, fields=None, method="POST", api_version="v1"): + """Generic HTTP MultiPart POST method for MSO uploads.""" + self.path = path + if self.platform != "nd": + self.url = urljoin(self.baseuri, path) + + info = dict() + + if self.platform == "nd": + try: + if os.path.exists(self.params.get("backup")): + info = self.connection.send_file_request( + method, + NDO_API_VERSION_PATH_FORMAT.format(api_version=api_version, path=path), + file=self.params.get("backup"), + remote_path=self.params.get("remote_path"), + ) + else: + self.fail_json(msg="Upload failed due to: No such file or directory, Backup file: '{0}'".format(self.params.get("backup"))) + except Exception as error: + self.fail_json("NDO upload failed due to: {0}".format(error)) + else: + if not HAS_MULTIPART_ENCODER: + self.fail_json(msg="requests-toolbelt is required for the upload state of this module") + + mp_encoder = MultipartEncoder(fields=fields) + self.headers["Content-Type"] = mp_encoder.content_type + self.headers["Accept-Encoding"] = "gzip, deflate, br" + + resp, info = fetch_url( + self.module, + self.url, + headers=self.headers, + data=mp_encoder, + method=method, + timeout=self.params.get("timeout"), + use_proxy=self.params.get("use_proxy"), + ) + + self.response = info.get("msg") + self.status = info.get("status") + + # Get change status from HTTP headers + if "modified" in info: + self.has_modified = True + if info.get("modified") == "false": + self.result["changed"] = False + elif info.get("modified") == "true": + self.result["changed"] = True + + # 200: OK, 201: Created, 202: Accepted, 204: No Content + if self.status in (200, 201, 202, 204): + if self.platform == "nd": + return info + else: + output = resp.read() + if output: + return json.loads(output) + + # 400: Bad Request, 401: Unauthorized, 403: Forbidden, + # 405: Method Not Allowed, 406: Not Acceptable + # 500: Internal Server Error, 501: Not Implemented + elif self.status: + if self.status >= 400: + try: + if self.platform == "nd": + payload = info.get("body") + else: + payload = json.loads(resp.read()) + except (ValueError, AttributeError): + try: + payload = json.loads(info.get("body")) + except Exception: + self.fail_json(msg="MSO Error:", info=info) + if "code" in payload: + self.fail_json(msg="MSO Error {code}: {message}".format(**payload), info=info, payload=payload) + else: + self.fail_json(msg="MSO Error:".format(**payload), info=info, payload=payload) + else: + self.fail_json(msg="Backup file upload failed due to: {0}".format(info)) + return {} + + def request(self, path, method=None, data=None, qs=None, api_version="v1"): + """Generic HTTP method for MSO requests.""" + self.path = path + + if method is not None: + self.method = method + + # If we PATCH with empty operations, return + if method == "PATCH" and not data: + return {} + else: + self.patch_operation = data + + # if method in ['PATCH', 'PUT']: + # if qs is not None: + # qs['enableVersionCheck'] = 'true' + # else: + # qs = dict(enableVersionCheck='true') + + if method in ["PATCH"]: + if qs is not None: + qs["validate"] = "false" + else: + qs = dict(validate="false") + + resp = None + if self.module._socket_path: + self.connection.set_params(self.params) + if api_version is not None: + uri = NDO_API_VERSION_PATH_FORMAT.format(api_version=api_version, path=self.path) + else: + uri = self.path + + if qs is not None: + uri = uri + update_qs(qs) + + try: + info = self.connection.send_request(method, uri, json.dumps(data)) + self.url = info.get("url") + self.httpapi_logs.extend(self.connection.pop_messages()) + info.pop("date", None) + except Exception as e: + try: + error_obj = json.loads(to_text(e)) + except Exception: + error_obj = dict( + error=dict(code=-1, message="Unable to parse error output as JSON. Raw error message: {0}".format(e), exception=to_text(e)) + ) + pass + self.fail_json(msg=error_obj["error"]["message"]) + + else: + if api_version is not None: + self.url = "{0}api/{1}/{2}".format(self.base_only_uri, api_version, self.path.lstrip("/")) + else: + self.url = "{0}{1}".format(self.base_only_uri, self.path.lstrip("/")) + + if qs is not None: + self.url = self.url + update_qs(qs) + resp, info = fetch_url( + self.module, + self.url, + headers=self.headers, + data=json.dumps(data), + method=self.method, + timeout=self.params.get("timeout"), + use_proxy=self.params.get("use_proxy"), + ) + + self.response = info.get("msg") + self.status = info.get("status", -1) + + # Get change status from HTTP headers + if "modified" in info: + self.has_modified = True + if info.get("modified") == "false": + self.result["changed"] = False + elif info.get("modified") == "true": + self.result["changed"] = True + + # 200: OK, 201: Created, 202: Accepted + if self.status in (200, 201, 202): + try: + output = resp.read() + if output: + try: + return json.loads(output) + except Exception as e: + self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. {0}".format(e)) + self.result["raw"] = output + return + except AttributeError: + return info.get("body") + + # 204: No Content + elif self.status == 204: + return {} + + # 404: Not Found + elif self.method == "DELETE" and self.status == 404: + return {} + + # 400: Bad Request, 401: Unauthorized, 403: Forbidden, + # 405: Method Not Allowed, 406: Not Acceptable + # 500: Internal Server Error, 501: Not Implemented + elif self.status >= 400: + self.result["status"] = self.status + body = info.get("body") + if body is not None: + try: + if isinstance(body, dict): + payload = body + else: + payload = json.loads(body) + except Exception as e: + self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. %s" % e) + self.result["raw"] = body + self.fail_json(msg="MSO Error:", data=data, info=info) + self.error = payload + if "code" in payload: + self.fail_json(msg="MSO Error {code}: {message}".format(**payload), data=data, info=info, payload=payload) + else: + self.fail_json(msg="MSO Error:".format(**payload), data=data, info=info, payload=payload) + else: + # Connection error + msg = "Connection failed for {0}. {1}".format(info.get("url"), info.get("msg")) + self.error = msg + self.fail_json(msg=msg) + return {} + + def query_objs(self, path, key=None, api_version="v1", **kwargs): + """Query the MSO REST API for objects in a path""" + found = [] + objs = self.request(path, api_version=api_version, method="GET") + + if objs == {} or objs == []: + return found + + if key is None: + key = path + + if isinstance(objs, dict): + if key not in objs: + self.fail_json(msg="Key '{0}' missing from data".format(key), data=objs) + objs_list = objs.get(key) + else: + objs_list = objs + for obj in objs_list: + for kw_key, kw_value in kwargs.items(): + if kw_value is None: + continue + if isinstance(kw_value, dict): + obj_value = obj.get(kw_key) + if obj_value is not None and isinstance(obj_value, dict): + breakout = False + for kw_key_lvl2, kw_value_lvl2 in kw_value.items(): + if obj_value.get(kw_key_lvl2) != kw_value_lvl2: + breakout = True + break + if breakout: + break + else: + break + elif obj.get(kw_key) != kw_value: + break + else: + found.append(obj) + + return found + + def query_obj(self, path, api_version="v1", **kwargs): + """Query the MSO REST API for the whole object at a path""" + obj = self.request(path, api_version=api_version, method="GET") + if obj == {}: + return {} + for kw_key, kw_value in kwargs.items(): + if kw_value is None: + continue + if isinstance(kw_value, dict): + obj_value = obj.get(kw_key) + if obj_value is not None and isinstance(obj_value, dict): + for kw_key_lvl2, kw_value_lvl2 in kw_value.items(): + if obj_value.get(kw_key_lvl2) != kw_value_lvl2: + return {} + elif obj.get(kw_key) != kw_value: + return {} + return obj + + def get_obj(self, path, api_version="v1", **kwargs): + """Get a specific object from a set of MSO REST objects""" + objs = self.query_objs(path, api_version=api_version, **kwargs) + if len(objs) == 0: + return {} + if len(objs) > 1: + self.fail_json(msg="More than one object matches unique filter: {0}".format(kwargs)) + return objs[0] + + def lookup_schema(self, schema): + """Look up schema and return its id""" + if schema is None: + return schema + + schema_summary = self.query_objs("schemas/list-identity", key="schemas", displayName=schema) + if not schema_summary: + self.fail_json(msg="Provided schema '{0}' does not exist.".format(schema)) + schema_id = schema_summary[0].get("id") + if not schema_id: + self.fail_json(msg="Schema lookup failed for schema '{0}': '{1}'".format(schema, schema_id)) + return schema_id + + def lookup_domain(self, domain): + """Look up a domain and return its id""" + if domain is None: + return domain + + d = self.get_obj("auth/domains", key="domains", name=domain) + if not d: + self.fail_json(msg="Domain '%s' is not a valid domain name." % domain) + if "id" not in d: + self.fail_json(msg="Domain lookup failed for domain '%s': %s" % (domain, d)) + return d.get("id") + + def lookup_roles(self, roles): + """Look up roles and return their ids""" + if roles is None: + return roles + + ids = [] + for role in roles: + access_type = "readWrite" + try: + role = ast.literal_eval(role) + if type(role) is dict and "name" in role: + name = role.get("name") + if role.get("access_type") == "read": + access_type = "readOnly" + except ValueError: + name = role + + r = self.get_obj("roles", name=name) + if not r: + self.fail_json(msg="Role '%s' is not a valid role name." % name) + if "id" not in r: + self.fail_json(msg="Role lookup failed for role '%s': %s" % (name, r)) + ids.append(dict(roleId=r.get("id"), accessType=access_type)) + return ids + + def lookup_site(self, site): + """Look up a site and return its id""" + if site is None: + return site + + s = self.get_obj("sites", name=site) + if not s: + self.fail_json(msg="Site '%s' is not a valid site name." % site) + if "id" not in s: + self.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s)) + return s.get("id") + + def lookup_sites(self, sites): + """Look up sites and return their ids""" + if sites is None: + return sites + + ids = [] + for site in sites: + s = self.get_obj("sites", name=site) + if not s: + self.fail_json(msg="Site '%s' is not a valid site name." % site) + if "id" not in s: + self.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s)) + ids.append(dict(siteId=s.get("id"), securityDomains=[])) + return ids + + def lookup_tenant(self, tenant): + """Look up a tenant and return its id""" + if tenant is None: + return tenant + + t = self.get_obj("tenants", key="tenants", name=tenant) + if not t: + self.fail_json(msg="Tenant '%s' is not valid tenant name." % tenant) + if "id" not in t: + self.fail_json(msg="Tenant lookup failed for tenant '%s': %s" % (tenant, t)) + return t.get("id") + + def lookup_remote_location(self, remote_location): + """Look up a remote location and return its path and id""" + if remote_location is None: + return None + + remote = self.get_obj("platform/remote-locations", key="remoteLocations", name=remote_location) + if "id" not in remote: + self.fail_json(msg="No remote location found for remote '%s'" % (remote_location)) + remote_info = dict(id=remote.get("id"), path=remote.get("credential")["remotePath"]) + return remote_info + + def lookup_users(self, users): + """Look up users and return their ids""" + # Ensure tenant has at least admin user + if users is None: + users = ["admin"] + elif "admin" not in users: + users.append("admin") + + ids = [] + for user in users: + if self.platform == "nd": + u = self.get_obj("users", loginID=user, api_version="v2") + else: + u = self.get_obj("users", username=user) + if not u: + self.fail_json(msg="User '%s' is not a valid user name." % user) + if "id" not in u: + if "userID" not in u: + self.fail_json(msg="User lookup failed for user '%s': %s" % (user, u)) + id = dict(userId=u.get("userID")) + else: + id = dict(userId=u.get("id")) + if id in ids: + self.fail_json(msg="User '%s' is duplicate." % user) + ids.append(id) + + return ids + + def create_label(self, label, label_type): + """Create a new label""" + return self.request("labels", method="POST", data=dict(displayName=label, type=label_type)) + + def lookup_labels(self, labels, label_type): + """Look up labels and return their ids (create if necessary)""" + if labels is None: + return None + + ids = [] + for label in labels: + label_obj = self.get_obj("labels", displayName=label) + if not label_obj: + label_obj = self.create_label(label, label_type) + if "id" not in label_obj: + self.fail_json(msg="Label lookup failed for label '%s': %s" % (label, label_obj)) + ids.append(label_obj.get("id")) + return ids + + def anp_ref(self, **data): + """Create anpRef string""" + return "/schemas/{schema_id}/templates/{template}/anps/{anp}".format(**data) + + def epg_ref(self, **data): + """Create epgRef string""" + return "/schemas/{schema_id}/templates/{template}/anps/{anp}/epgs/{epg}".format(**data) + + def bd_ref(self, **data): + """Create bdRef string""" + return "/schemas/{schema_id}/templates/{template}/bds/{bd}".format(**data) + + def contract_ref(self, **data): + """Create contractRef string""" + # Support the contract argspec + if "name" in data: + data["contract"] = data.get("name") + return "/schemas/{schema_id}/templates/{template}/contracts/{contract}".format(**data) + + def filter_ref(self, **data): + """Create a filterRef string""" + return "/schemas/{schema_id}/templates/{template}/filters/{filter}".format(**data) + + def vrf_ref(self, **data): + """Create vrfRef string""" + return "/schemas/{schema_id}/templates/{template}/vrfs/{vrf}".format(**data) + + def l3out_ref(self, **data): + """Create l3outRef string""" + return "/schemas/{schema_id}/templates/{template}/l3outs/{l3out}".format(**data) + + def ext_epg_ref(self, **data): + """Create extEpgRef string""" + return "/schemas/{schema_id}/templates/{template}/externalEpgs/{external_epg}".format(**data) + + def service_graph_ref(self, **data): + """Create serviceGraphRef string""" + return "/schemas/{schema_id}/templates/{template}/serviceGraphs/{service_graph}".format(**data) + + def vrf_dict_from_ref(self, data): + vrf_ref_regex = re.compile(r"\/schemas\/(.*)\/templates\/(.*)\/vrfs\/(.*)") + vrf_dict = vrf_ref_regex.search(data) + return { + "vrfName": vrf_dict.group(3), + "schemaId": vrf_dict.group(1), + "templateName": vrf_dict.group(2), + } + + def dict_from_ref(self, data): + if data and data != "": + ref_regex = re.compile(r"\/schemas\/(.*)\/templates\/(.*?)\/(.*?)\/(.*)") + dic = ref_regex.search(data) + if dic is not None: + schema_id = dic.group(1) + template_name = dic.group(2) + category = dic.group(3) + name = dic.group(4) + uri_map = { + "vrfs": ["vrfName", "schemaId", "templateName"], + "bds": ["bdName", "schemaId", "templateName"], + "filters": ["filterName", "schemaId", "templateName"], + "contracts": ["contractName", "schemaId", "templateName"], + "l3outs": ["l3outName", "schemaId", "templateName"], + "anps": ["anpName", "schemaId", "templateName"], + "serviceGraphs": ["serviceGraphName", "schemaId", "templateName"], + "serviceNode": ["serviceNodeName", "schemaId", "templateName", "serviceGraphName"], + } + result = { + uri_map[category][1]: schema_id, + uri_map[category][2]: template_name, + } + + self.recursive_dict_from_ref_regex(name, result, uri_map[category][0]) + + return result + else: + self.fail_json(msg="There was no group in search: {data}".format(data=data)) + + def recursive_dict_from_ref_regex(self, data, result, category): + continued_ref_regex = re.compile(r"(.*?)\/([a-zA-Z]+.*)") + section_ref_regex = re.compile(r"([a-zA-Z]+)\/(.*)") + dic_name = continued_ref_regex.search(data) + if dic_name is not None: + result[category] = dic_name.group(1) + next_section = dic_name.group(2) + dic_next_section = section_ref_regex.search(next_section) + if dic_next_section is not None: + next_name = dic_next_section.group(2) + self.recursive_dict_from_ref_regex(next_name, result, dic_next_section.group(1).rstrip("s") + "Name") + else: + result[category] = data + + def recursive_dict_from_ref(self, data): + for key in data: + if key.endswith("Ref"): + data[key] = self.dict_from_ref(data.get(key)) + if isinstance(data[key], list): + for item in data[key]: + self.recursive_dict_from_ref(item) + return data + + def make_reference(self, data, reftype, schema_id, template): + """Create a reference from a dictionary""" + # Removes entry from payload + if data is None: + return None + + if data.get("schema") is not None: + schema_obj = self.get_obj("schemas", displayName=data.get("schema")) + if not schema_obj: + self.fail_json(msg="Referenced schema '{schema}' in {reftype}ref does not exist".format(reftype=reftype, **data)) + schema_id = schema_obj.get("id") + + if data.get("template") is not None: + template = data.get("template") + + refname = "%sName" % reftype + + return { + refname: data.get("name"), + "schemaId": schema_id, + "templateName": template, + } + + def make_subnets(self, data, is_bd_subnet=True): + """Create a subnets list from input""" + if data is None: + return None + + subnets = [] + for subnet in data: + if "subnet" in subnet: + subnet["ip"] = subnet.get("subnet") + if subnet.get("description") is None: + subnet["description"] = subnet.get("subnet") + subnet_payload = dict( + ip=subnet.get("ip"), + description=str(subnet.get("description")), + scope=subnet.get("scope"), + shared=subnet.get("shared"), + noDefaultGateway=subnet.get("no_default_gateway"), + ) + if is_bd_subnet: + subnet_payload.update(dict(querier=subnet.get("querier"), primary=subnet.get("primary"), virtual=subnet.get("virtual"))) + subnets.append(subnet_payload) + + return subnets + + def make_dhcp_label(self, data): + """Create a DHCP policy from input""" + if data is None: + return None + if type(data) == list: + dhcps = [] + for dhcp in data: + if "dhcp_option_policy" in dhcp: + dhcp["dhcpOptionLabel"] = dhcp.get("dhcp_option_policy") + del dhcp["dhcp_option_policy"] + dhcps.append(dhcp) + return dhcps + if "version" in data: + data["version"] = int(data.get("version")) + if data and "dhcp_option_policy" in data: + dhcp_option_policy = data.get("dhcp_option_policy") + if dhcp_option_policy is not None and "version" in dhcp_option_policy: + dhcp_option_policy["version"] = int(dhcp_option_policy.get("version")) + data["dhcpOptionLabel"] = dhcp_option_policy + del data["dhcp_option_policy"] + return data + + def sanitize(self, updates, collate=False, required=None, unwanted=None): + """Clean up unset keys from a request payload""" + if required is None: + required = [] + if unwanted is None: + unwanted = [] + self.proposed = deepcopy(self.existing) + self.sent = deepcopy(self.existing) + + if isinstance(self.existing, dict): + for key in self.existing: + # Remove References + if key.endswith("Ref"): + del self.proposed[key] + del self.sent[key] + continue + + # Removed unwanted keys + elif key in unwanted: + del self.proposed[key] + del self.sent[key] + continue + + if isinstance(updates, dict): + # Clean up self.sent + for key in updates: + # Always retain 'id' + if key in required: + if key in self.existing or updates.get(key) is not None: + self.sent[key] = updates.get(key) + continue + + # Remove unspecified values + elif not collate and updates.get(key) is None: + if key in self.existing: + del self.sent[key] + continue + + # Remove identical values + elif not collate and updates.get(key) == self.existing.get(key): + del self.sent[key] + continue + + # Add everything else + if updates.get(key) is not None: + self.sent[key] = updates.get(key) + + # Update self.proposed + self.proposed.update(self.sent) + + elif updates is not None: + self.sent = updates + # Update self.proposed + self.proposed = self.sent + + def delete_keys_from_dict(self, dict_to_sanitize, keys): + # TODO investigate combine this method above sanitize method + copy = deepcopy(dict_to_sanitize) + for ( + k, + v, + ) in copy.items(): + if k in keys: + del dict_to_sanitize[k] + elif isinstance(v, dict): + dict_to_sanitize[k] = self.delete_keys_from_dict(v, keys) + elif isinstance(v, list): + for index, item in enumerate(v): + if isinstance(item, dict): + dict_to_sanitize[k][index] = self.delete_keys_from_dict(item, keys) + return dict_to_sanitize + + def exit_json(self, **kwargs): + """Custom written method to exit from module.""" + + if self.params.get("state") in ("absent", "present", "upload", "restore", "download", "move", "clone"): + if self.params.get("output_level") in ("debug", "info"): + self.result["previous"] = self.previous + # FIXME: Modified header only works for PATCH + if not self.has_modified and self.previous != self.existing: + self.result["changed"] = True + if self.stdout: + self.result["stdout"] = self.stdout + + # Return the gory details when we need it + if self.params.get("output_level") == "debug": + self.result["method"] = self.method + self.result["response"] = self.response + self.result["status"] = self.status + self.result["url"] = self.url + self.result["httpapi_logs"] = self.httpapi_logs + self.result["socket"] = self.module._socket_path + + if self.params.get("state") in ("absent", "present"): + self.result["sent"] = self.sent + self.result["proposed"] = self.proposed + + if self.method == "PATCH": + self.result["patch_operation"] = self.patch_operation + + self.result["current"] = self.existing + + if self.module._diff and self.result.get("changed") is True: + self.result["diff"] = dict( + before=self.previous, + after=self.existing, + ) + + self.result.update(**kwargs) + self.module.exit_json(**self.result) + + def fail_json(self, msg, **kwargs): + """Custom written method to return info on failure.""" + + if self.params.get("state") in ("absent", "present"): + if self.params.get("output_level") in ("debug", "info"): + self.result["previous"] = self.previous + # FIXME: Modified header only works for PATCH + if not self.has_modified and self.previous != self.existing: + self.result["changed"] = True + if self.stdout: + self.result["stdout"] = self.stdout + + # Return the gory details when we need it + if self.params.get("output_level") == "debug": + if self.url is not None: + self.result["method"] = self.method + self.result["response"] = self.response + self.result["status"] = self.status + self.result["url"] = self.url + self.result["httpapi_logs"] = self.httpapi_logs + self.result["socket"] = self.module._socket_path + + if self.params.get("state") in ("absent", "present"): + self.result["sent"] = self.sent + self.result["proposed"] = self.proposed + + if self.method == "PATCH": + self.result["patch_operation"] = self.patch_operation + + self.result["current"] = self.existing + + self.result.update(**kwargs) + self.module.fail_json(msg=msg, **self.result) + + def check_changed(self): + """Check if changed by comparing new values from existing""" + existing = self.existing + if "password" in existing: + existing["password"] = self.sent.get("password") + + existing = self.remove_keys_from_dict_when_value_empty(existing) + self.stdout = json.dumps(existing) + + return not issubset(self.sent, existing) + + def update_service_graph_obj(self, service_graph_obj): + """update filter with more information""" + service_graph_obj["serviceGraphRef"] = self.dict_from_ref(service_graph_obj.get("serviceGraphRef")) + for service_node in service_graph_obj["serviceNodesRelationship"]: + service_node.get("consumerConnector")["bdRef"] = self.dict_from_ref(service_node.get("consumerConnector").get("bdRef")) + service_node.get("providerConnector")["bdRef"] = self.dict_from_ref(service_node.get("providerConnector").get("bdRef")) + service_node["serviceNodeRef"] = self.dict_from_ref(service_node.get("serviceNodeRef")) + if service_graph_obj.get("serviceGraphContractRelationRef"): + del service_graph_obj["serviceGraphContractRelationRef"] + + def update_filter_obj(self, contract_obj, filter_obj, filter_type, contract_display_name=None, update_filter_ref=True): + """update filter with more information""" + if update_filter_ref: + filter_obj["filterRef"] = self.dict_from_ref(filter_obj.get("filterRef")) + if contract_display_name: + filter_obj["displayName"] = contract_display_name + else: + filter_obj["displayName"] = contract_obj.get("displayName") + filter_obj["filterType"] = filter_type + filter_obj["contractScope"] = contract_obj.get("scope") + filter_obj["contractFilterType"] = contract_obj.get("filterType") + # Conditional statement 'description == ""' is needed to set empty string. + if contract_obj.get("description") or contract_obj.get("description") == "": + filter_obj["description"] = contract_obj.get("description") + # Conditional statement is needed to determine if "prio" exist in contract object. + # Same reason as described mso_schema_template_contract_filter.py. + if contract_obj.get("prio"): + filter_obj["prio"] = contract_obj.get("prio") + + def query_schema(self, schema): + schema_id = self.lookup_schema(schema) + schema_path = "schemas/{0}".format(schema_id) + schema_obj = self.query_obj(schema_path, displayName=schema) + if not schema_obj: + self.module.fail_json(msg="Schema '{0}' is not a valid schema name.".format(schema)) + return schema_id, schema_path, schema_obj + + def query_service_node_types(self): + node_objs = self.query_objs("schemas/service-node-types", key="serviceNodeTypes") + if not node_objs: + self.module.fail_json(msg="Service node types do not exist") + return node_objs + + def lookup_service_node_device(self, site_id, tenant, device_name=None, service_node_type=None): + if service_node_type is None: + node_devices = self.query_objs("sites/{0}/aci/tenants/{1}/devices".format(site_id, tenant), key="devices") + else: + node_devices = self.query_objs("sites/{0}/aci/tenants/{1}/devices?deviceType={2}".format(site_id, tenant, service_node_type), key="devices") + if device_name is not None: + for device in node_devices: + if device_name == device.get("name"): + return device + self.module.fail_json(msg="Provided device '{0}' of type '{1}' does not exist.".format(device_name, service_node_type)) + return node_devices + + # Workaround function due to inconsistency in attributes REQUEST/RESPONSE API + # Fix for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing + def find_dicts_with_target_key(self, target_dict, target, replace, result=None): + if result is None: + result = [] + + for key, value in target_dict.items(): + if key == target: + result.append(target_dict) + if isinstance(value, dict): + self.find_dicts_with_target_key(value, target, replace, result) + if isinstance(value, list): + for entry in value: + if isinstance(entry, dict): + self.find_dicts_with_target_key(entry, target, replace, result) + + return result + + # Workaround function due to inconsistency in attributes REQUEST/RESPONSE API + # Fix for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing + def replace_keys_in_dict(self, target, replace, target_dict=None): + if target_dict is None: + target_dict = self.existing + + key_list = self.find_dicts_with_target_key(target_dict, target, replace) + for item in key_list: + item[replace] = item.get(target) + del item[target] + + # Workaround function to remove null/None fields returned by API RESPONSE + def remove_keys_from_dict_when_value_empty(self, target_dict, modified_target=None): + if modified_target is None: + modified_target = deepcopy(target_dict) + + for key, value in target_dict.items(): + if value is None: + del modified_target[key] + elif isinstance(value, dict): + self.remove_keys_from_dict_when_value_empty(value, modified_target[key]) + elif isinstance(value, list): + for entry_index, entry in enumerate(value): + if isinstance(entry, dict): + self.remove_keys_from_dict_when_value_empty(entry, modified_target[key][entry_index]) + + return modified_target + + def validate_schema(self, schema_id): + return self.request("schemas/{id}/validate".format(id=schema_id), method="GET") diff --git a/ansible_collections/cisco/mso/plugins/module_utils/schema.py b/ansible_collections/cisco/mso/plugins/module_utils/schema.py new file mode 100644 index 000000000..ca08ab10b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/module_utils/schema.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from collections import namedtuple + +KVPair = namedtuple("KVPair", "key value") +Item = namedtuple("Item", "index details") + + +class MSOSchema: + def __init__(self, mso_module, schema_name, template_name=None, site_name=None): + self.mso = mso_module + self.schema_name = schema_name + self.id, self.path, self.schema = mso_module.query_schema(schema_name) + self.schema_objects = {} + if template_name: + self.set_template(template_name) + if site_name and template_name: + self.set_site(template_name, site_name) + + @staticmethod + def get_object_from_list(search_list, kv_list): + """ + Get the first matched object from a list of mso object dictionaries. + :param search_list: Objects to search through -> List. + :param kv_list: Key/value pairs that should match in the object. -> List[KVPair(Str, Str)] + :return: The index and details of the object. -> Item (Named Tuple) + Values of provided keys of all existing objects. -> List + """ + + def kv_match(kvs, item): + return all((item.get(kv.key) == kv.value for kv in kvs)) + + match = next((Item(index, item) for index, item in enumerate(search_list) if kv_match(kv_list, item)), None) + existing = [item.get(kv.key) for item in search_list for kv in kv_list] + return match, existing + + def validate_schema_objects_present(self, required_schema_objects): + """ + Validate that attributes are set to a value that is not equal None. + :param required_schema_objects: List of schema objects to verify. -> List + :return: None + """ + for schema_object in required_schema_objects: + if schema_object not in self.schema_objects.keys(): + msg = "Required attribute '{0}' is not specified on schema instance with name {1}".format(schema_object, self.schema_name) + self.mso.fail_json(msg=msg) + + def set_template(self, template_name, fail_module=True): + """ + Get template item that matches the name of a template. + :param template_name: Name of the template to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template item. -> Item(Int, Dict) | None + """ + + kv_list = [KVPair("name", template_name)] + match, existing = self.get_object_from_list(self.schema.get("templates"), kv_list) + if not match and fail_module: + msg = "Provided template '{0}' not matching existing template(s): {1}".format(template_name, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["template"] = match + + def set_template_bd(self, bd, fail_module=True): + """ + Get template bridge domain item that matches the name of a bd. + :param bd: Name of the bd to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template bd item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template"]) + kv_list = [KVPair("name", bd)] + match, existing = self.get_object_from_list(self.schema_objects["template"].details.get("bds"), kv_list) + if not match and fail_module: + msg = "Provided BD '{0}' not matching existing bd(s): {1}".format(bd, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["template_bd"] = match + + def set_template_anp(self, anp, fail_module=True): + """ + Get template application profile item that matches the name of an anp. + :param anp: Name of the anp to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template anp item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template"]) + kv_list = [KVPair("name", anp)] + match, existing = self.get_object_from_list(self.schema_objects["template"].details.get("anps"), kv_list) + if not match and fail_module: + msg = "Provided ANP '{0}' not matching existing anp(s): {1}".format(anp, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["template_anp"] = match + + def set_template_anp_epg(self, epg, fail_module=True): + """ + Get template endpoint group item that matches the name of an epg. + :param epg: Name of the epg to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template epg item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template_anp"]) + kv_list = [KVPair("name", epg)] + match, existing = self.get_object_from_list(self.schema_objects["template_anp"].details.get("epgs"), kv_list) + if not match and fail_module: + msg = "Provided EPG '{0}' not matching existing epg(s): {1}".format(epg, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["template_anp_epg"] = match + + def set_template_external_epg(self, external_epg, fail_module=True): + """ + Get template external epg item that matches the name of an anp. + :param anp: Name of the anp to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template anp item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template"]) + kv_list = [KVPair("name", external_epg)] + match, existing = self.get_object_from_list(self.schema_objects["template"].details.get("externalEpgs"), kv_list) + if not match and fail_module: + msg = "Provided External EPG '{0}' not matching existing external_epg(s): {1}".format(external_epg, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["template_external_epg"] = match + + def set_site(self, template_name, site_name, fail_module=True): + """ + Get site item that matches the name of a site. + :param template_name: Name of the template to match. -> Str + :param site_name: Name of the site to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site item. -> Item(Int, Dict) | None + """ + if not self.schema.get("sites"): + msg = "No sites associated with schema '{0}'. Associate the site with the schema using (M) mso_schema_site.".format(self.schema_name) + self.mso.fail_json(msg=msg) + + kv_list = [KVPair("siteId", self.mso.lookup_site(site_name)), KVPair("templateName", template_name)] + match, existing = self.get_object_from_list(self.schema.get("sites"), kv_list) + if not match and fail_module: + msg = "Provided site '{0}' not associated with template '{1}'. Site is currently associated with template(s): {2}".format( + site_name, template_name, ", ".join(existing[1::2]) + ) + self.mso.fail_json(msg=msg) + self.schema_objects["site"] = match + + def set_site_bd(self, bd_name, fail_module=True): + """ + Get site bridge domain item that matches the name of a bd. + :param bd_name: Name of the bd to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site bd item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template", "site"]) + kv_list = [KVPair("bdRef", self.mso.bd_ref(schema_id=self.id, template=self.schema_objects["template"].details.get("name"), bd=bd_name))] + match, existing = self.get_object_from_list(self.schema_objects["site"].details.get("bds"), kv_list) + if not match and fail_module: + msg = "Provided BD '{0}' not matching existing site bd(s): {1}".format(bd_name, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_bd"] = match + + def set_site_bd_subnet(self, subnet, fail_module=True): + """ + Get site bridge domain subnet item that matches the ip of a subnet. + :param subnet: Subnet (ip) to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site bd subnet item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["site_bd"]) + kv_list = [KVPair("ip", subnet)] + match, existing = self.get_object_from_list(self.schema_objects["site_bd"].details.get("subnets"), kv_list) + if not match and fail_module: + msg = "Provided subnet '{0}' not matching existing site bd subnet(s): {1}".format(subnet, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_bd_subnet"] = match + + def set_site_anp(self, anp_name, fail_module=True): + """ + Get site application profile item that matches the name of a anp. + :param anp_name: Name of the anp to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site anp item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template_anp", "site"]) + kv_list = [KVPair("anpRef", self.schema_objects["template_anp"].details.get("anpRef"))] + match, existing = self.get_object_from_list(self.schema_objects["site"].details.get("anps"), kv_list) + if not match and fail_module: + msg = "Provided ANP '{0}' not matching existing site anp(s): {1}".format(anp_name, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_anp"] = match + + def set_site_anp_epg(self, epg_name, fail_module=True): + """ + Get site anp epg item that matches the epgs. + :param epg: epg to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site anp epg item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["site_anp", "template_anp_epg"]) + kv_list = [KVPair("epgRef", self.schema_objects["template_anp_epg"].details.get("epgRef"))] + match, existing = self.get_object_from_list(self.schema_objects["site_anp"].details.get("epgs"), kv_list) + if not match and fail_module: + msg = "Provided EPG '{0}' not matching existing site anp epg(s): {1}".format(epg_name, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_anp_epg"] = match diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_backup.py b/ansible_collections/cisco/mso/plugins/modules/mso_backup.py new file mode 100644 index 000000000..fc2564b82 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_backup.py @@ -0,0 +1,331 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# Copyright: (c) 2023, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2023, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_backup +short_description: Manages backups +description: +- Manage backups on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +- Lionel Hercot (@lhercot) +- Sabari Jaganathan (@sajagana) +options: + location_type: + description: + - The type of location for the backup to be stored + type: str + choices: [ local, remote] + default: local + backup: + description: + - The name given to the backup + - C(backup) is mutually exclusive with C(backup_id). Only use one of the two. + type: str + aliases: [ name ] + backup_id: + description: + - The id of a specific backup + - C(backup_id) is mutually exclusive with C(backup). Only use one of the two. + type: str + aliases: [ id ] + remote_location: + description: + - The remote location's name where the backup should be stored + type: str + remote_path: + description: + - This path is relative to the remote location. + - A '/' is automatically added between the remote location folder and this path. + - This folder structure should already exist on the remote location. + type: str + description: + description: + - Brief information about the backup. + type: str + destination: + description: + - Location where to download the backup to + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + - Use C(upload) for uploading backup. + - Use C(restore) for restoring backup. + - Use C(download) for downloading backup. + - Use C(move) for moving backup from local to remote location. + type: str + choices: [ absent, present, query, upload, restore, download, move ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Create a new local backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + description: via Ansible + location_type: local + state: present + delegate_to: localhost + +- name: Create a new remote backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + description: via Ansible + location_type: remote + remote_location: ansible_test + state: present + delegate_to: localhost + +- name: Move backup to remote location + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup0 + remote_location: ansible_test + remote_path: test + state: move + delegate_to: localhost + +- name: Download a backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + destination: ./ + state: download + delegate_to: localhost + +- name: Upload a backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: ./Backup + state: upload + delegate_to: localhost + +- name: Restore a backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + state: restore + delegate_to: localhost + +- name: Remove a Backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + state: absent + delegate_to: localhost + +- name: Query a backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + state: query + delegate_to: localhost + register: query_result + +- name: Query a backup with its complete name + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup_20200721220043 + state: query + delegate_to: localhost + register: query_result + +- name: Query all backups + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +import os + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + location_type=dict(type="str", default="local", choices=["local", "remote"]), + description=dict(type="str"), + backup=dict(type="str", aliases=["name"]), + backup_id=dict(type="str", aliases=["id"]), + remote_location=dict(type="str"), + remote_path=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query", "upload", "restore", "download", "move"]), + destination=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["location_type", "remote", ["remote_location"]], + ["state", "absent", ["backup", "backup_id"], True], + ["state", "present", ["backup"]], + ["state", "upload", ["backup", "backup_id"], True], + ["state", "restore", ["backup", "backup_id"], True], + ["state", "download", ["backup", "backup_id"], True], + ["state", "download", ["destination"]], + ["state", "move", ["backup", "backup_id"], True], + ["state", "move", ["remote_location", "remote_path"]], + ], + mutually_exclusive=[ + ("backup", "backup_id"), + ], + ) + + description = module.params.get("description") + location_type = module.params.get("location_type") + state = module.params.get("state") + backup = module.params.get("backup") + backup_id = module.params.get("backup_id") + remote_location = module.params.get("remote_location") + remote_path = module.params.get("remote_path") + destination = module.params.get("destination") + + mso = MSOModule(module) + + backup_names = [] + mso.existing = mso.query_objs("backups/backupRecords", key="backupRecords") + if backup or backup_id: + if mso.existing: + data = mso.existing + mso.existing = [] + for backup_info in data: + if (backup_id and backup_id == backup_info.get("id")) or ( + backup and (backup == backup_info.get("name").split("_")[0] or backup == backup_info.get("name")) + ): + mso.existing.append(backup_info) + backup_names.append(backup_info.get("name")) + + if state == "query": + mso.exit_json() + + elif state == "absent": + mso.previous = mso.existing + if len(mso.existing) > 1: + mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(", ".join(backup_names))) + elif len(mso.existing) == 1: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request("backups/backupRecords/{id}".format(id=mso.existing[0].get("id")), method="DELETE") + mso.exit_json() + + elif state == "present": + mso.previous = mso.existing + + payload = dict(name=backup, description=description, locationType=location_type) + + if location_type == "remote": + remote_location_info = mso.lookup_remote_location(remote_location) + payload.update(remoteLocationId=remote_location_info.get("id")) + if remote_path: + remote_path = "{0}/{1}".format(remote_location_info.get("path"), remote_path) + payload.update(remotePath=remote_path) + + mso.proposed = payload + + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request("backups", method="POST", data=payload) + mso.exit_json() + + elif state == "upload": + mso.previous = mso.existing + + if module.check_mode: + mso.existing = mso.proposed + else: + try: + request_url = "backups/upload" + payload = dict() + if mso.platform == "nd": + if remote_location is None or remote_path is None: + mso.module.fail_json(msg="NDO backup upload failed: remote_location and remote_path are required for NDO backup upload") + remote_location_info = mso.lookup_remote_location(remote_location) + request_url = "backups/remoteUpload/{0}".format(remote_location_info.get("id")) + else: + payload = dict(name=(os.path.basename(backup), open(backup, "rb"), "application/x-gzip")) + + mso.existing = mso.request_upload(request_url, fields=payload) + except Exception as error: + mso.module.fail_json(msg="Upload failed due to: {0}, Backup file: '{1}'".format(error, ", ".join(backup.split("/")[-1:]))) + mso.exit_json() + + if len(mso.existing) == 0: + mso.module.fail_json(msg="Backup '{0}' does not exist".format(backup)) + elif len(mso.existing) > 1: + mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(", ".join(backup_names))) + + elif state == "restore": + mso.previous = mso.existing + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request("backups/{id}/restore".format(id=mso.existing[0].get("id")), method="PUT") + + elif state == "download": + mso.previous = mso.existing + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request_download("backups/{id}/download".format(id=mso.existing[0].get("id")), destination=destination) + + elif state == "move": + mso.previous = mso.existing + remote_location_info = mso.lookup_remote_location(remote_location) + remote_path = "{0}/{1}".format(remote_location_info.get("path"), remote_path) + payload = dict(remoteLocationId=remote_location_info.get("id"), remotePath=remote_path, backupRecordId=mso.existing[0].get("id")) + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request("backups/remote-location", method="POST", data=payload) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py b/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py new file mode 100644 index 000000000..e97b59b2e --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_backup_schedule +short_description: Manages backup schedules +description: +- Manage backup schedules on Cisco ACI Multi-Site. +author: +- Akini Ross (@akinross) +options: + start_date: + description: + - The date to start the scheduler in format YYYY-MM-DD + - If no date is provided, the current date will be used. + type: str + start_time: + description: + - The time to start the scheduler in format HH:MM:SS + - If no time is provided, midnight "00:00:00" will be used. + type: str + frequency_unit: + description: + - The interval unit type + choices: [ hours, days ] + type: str + frequency_length: + description: + - Amount of hours or days for the schedule trigger frequency + type: int + remote_location: + description: + - The remote location's name where the backup should be stored + type: str + remote_path: + description: + - This path is relative to the remote location. + - A '/' is automatically added between the remote location folder and this path. + - This folder structure should already exist on the remote location. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Get current backup schedule + cisco.mso.mso_backup_schedule: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Set backup schedule + cisco.mso.mso_backup_schedule: + host: mso_host + username: admin + password: SomeSecretPassword + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + delegate_to: localhost + +- name: Set backup schedule with date and time + cisco.mso.mso_backup_schedule: + host: mso_host + username: admin + password: SomeSecretPassword + frequency_unit: days + frequency_length: 1 + remote_location: ansible_test + remote_path: test + start_time: 20:57:36 + start_date: 2023-04-09 + state: present + delegate_to: localhost + +- name: Delete backup schedule + cisco.mso.mso_backup_schedule: + host: mso_host + username: admin + password: SomeSecretPassword + state: absent + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from datetime import datetime, tzinfo, timedelta + +# UTC Timezone implementation as datetime.timezone is not supported in Python 2.7 + + +class UTC(tzinfo): + """UTC""" + + def utcoffset(self, dt): + return timedelta(0) + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return timedelta(0) + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + start_date=dict(type="str"), + start_time=dict(type="str"), + frequency_unit=dict(type="str", choices=["hours", "days"]), + frequency_length=dict(type="int"), + remote_location=dict(type="str"), + remote_path=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True, required_if=[["state", "present", ["frequency_unit", "frequency_length", "remote_location"]]] + ) + + start_date = module.params.get("start_date") + start_time = module.params.get("start_time") + frequency_unit = module.params.get("frequency_unit") + frequency_length = module.params.get("frequency_length") + remote_location = module.params.get("remote_location") + remote_path = module.params.get("remote_path") + state = module.params.get("state") + + mso = MSOModule(module) + api_path = "backups/schedule" + mso.existing = mso.request(api_path, method="GET") + + if state == "absent": + mso.previous = mso.existing + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(api_path, method="DELETE") + + elif state == "present": + mso.previous = mso.existing + + remote_location_info = mso.lookup_remote_location(remote_location) + + if start_date: + try: + y, m, d = start_date.split("-") + year = int(y) + month = int(m) + day = int(d) + except Exception as e: + module.fail_json(msg="Failed to parse date format 'YYYY-MM-DD' %s, %s" % (start_date, e)) + else: + current_date = datetime.now(UTC()).date() + year = current_date.year + month = current_date.month + day = current_date.day + + if start_time: + try: + h, m, s = start_time.split(":") + hours = int(h) + minutes = int(m) + seconds = int(s) + except Exception as e: + module.fail_json(msg="Failed to parse time format 'HH:MM:SS' %s, %s" % (start_time, e)) + else: + hours = minutes = seconds = 0 + + try: + set_date = datetime(year, month, day, hours, minutes, seconds) + except Exception as e: + module.fail_json(msg="Failed to create datetime object with date '%s', and time '%s'. Error: %s" % (start_date, start_time, e)) + + payload = dict( + startDate="{0}.000Z".format(set_date.isoformat()), + intervalTimeUnit=frequency_unit.upper(), + intervalLength=frequency_length, + remoteLocationId=remote_location_info.get("id"), + locationType="remote", + ) + + if remote_path: + payload.update(remotePath="{0}/{1}".format(remote_location_info.get("path"), remote_path)) + + mso.proposed = payload + + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(api_path, method="POST", data=payload) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py new file mode 100644 index 000000000..e9b7f23d3 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy module) +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_dhcp_option_policy +short_description: Manage DHCP Option policies. +description: +- Manage DHCP Option policies on Cisco Multi-Site Orchestrator. +author: +- Lionel Hercot (@lhercot) +options: + dhcp_option_policy: + description: + - Name of the DHCP Option Policy + type: str + aliases: [ name ] + description: + description: + - Description of the DHCP Option Policy + type: str + tenant: + description: + - Tenant where the DHCP Option Policy is located. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new DHCP Option Policy + cisco.mso.mso_dhcp_option_policy: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + description: "My Test DHCP Policy" + tenant: ansible_test + state: present + delegate_to: localhost + +- name: Remove DHCP Option Policy + cisco.mso.mso_dhcp_option_policy: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + state: absent + delegate_to: localhost + +- name: Query a DHCP Option Policy + cisco.mso.mso_dhcp_option_policy: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + state: query + delegate_to: localhost + +- name: Query all DHCP Option Policies + cisco.mso.mso_dhcp_option_policy: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + dhcp_option_policy=dict(type="str", aliases=["name"]), + description=dict(type="str"), + tenant=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["dhcp_option_policy"]], + ["state", "present", ["dhcp_option_policy", "tenant"]], + ], + ) + + dhcp_option_policy = module.params.get("dhcp_option_policy") + description = module.params.get("description") + tenant = module.params.get("tenant") + state = module.params.get("state") + + mso = MSOModule(module) + + path = "policies/dhcp/option" + + # Query for existing object(s) + if dhcp_option_policy: + mso.existing = mso.get_obj(path, name=dhcp_option_policy, key="DhcpRelayPolicies") + if mso.existing: + policy_id = mso.existing.get("id") + # If we found an existing object, continue with it + path = "{0}/{1}".format(path, policy_id) + else: + mso.existing = mso.query_objs(path, key="DhcpRelayPolicies") + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method="DELETE", data=mso.sent) + + elif state == "present": + tenant_id = mso.lookup_tenant(tenant) + payload = dict( + name=dhcp_option_policy, + desc=description, + policyType="dhcp", + policySubtype="option", + tenantId=tenant_id, + ) + mso.sanitize(payload, collate=True) + + if mso.existing: + if mso.check_changed(): + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent) + else: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py new file mode 100644 index 000000000..f4c397c24 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy module) +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_dhcp_option_policy_option +short_description: Manage DHCP options in a DHCP Option policy. +description: +- Manage DHCP options in a DHCP Option policy on Cisco Multi-Site Orchestrator. +author: +- Lionel Hercot (@lhercot) +options: + dhcp_option_policy: + description: + - Name of the DHCP Option Policy + type: str + required: true + aliases: [ name ] + name: + description: + - Name of the option in the DHCP Option Policy + type: str + aliases: [ option ] + id: + description: + - Id of the option in the DHCP Option Policy + type: int + data: + description: + - Data of the DHCP option in the DHCP Option Policy + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new option to a DHCP Option Policy + cisco.mso.mso_dhcp_option_policy_option: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + name: ansible_test + id: 1 + data: Data stored in the option + state: present + delegate_to: localhost + +- name: Remove a option to a DHCP Option Policy + cisco.mso.mso_dhcp_option_policy_option: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + name: ansible_test + state: absent + delegate_to: localhost + +- name: Query a option to a DHCP Option Policy + cisco.mso.mso_dhcp_option_policy_option: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + name: ansible_test + state: query + delegate_to: localhost + +- name: Query all option of a DHCP Option Policy + cisco.mso.mso_dhcp_option_policy_option: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_option_policy: my_test_dhcp_policy + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import ( + MSOModule, + mso_argument_spec, +) + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + dhcp_option_policy=dict(type="str", required=True), + name=dict(type="str", aliases=["option"]), + id=dict(type="int"), + data=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["name", "id", "data"]], + ["state", "absent", ["name"]], + ], + ) + + dhcp_option_policy = module.params.get("dhcp_option_policy") + option_id = module.params.get("id") + name = module.params.get("name") + data = module.params.get("data") + state = module.params.get("state") + + mso = MSOModule(module) + + path = "policies/dhcp/option" + + option_index = None + previous_option = {} + + # Query for existing object(s) + dhcp_option_obj = mso.get_obj(path, name=dhcp_option_policy, key="DhcpRelayPolicies") + if "id" not in dhcp_option_obj: + mso.fail_json(msg="DHCP Option Policy '{0}' is not a valid DHCP Option Policy name.".format(dhcp_option_policy)) + policy_id = dhcp_option_obj.get("id") + options = [] + if "dhcpOption" in dhcp_option_obj: + options = dhcp_option_obj.get("dhcpOption") + for index, opt in enumerate(options): + if opt.get("name") == name: + previous_option = opt + option_index = index + + # If we found an existing object, continue with it + path = "{0}/{1}".format(path, policy_id) + + if state == "query": + mso.existing = options + if name is not None: + mso.existing = previous_option + mso.exit_json() + + mso.previous = previous_option + if state == "absent": + option = {} + if previous_option and option_index is not None: + options.pop(option_index) + + elif state == "present": + option = dict( + id=str(option_id), + name=name, + data=data, + ) + if option_index is not None: + options[option_index] = option + else: + options.append(option) + + if module.check_mode: + mso.existing = option + else: + mso.existing = dhcp_option_obj + dhcp_option_obj["dhcpOption"] = options + mso.sanitize(dhcp_option_obj, collate=True) + new_dhcp_option_obj = mso.request(path, method="PUT", data=mso.sent) + mso.existing = {} + for index, opt in enumerate(new_dhcp_option_obj.get("dhcpOption")): + if opt.get("name") == name: + mso.existing = opt + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py new file mode 100644 index 000000000..394825feb --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Jorge Gomez Velasquez <jgomezve@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_dhcp_relay_policy +short_description: Manage DHCP Relay policies. +description: +- Manage DHCP Relay policies on Cisco Multi-Site Orchestrator. +author: +- Jorge Gomez (@jorgegome2307) +options: + dhcp_relay_policy: + description: + - Name of the DHCP Relay Policy + type: str + aliases: [ name ] + description: + description: + - Description of the DHCP Relay Policy + type: str + tenant: + description: + - Tenant where the DHCP Relay Policy is located. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + description: "My Test DHCP Policy" + tenant: ansible_test + state: present + delegate_to: localhost + +- name: Remove DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + state: absent + delegate_to: localhost + +- name: Query a DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + state: query + delegate_to: localhost + +- name: Query all DHCP Relay Policies + cisco.mso.mso_dhcp_relay_policy: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + dhcp_relay_policy=dict(type="str", aliases=["name"]), + description=dict(type="str"), + tenant=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["dhcp_relay_policy"]], + ["state", "present", ["dhcp_relay_policy", "tenant"]], + ], + ) + + dhcp_relay_policy = module.params.get("dhcp_relay_policy") + description = module.params.get("description") + tenant = module.params.get("tenant") + state = module.params.get("state") + + mso = MSOModule(module) + + path = "policies/dhcp/relay" + + # Query for existing object(s) + if dhcp_relay_policy: + mso.existing = mso.get_obj(path, name=dhcp_relay_policy, key="DhcpRelayPolicies") + if mso.existing: + policy_id = mso.existing.get("id") + # If we found an existing object, continue with it + path = "{0}/{1}".format(path, policy_id) + else: + mso.existing = mso.query_objs(path, key="DhcpRelayPolicies") + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method="DELETE", data=mso.sent) + + elif state == "present": + tenant_id = mso.lookup_tenant(tenant) + payload = dict( + name=dhcp_relay_policy, + desc=description, + policyType="dhcp", + policySubtype="relay", + tenantId=tenant_id, + ) + mso.sanitize(payload, collate=True) + + if mso.existing: + if mso.check_changed(): + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent) + else: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py new file mode 100644 index 000000000..760d90430 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py @@ -0,0 +1,256 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Jorge Gomez Velasquez <jgomezve@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_dhcp_relay_policy_provider +short_description: Manage DHCP providers in a DHCP Relay policy. +description: +- Manage DHCP providers in a DHCP Relay policy on Cisco Multi-Site Orchestrator. +author: +- Jorge Gomez (@jorgegome2307) +options: + dhcp_relay_policy: + description: + - Name of the DHCP Relay Policy + type: str + required: true + aliases: [ name ] + ip: + description: + - IP address of the DHCP Server + type: str + tenant: + description: + - Tenant where the DHCP provider is located. + type: str + schema: + description: + - Schema where the DHCP provider is configured + type: str + template: + description: + - template where the DHCP provider is configured + type: str + application_profile: + description: + - Application Profile where the DHCP provider is configured + type: str + aliases: [ anp ] + endpoint_group: + description: + - EPG where the DHCP provider is configured + type: str + aliases: [ epg ] + external_endpoint_group: + description: + - External EPG where the DHCP provider is configured + type: str + aliases: [ ext_epg, external_epg ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new provider to a DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy_provider: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + tenant: ansible_test + schema: ansible_test + template: Template 1 + application_profile: ansible_test + endpoint_group: ansible_test + state: present + delegate_to: localhost + +- name: Remove a provider to a DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy_provider: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + tenant: ansible_test + schema: ansible_test + template: Template 1 + application_profile: ansible_test + endpoint_group: ansible_test + state: absent + delegate_to: localhost + +- name: Query a provider to a DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy_provider: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + tenant: ansible_test + schema: ansible_test + template: Template 1 + application_profile: ansible_test + endpoint_group: ansible_test + state: query + delegate_to: localhost + +- name: Query all provider of a DHCP Relay Policy + cisco.mso.mso_dhcp_relay_policy_provider: + host: mso_host + username: admin + password: SomeSecretPassword + dhcp_relay_policy: my_test_dhcp_policy + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import ( + MSOModule, + mso_argument_spec, +) + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + dhcp_relay_policy=dict(type="str", required=True, aliases=["name"]), + ip=dict(type="str"), + tenant=dict(type="str"), + schema=dict(type="str"), + template=dict(type="str"), + application_profile=dict(type="str", aliases=["anp"]), + endpoint_group=dict(type="str", aliases=["epg"]), + external_endpoint_group=dict(type="str", aliases=["ext_epg", "external_epg"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["ip", "tenant", "schema", "template"]], + ["state", "absent", ["tenant", "schema", "template"]], + ], + ) + + dhcp_relay_policy = module.params.get("dhcp_relay_policy") + ip = module.params.get("ip") + tenant = module.params.get("tenant") + schema = module.params.get("schema") + template = module.params.get("template") + if template is not None: + template = template.replace(" ", "") + application_profile = module.params.get("application_profile") + endpoint_group = module.params.get("endpoint_group") + external_endpoint_group = module.params.get("external_endpoint_group") + state = module.params.get("state") + + mso = MSOModule(module) + + path = "policies/dhcp/relay" + + tenant_id = mso.lookup_tenant(tenant) + # Get schema_id + schema_id = mso.lookup_schema(schema) + + provider = dict( + addr=ip, + externalEpgRef="", + epgRef="", + l3Ref="", + tenantId=tenant_id, + ) + provider_index = None + previous_provider = {} + + if application_profile is not None and endpoint_group is not None: + provider["epgRef"] = "/schemas/{schemaId}/templates/{templateName}/anps/{app}/epgs/{epg}".format( + schemaId=schema_id, + templateName=template, + app=application_profile, + epg=endpoint_group, + ) + elif external_endpoint_group is not None: + provider["externalEpgRef"] = "/schemas/{schemaId}/templates/{templateName}/externalEpgs/{ext_epg}".format( + schemaId=schema_id, templateName=template, ext_epg=external_endpoint_group + ) + + # Query for existing object(s) + dhcp_relay_obj = mso.get_obj(path, name=dhcp_relay_policy, key="DhcpRelayPolicies") + if "id" not in dhcp_relay_obj: + mso.fail_json(msg="DHCP Relay Policy '{0}' is not a valid DHCP Relay Policy name.".format(dhcp_relay_policy)) + policy_id = dhcp_relay_obj.get("id") + providers = [] + if "provider" in dhcp_relay_obj: + providers = dhcp_relay_obj.get("provider") + for index, prov in enumerate(providers): + if (provider.get("epgRef") != "" and prov.get("epgRef") == provider.get("epgRef")) or ( + provider.get("externalEpgRef") != "" and prov.get("externalEpgRef") == provider.get("externalEpgRef") + ): + previous_provider = prov + provider_index = index + + # If we found an existing object, continue with it + path = "{0}/{1}".format(path, policy_id) + + if state == "query": + mso.existing = providers + if endpoint_group is not None or external_endpoint_group is not None: + mso.existing = previous_provider + mso.exit_json() + + if endpoint_group is None and external_endpoint_group is None: + mso.fail_json(msg="Missing either endpoint_group or external_endpoint_group required attribute.") + + mso.previous = previous_provider + if state == "absent": + provider = {} + if previous_provider: + if provider_index is not None: + providers.pop(provider_index) + + elif state == "present": + if provider_index is not None: + providers[provider_index] = provider + else: + providers.append(provider) + + if module.check_mode: + mso.existing = provider + else: + mso.existing = dhcp_relay_obj + dhcp_relay_obj["provider"] = providers + mso.sanitize(dhcp_relay_obj, collate=True) + new_dhcp_relay_obj = mso.request(path, method="PUT", data=mso.sent) + mso.existing = {} + for index, prov in enumerate(new_dhcp_relay_obj.get("provider")): + if (provider.get("epgRef") != "" and prov.get("epgRef") == provider.get("epgRef")) or ( + provider.get("externalEpgRef") != "" and prov.get("externalEpgRef") == provider.get("externalEpgRef") + ): + mso.existing = prov + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_label.py b/ansible_collections/cisco/mso/plugins/modules/mso_label.py new file mode 100644 index 000000000..f4f05f7de --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_label.py @@ -0,0 +1,164 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_label +short_description: Manage labels +description: +- Manage labels on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + label: + description: + - The name of the label. + type: str + aliases: [ name ] + type: + description: + - The type of the label. + type: str + choices: [ site ] + default: site + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new label + cisco.mso.mso_label: + host: mso_host + username: admin + password: SomeSecretPassword + label: Belgium + type: site + state: present + delegate_to: localhost + +- name: Remove a label + cisco.mso.mso_label: + host: mso_host + username: admin + password: SomeSecretPassword + label: Belgium + state: absent + delegate_to: localhost + +- name: Query a label + cisco.mso.mso_label: + host: mso_host + username: admin + password: SomeSecretPassword + label: Belgium + state: query + delegate_to: localhost + register: query_result + +- name: Query all labels + cisco.mso.mso_label: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + label=dict(type="str", aliases=["name"]), + type=dict(type="str", default="site", choices=["site"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["label"]], + ["state", "present", ["label"]], + ], + ) + + label = module.params.get("label") + label_type = module.params.get("type") + state = module.params.get("state") + + mso = MSOModule(module) + + label_id = None + path = "labels" + + # Query for existing object(s) + if label: + mso.existing = mso.get_obj(path, displayName=label) + if mso.existing: + label_id = mso.existing.get("id") + # If we found an existing object, continue with it + path = "labels/{id}".format(id=label_id) + else: + mso.existing = mso.query_objs(path) + + if state == "query": + pass + + elif state == "absent": + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method="DELETE") + + elif state == "present": + mso.previous = mso.existing + + payload = dict( + id=label_id, + displayName=label, + type=label_type, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + if mso.check_changed(): + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent) + else: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py b/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py new file mode 100644 index 000000000..10546563f --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py @@ -0,0 +1,243 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_remote_location +short_description: Manages remote locations +description: +- Manage remote locations on Cisco ACI Multi-Site. +author: +- Akini Ross (@akinross) +options: + remote_location: + description: + - The remote location's name. + type: str + aliases: [ name ] + description: + description: + - The remote location's description. + type: str + remote_protocol: + description: + - The protocol used to export to the remote server. + - If the remote location is a Windows server, you must use the C(sftp) protocol. + choices: [ scp, sftp ] + type: str + remote_host: + description: + - The host name or IP address of the remote server. + type: str + remote_path: + description: + - The full path to a directory on the remote server where backups are saved. + - The path must start with a slash (/) character and must not contain periods (.) or backslashes (\). + - The directory must already exist on the server. + type: str + remote_port: + description: + - The port used to connect to the remote server. + default: 22 + type: int + authentication_type: + description: + - The authentication method used to connect to the remote server. + choices: [ password, ssh ] + type: str + remote_username: + description: + - The username used to log in to the remote server. + type: str + remote_password: + description: + - The password used to log in to the remote server. + type: str + remote_ssh_key: + description: + - The private ssh key used to log in to the remote server. + - The private ssh key must be provided in PEM format. + - The private ssh key must be a single line string with linebreaks represent as "\n". + type: str + remote_ssh_passphrase: + description: + - The private ssh key passphrase used to log in to the remote server. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Query all remote locations + cisco.mso.mso_remote_location: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: backups + +- name: Query a remote location + cisco.mso.mso_remote_location: + host: mso_host + username: admin + password: SomeSecretPassword + remote_location: ansible_test + state: query + delegate_to: localhost + +- name: Configure a remote location + cisco.mso.mso_remote_location: + host: mso_host + username: admin + password: SomeSecretPassword + remote_location: ansible_test + remote_protocol: scp + remote_host: 10.0.0.1 + remote_path: /username/backup + remote_authentication_type: password + remote_username: username + remote_password: password + state: present + delegate_to: localhost + +- name: Delete a remote location + cisco.mso.mso_remote_location: + host: mso_host + username: admin + password: SomeSecretPassword + remote_location: ansible_test + state: absent + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + remote_location=dict(type="str", aliases=["name"]), + description=dict(type="str"), + remote_protocol=dict(type="str", choices=["scp", "sftp"]), + remote_host=dict(type="str"), + remote_path=dict(type="str"), + remote_port=dict(type="int", default=22), + authentication_type=dict(type="str", choices=["password", "ssh"]), + remote_username=dict(type="str"), + remote_password=dict(type="str", no_log=True), + remote_ssh_key=dict(type="str", no_log=True), + remote_ssh_passphrase=dict(type="str", no_log=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["remote_location", "remote_protocol", "remote_host", "remote_path", "authentication_type"]], + ["state", "absent", ["remote_location"]], + ["authentication_type", "password", ["remote_username", "remote_password"]], + ["authentication_type", "ssh", ["remote_ssh_key"]], + ], + ) + + location_name = module.params.get("remote_location") + description = module.params.get("description") + protocol = module.params.get("remote_protocol") + host = module.params.get("remote_host") + path = module.params.get("remote_path") + port = module.params.get("remote_port") + authentication_type = module.params.get("authentication_type") + username = module.params.get("remote_username") + password = module.params.get("remote_password") + ssh_key = module.params.get("remote_ssh_key") + passphrase = module.params.get("remote_ssh_passphrase") + state = module.params.get("state") + + mso = MSOModule(module) + api_path = "platform/remote-locations" + mso.existing = mso.query_objs(api_path, key="remoteLocations") + + remote_location_obj = None + if location_name and mso.existing: + remote_location_obj = next((item for item in mso.existing if item.get("name") == location_name), None) + if remote_location_obj: + mso.existing = remote_location_obj + + if state == "query": + if location_name and not remote_location_obj: + existing_location_list = ", ".join([item.get("name") for item in mso.existing]) + mso.module.fail_json(msg="Remote location {0} not found. Remote locations configured: {1}".format(location_name, existing_location_list)) + + elif state == "absent": + mso.previous = mso.existing + + if module.check_mode: + mso.existing = {} + elif remote_location_obj: + mso.existing = mso.request("{0}/{1}".format(api_path, remote_location_obj.get("id")), method="DELETE") + + elif state == "present": + mso.previous = mso.existing + + credential = dict( + authType=authentication_type if authentication_type == "password" else "sshKey", + hostname=host, + port=port, + protocolType=protocol, + remotePath=path, + username=username, + ) + + if authentication_type == "password": + credential.update(password=password) + else: + credential.update(sshKey=ssh_key) + if passphrase: + credential.update(passPhrase=passphrase) + + payload = dict(name=location_name, credential=credential) + + if description: + payload.update(description=description) + + mso.proposed = payload + + if module.check_mode: + mso.existing = mso.proposed + else: + if remote_location_obj: + payload.update(id=remote_location_obj.get("id")) + mso.existing = mso.request("{0}/{1}".format(api_path, remote_location_obj.get("id")), method="PUT", data=payload) + else: + mso.existing = mso.request(api_path, method="POST", data=payload) + + mso.existing["credential"].pop("password", None) + mso.existing["credential"].pop("sshKey", None) + mso.existing["credential"].pop("passPhrase", None) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_rest.py b/ansible_collections/cisco/mso/plugins/modules/mso_rest.py new file mode 100644 index 000000000..ae05b093b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_rest.py @@ -0,0 +1,186 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_rest +short_description: Direct access to the Cisco MSO REST API +description: +- Enables the management of the Cisco MSO fabric through direct access to the Cisco MSO REST API. +- This module is not idempotent and does not report changes. +options: + method: + description: + - The HTTP method of the request. + - Using C(delete) is typically used for deleting objects. + - Using C(get) is typically used for querying objects. + - Using C(post) is typically used for modifying objects. + - Using C(put) is typically used for modifying existing objects. + - Using C(patch) is typically also used for modifying existing objects. + type: str + choices: [ delete, get, post, put, patch ] + default: get + aliases: [ action ] + path: + description: + - URI being used to execute API calls. + type: str + required: true + aliases: [ uri ] + content: + description: + - Sets the payload of the API request directly. + - This may be convenient to template simple requests. + - For anything complex use the C(template) lookup plugin (see examples). + type: raw + aliases: [ payload ] +extends_documentation_fragment: +- cisco.mso.modules + +notes: +- Most payloads are known not to be idempotent, so be careful when constructing payloads. +seealso: +- module: cisco.mso.mso_tenant +author: +- Anvitha Jain (@anvitha-jain) +""" + +EXAMPLES = r""" +- name: Add schema (JSON) + cisco.mso.mso_rest: + host: mso + username: admin + password: SomeSecretPassword + path: /mso/api/v1/schemas + method: post + content: + { + "displayName": "{{ mso_schema | default('ansible_test') }}", + "templates": [{ + "name": "Template_1", + "tenantId": "{{ add_tenant.jsondata.id }}", + "displayName": "Template_1", + "templateSubType": [], + "templateType": "stretched-template", + "anps": [], + "contracts": [], + "vrfs": [], + "bds": [], + "filters": [], + "externalEpgs": [], + "serviceGraphs": [], + "intersiteL3outs": [] + }], + "sites": [], + "_updateVersion": 0 + } + delegate_to: localhost + +- name: Query schema + cisco.mso.mso_rest: + host: mso + username: admin + password: SomeSecretPassword + path: /mso/api/v1/schemas + method: get + delegate_to: localhost + +- name: Patch schema (YAML) + cisco.mso.mso_rest: + host: mso + username: admin + password: SomeSecretPassword + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: patch + content: + - op: add + path: /templates/Template_1/anps/- + value: + name: AP2 + displayName: AP2 + epgs: [] + _updateVersion: 0 + delegate_to: localhost + +- name: Add a tenant from a templated payload file from templates + cisco.mso.mso_rest: + host: mso + username: admin + password: SomeSecretPassword + method: post + path: /api/v1/tenants + content: "{{ lookup('template', 'mso/tenant.json.j2') }}" + delegate_to: localhost +""" + +RETURN = r""" +""" + +# Optional, only used for YAML validation +try: + import yaml + + HAS_YAML = True +except Exception: + HAS_YAML = False + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible.module_utils._text import to_text + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + path=dict(type="str", required=True, aliases=["uri"]), + method=dict(type="str", default="get", choices=["delete", "get", "post", "put", "patch"], aliases=["action"]), + content=dict(type="raw", aliases=["payload"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + ) + + content = module.params.get("content") + path = module.params.get("path") + + mso = MSOModule(module) + + # Validate content/payload + if content and isinstance(content, str) and HAS_YAML: + try: + # Validate YAML/JSON string + content = yaml.safe_load(content) + except Exception as e: + module.fail_json(msg="Failed to parse provided JSON/YAML payload: %s" % to_text(e), exception=to_text(e), payload=content) + + mso.method = mso.params.get("method").upper() + + # Perform request + if module.check_mode: + mso.result["jsondata"] = content + else: + mso.result["jsondata"] = mso.request(path, method=mso.method, data=content, api_version=None) + + mso.result["status"] = mso.status + + if mso.method != "GET": + mso.result["changed"] = True + if mso.method == "DELETE": + mso.result["jsondata"] = None + + # Report success + mso.exit_json(**mso.result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_role.py b/ansible_collections/cisco/mso/plugins/modules/mso_role.py new file mode 100644 index 000000000..cfa4483b0 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_role.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_role +short_description: Manage roles +description: +- Manage roles on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + role: + description: + - The name of the role. + type: str + aliases: [ name ] + display_name: + description: + - The name of the role to be displayed in the web UI. + type: str + description: + description: + - The description of the role. + type: str + read_permissions: + description: + - A list of read permissions tied to this role. + type: list + elements: str + choices: + - backup-db + - manage-audit-records + - manage-labels + - manage-roles + - manage-schemas + - manage-sites + - manage-tenants + - manage-tenant-schemas + - manage-users + - platform-logs + - view-all-audit-records + - view-labels + - view-roles + - view-schemas + - view-sites + - view-tenants + - view-tenant-schemas + - view-users + write_permissions: + description: + - A list of write permissions tied to this role. + type: list + elements: str + aliases: [ permissions ] + choices: + - backup-db + - manage-audit-records + - manage-labels + - manage-roles + - manage-schemas + - manage-sites + - manage-tenants + - manage-tenant-schemas + - manage-users + - platform-logs + - view-all-audit-records + - view-labels + - view-roles + - view-schemas + - view-sites + - view-tenants + - view-tenant-schemas + - view-users + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new role + cisco.mso.mso_role: + host: mso_host + username: admin + password: SomeSecretPassword + role: readOnly + display_name: Read Only + description: Read-only access for troubleshooting + read_permissions: + - view-roles + - view-schemas + - view-sites + - view-tenants + - view-tenant-schemas + - view-users + write_permissions: + - manage-roles + - manage-schemas + - manage-sites + - manage-tenants + - manage-tenant-schemas + - manage-users + state: present + delegate_to: localhost + +- name: Remove a role + cisco.mso.mso_role: + host: mso_host + username: admin + password: SomeSecretPassword + role: readOnly + state: absent + delegate_to: localhost + +- name: Query a role + cisco.mso.mso_role: + host: mso_host + username: admin + password: SomeSecretPassword + role: readOnly + state: query + delegate_to: localhost + register: query_result + +- name: Query all roles + cisco.mso.mso_role: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + role=dict(type="str", aliases=["name"]), + display_name=dict(type="str"), + description=dict(type="str"), + read_permissions=dict( + type="list", + elements="str", + choices=[ + "backup-db", + "manage-audit-records", + "manage-labels", + "manage-roles", + "manage-schemas", + "manage-sites", + "manage-tenants", + "manage-tenant-schemas", + "manage-users", + "platform-logs", + "view-all-audit-records", + "view-labels", + "view-roles", + "view-schemas", + "view-sites", + "view-tenants", + "view-tenant-schemas", + "view-users", + ], + ), + write_permissions=dict( + type="list", + elements="str", + aliases=["permissions"], + choices=[ + "backup-db", + "manage-audit-records", + "manage-labels", + "manage-roles", + "manage-schemas", + "manage-sites", + "manage-tenants", + "manage-tenant-schemas", + "manage-users", + "platform-logs", + "view-all-audit-records", + "view-labels", + "view-roles", + "view-schemas", + "view-sites", + "view-tenants", + "view-tenant-schemas", + "view-users", + ], + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["role"]], + ["state", "present", ["role"]], + ], + ) + + role = module.params.get("role") + description = module.params.get("description") + read_permissions = module.params.get("read_permissions") + write_permissions = module.params.get("write_permissions") + state = module.params.get("state") + + mso = MSOModule(module) + + role_id = None + path = "roles" + + # Query for existing object(s) + if role: + mso.existing = mso.get_obj(path, name=role) + if mso.existing: + role_id = mso.existing.get("id") + # If we found an existing object, continue with it + path = "roles/{id}".format(id=role_id) + else: + mso.existing = mso.query_objs(path) + + if state == "query": + pass + + elif state == "absent": + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method="DELETE") + + elif state == "present": + mso.previous = mso.existing + + payload = dict( + id=role_id, + name=role, + displayName=role, + description=description, + readPermissions=read_permissions, + writePermissions=write_permissions, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + if mso.check_changed(): + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent) + else: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema.py new file mode 100644 index 000000000..2eba13ac9 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema +short_description: Manage schemas +description: +- Manage schemas on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + aliases: [ name ] + state: + description: + - Use C(absent) for removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, query ] + default: query +notes: +- Due to restrictions of the MSO REST API this module cannot create empty schemas (i.e. schemas without templates). + Use the M(cisco.mso.mso_schema_template) to automatically create schemas with templates. +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_template +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Remove schemas + cisco.mso.mso_schema: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + state: absent + delegate_to: localhost + +- name: Query a schema + cisco.mso.mso_schema: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all schemas + cisco.mso.mso_schema: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", aliases=["name"]), + # messages=dict(type='dict'), + # associations=dict(type='list'), + # health_faults=dict(type='list'), + # references=dict(type='dict'), + # policy_states=dict(type='list'), + state=dict(type="str", default="query", choices=["absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["schema"]], + ], + ) + + schema = module.params.get("schema") + state = module.params.get("state") + + mso = MSOModule(module) + + schema_id = None + path = "schemas" + + # Query for existing object(s) + if schema: + mso.existing = mso.get_obj(path, displayName=schema) + if mso.existing: + schema_id = mso.existing.get("id") + path = "schemas/{id}".format(id=schema_id) + else: + mso.existing = mso.query_objs(path) + + if state == "query": + pass + + elif state == "absent": + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method="DELETE") + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py new file mode 100644 index 000000000..840fb12ca --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_clone +short_description: Clone schemas +description: +- Clone schemas on Cisco ACI Multi-Site. +- Clones only template objects and not site objects. +- This module can only be used on versions of MSO that are 3.3 or greater. +author: +- Anvitha Jain (@anvitha-jain) +options: + source_schema: + description: + - The name of the source_schema. + type: str + destination_schema: + description: + - The name of the destination_schema. + type: str + state: + description: + - Use C(clone) for adding. + type: str + choices: [ clone ] + default: clone +seealso: +- module: cisco.mso.mso_schema +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Clone schema + cisco.mso.mso_schema_clone: + host: mso_host + username: admin + password: SomeSecretPassword + source_schema: Source_Schema + destination_schema: Destination_Schema + state: clone + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_4_UNIQUE_IDENTIFIERS +import json + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + source_schema=dict(type="str"), + destination_schema=dict(type="str"), + state=dict(type="str", default="clone", choices=["clone"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "clone", ["destination_schema"]], + ], + ) + + source_schema = module.params.get("source_schema") + destination_schema = module.params.get("destination_schema") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get source schema details + source_schema_path = "schemas/{0}".format(mso.lookup_schema(source_schema)) + source_schema_obj = mso.query_obj(source_schema_path, displayName=source_schema) + + source_data = source_schema_obj.get("templates") + source_data = json.loads(json.dumps(source_data).replace("/{0}".format(source_schema_path), "")) + # certain unique identifiers are present in NDO4.0> source which need to be deleted from source_data prior to POST + for template in source_data: + mso.delete_keys_from_dict(template, NDO_4_UNIQUE_IDENTIFIERS) + + path = "schemas" + + # Check if source and destination schema are named differently + if source_schema == destination_schema: + mso.fail_json(msg="Source and Destination schema cannot have same names.") + # Query for existing object(s) + if destination_schema: + mso.existing = mso.get_obj(path, displayName=destination_schema) + if mso.existing: + mso.fail_json(msg="Schema with the name '{0}' already exists. Please use another name.".format(destination_schema)) + + if state == "clone": + mso.previous = mso.existing + payload = dict( + displayName=destination_schema, + templates=source_data, + ) + mso.sanitize(payload, collate=True) + + if not mso.existing: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py new file mode 100644 index 000000000..83a5213a2 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site +short_description: Manage sites in schemas +description: +- Manage sites on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site to manage. + type: str + template: + description: + - The name of the template. + type: str + aliases: [ name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template +- module: cisco.mso.mso_site +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site to a schema + cisco.mso.mso_schema_site: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site1 + template: Template 1 + state: present + delegate_to: localhost + +- name: Remove a site from a schema + cisco.mso.mso_schema_site: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site1 + template: Template 1 + state: absent + delegate_to: localhost + +- name: Query a schema site + cisco.mso.mso_schema_site: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all schema sites + cisco.mso.mso_schema_site: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", aliases=["name"]), + template=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["site", "template"]], + ["state", "present", ["site", "template"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template") + if template is not None: + template = template.replace(" ", "") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get site + site_id = mso.lookup_site(site) + + mso.existing = {} + if "sites" in schema_obj: + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if template: + if (site_id, template) in sites: + site_idx = sites.index((site_id, template)) + site_path = "/sites/{0}".format(site_idx) + mso.existing = schema_obj.get("sites")[site_idx] + else: + mso.existing = schema_obj.get("sites") + + if state == "query": + if not mso.existing: + if template: + mso.fail_json(msg="Template '{0}' not found".format(template)) + else: + mso.existing = [] + mso.exit_json() + + sites_path = "/sites" + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + # Remove existing site + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=site_path)) + + elif state == "present": + if not mso.existing: + # Add new site + payload = dict( + siteId=site_id, + templateName=template, + anps=[], + bds=[], + contracts=[], + externalEpgs=[], + intersiteL3outs=[], + serviceGraphs=[], + vrfs=[], + ) + + mso.sanitize(payload, collate=True) + + ops.append(dict(op="add", path=sites_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py new file mode 100644 index 000000000..0552b6a31 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp +short_description: Manage site-local Application Network Profiles (ANPs) in schema template +description: +- Manage site-local ANPs in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP to manage. + type: str + aliases: [ name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_site_anp_epg +- module: cisco.mso.mso_schema_template_anp +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site ANP + cisco.mso.mso_schema_site_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: present + delegate_to: localhost + +- name: Remove a site ANP + cisco.mso.mso_schema_site_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: absent + delegate_to: localhost + +- name: Query a specific site ANP + cisco.mso.mso_schema_site_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site ANPs + cisco.mso.mso_schema_site_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["anp"]], + ["state", "present", ["anp"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json( + msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. " + "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list)) + ) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj.get("sites")[site_idx]["anps"]] + + if anp is not None and anp_ref in anps: + anp_idx = anps.index(anp_ref) + anp_path = "/sites/{0}/anps/{1}".format(site_template, anp) + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx] + + if state == "query": + if anp is None: + mso.existing = schema_obj.get("sites")[site_idx]["anps"] + elif not mso.existing: + mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp)) + mso.exit_json() + + anps_path = "/sites/{0}/anps".format(site_template) + ops = [] + + # Workaround due to inconsistency in attributes REQUEST/RESPONSE API + # FIX for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing + mso.replace_keys_in_dict("deployImmediacy", "deploymentImmediacy") + if mso.existing.get("anpRef"): + anp_ref = mso.dict_from_ref(mso.existing.get("anpRef")) + mso.existing["anpRef"] = anp_ref + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=anp_path)) + + elif state == "present": + payload = dict( + anpRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + ), + ) + + if "epgs" in mso.existing: + for epg in mso.existing.get("epgs"): + epg = mso.recursive_dict_from_ref(epg) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=anp_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=anps_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py new file mode 100644 index 000000000..1c2ad6433 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg +short_description: Manage site-local Endpoint Groups (EPGs) in schema template +description: +- Manage site-local EPGs in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + aliases: [ name ] + private_link_label: + description: + - The private link label used to represent this subnet. + - This parameter is available for MSO version greater than 3.3. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_site_anp +- module: cisco.mso.mso_schema_site_anp_epg_subnet +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site EPG + cisco.mso.mso_schema_site_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + state: present + delegate_to: localhost + +- name: Remove a site EPG + cisco.mso.mso_schema_site_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + state: absent + delegate_to: localhost + +- name: Query a specific site EPGs + cisco.mso.mso_schema_site_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site EPGs + cisco.mso.mso_schema_site_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + private_link_label=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["epg"]], + ["state", "present", ["epg"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + private_link_label = module.params.get("private_link_label") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json( + msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. " + "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list)) + ) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + payload = {} + ops = [] + op_path = "" + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]] + anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]] + if anp not in anps_in_temp: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps_in_temp))) + else: + # Get anp index at template level + template_anp_idx = anps_in_temp.index(anp) + + # If anp not at site level but exists at template level + if anp_ref not in anps: + op_path = "/sites/{0}/anps".format(site_template) + payload = dict( + anpRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + ), + ) + else: + # Get anp index at site level + anp_idx = anps.index(anp_ref) + + if epg is not None: + # Get EPG + epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) + new_epg = dict( + epgRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + epgName=epg, + ) + ) + + # If anp exists at site level + if "anpRef" not in payload: + epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]] + + # If anp already at site level AND if epg not at site level (or) anp not at site level? + if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload: + epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]] + + # If EPG not at template level - Fail + if epg not in epgs_in_temp: + mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1}".format(epg, ", ".join(epgs_in_temp))) + + # EPG at template level but not at site level. Create payload at site level for EPG + else: + # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload + if "anpRef" not in payload: + op_path = "/sites/{0}/anps/{1}/epgs".format(site_template, anp) + payload = new_epg + else: + # If anp in payload, anp exists at site level. Update payload with EPG payload + payload["epgs"] = [new_epg] + + # Get index of EPG at site level + else: + epg_idx = epgs.index(epg_ref) + epg_path = "/sites/{0}/anps/{1}/epgs/{2}".format(site_template, anp, epg) + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx] + payload = new_epg + + ops = [] + + if state == "query": + if anp_ref not in anps: + mso.fail_json(msg="Provided anp '{0}' does not exist at site level.".format(anp)) + if epg is None: + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"] + elif not mso.existing: + mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg)) + mso.exit_json() + + # Workaround due to inconsistency in attributes REQUEST/RESPONSE API + # FIX for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing + mso.replace_keys_in_dict("deployImmediacy", "deploymentImmediacy") + if mso.existing.get("epgRef"): + epg_ref = mso.dict_from_ref(mso.existing.get("epgRef")) + mso.existing["epgRef"] = epg_ref + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=epg_path)) + + elif state == "present": + if private_link_label is not None: + payload["privateLinkLabel"] = dict(name=private_link_label) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=epg_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py new file mode 100644 index 000000000..eda60cfd1 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py @@ -0,0 +1,464 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg_bulk_staticport +short_description: Manage site-local EPG static ports in bulk in schema template +description: +- Manage site-local EPG static ports in bulk in schema template on Cisco ACI Multi-Site. +author: +- Anvitha Jain (@anvjain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG. + type: str + required: true + type: + description: + - The path type of the static port + - vpc is used for a Virtual Port Channel + - dpc is used for a Direct Port Channel + - port is used for a single interface + type: str + choices: [ port, vpc, dpc ] + default: port + pod: + description: + - The pod of the static port. + type: str + leaf: + description: + - The leaf of the static port. + type: str + fex: + description: + - The fex id of the static port. + type: str + path: + description: + - The path of the static port. + type: str + vlan: + description: + - The port encap VLAN id of the static port. + type: int + deployment_immediacy: + description: + - The deployment immediacy of the static port. + - C(immediate) means B(Deploy immediate). + - C(lazy) means B(deploy on demand). + type: str + choices: [ immediate, lazy ] + default: lazy + mode: + description: + - The mode of the static port. + - C(native) means B(Access (802.1p)). + - C(regular) means B(Trunk). + - C(untagged) means B(Access (untagged)). + type: str + choices: [ native, regular, untagged ] + default: untagged + primary_micro_segment_vlan: + description: + - Primary micro-seg VLAN of static port. + type: int + static_ports: + description: + - List of static port configurations and elements in the form of a dictionary. + - Module level attributes will be overridden by the path level attributes. + - Making changes to an item in the list will update the whole payload. + type: list + elements: dict + suboptions: + type: + description: + - The path type of the static port + - vpc is used for a Virtual Port Channel + - dpc is used for a Direct Port Channel + - port is used for a single interface + type: str + choices: [ port, vpc, dpc ] + pod: + description: + - The pod of the static port. + type: str + leaf: + description: + - The leaf of the static port. + type: str + fex: + description: + - The fex id of the static port. + type: str + path: + description: + - The path of the static port. + - Path has to be unique for each static port in a particular leaf. + type: str + vlan: + description: + - The port encap VLAN id of the static port. + type: int + deployment_immediacy: + description: + - The deployment immediacy of the static port. + - C(immediate) means B(Deploy immediate). + - C(lazy) means B(deploy on demand). + type: str + choices: [ immediate, lazy ] + mode: + description: + - The mode of the static port. + - C(native) means B(Access (802.1p)). + - C(regular) means B(Trunk). + - C(untagged) means B(Access (untagged)). + type: str + choices: [ native, regular, untagged ] + primary_micro_segment_vlan: + description: + - Primary micro-seg VLAN of the static port. + type: int + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing an object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_anp_epg +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new static port to a site EPG + cisco.mso.mso_schema_site_anp_epg_bulk_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + deployment_immediacy: immediate + static_ports: + - path: eth1/2 + leaf: 102 + - path: eth1/3 + vlan: 124 + state: present + delegate_to: localhost + +- name: Add a new static fex port to a site EPG + cisco.mso.mso_schema_site_anp_epg_bulk_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + deployment_immediacy: lazy + static_ports: + - path: eth1/2 + leaf: 102 + - path: eth1/3 + vlan: 124 + - fex: 151 + state: present + delegate_to: localhost + +- name: Add a new static VPC to a site EPG + cisco.mso.mso_schema_site_anp_epg_bulk_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + static_ports: + - path: eth1/2 + leaf: 102 + - path: eth1/3 + vlan: 124 + - fex: 151 + - leaf: 101-102 + path: ansible_polgrp + vlan: 127 + type: vpc + mode: untagged + deployment_immediacy: lazy + state: present + delegate_to: localhost + +- name: Remove static ports from a site EPG + cisco.mso.mso_schema_site_anp_epg_bulk_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + state: absent + delegate_to: localhost + +- name: Query all site EPG static ports + cisco.mso.mso_schema_site_anp_epg_bulk_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_site_anp_epg_bulk_staticport_spec +from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + type=dict(type="str", default="port", choices=["port", "vpc", "dpc"]), + pod=dict(type="str"), # This parameter is not required for querying all objects + leaf=dict(type="str"), # This parameter is not required for querying all objects + fex=dict(type="str"), # This parameter is not required for querying all objects + path=dict(type="str"), # This parameter is not required for querying all objects + vlan=dict(type="int"), # This parameter is not required for querying all objects + primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects + deployment_immediacy=dict(type="str", default="lazy", choices=["immediate", "lazy"]), + mode=dict(type="str", default="untagged", choices=["native", "regular", "untagged"]), + static_ports=dict(type="list", elements="dict", options=mso_site_anp_epg_bulk_staticport_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["static_ports"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + module_path_type = module.params.get("type") + module_pod = module.params.get("pod") + module_leaf = module.params.get("leaf") + module_fex = module.params.get("fex") + module_path = module.params.get("path") + module_vlan = module.params.get("vlan") + module_primary_micro_segment_vlan = module.params.get("primary_micro_segment_vlan") + module_deployment_immediacy = module.params.get("deployment_immediacy") + module_mode = module.params.get("mode") + static_ports = module.params.get("static_ports") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + mso_schema = MSOSchema(mso, schema, template, site) + mso_objects = mso_schema.schema_objects + + # Verifies ANP and EPG exists at template level + mso_schema.set_template_anp(anp) + mso_schema.set_template_anp_epg(epg) + + # Verifies if ANP exists at site level + mso_schema.set_site_anp(anp, fail_module=False) + + payload = dict() + ops = [] + op_path = "/sites/{0}-{1}/anps".format(mso_objects.get("site").details.get("siteId"), template) + mso.existing = [] + + # If anp not at site level but exists at template level + if not mso_objects.get("site_anp"): + op_path = op_path + "/-" + payload.update( + anpRef=dict( + schemaId=mso_schema.id, + templateName=template, + anpName=anp, + ), + ) + else: + mso_schema.set_site_anp_epg(epg, fail_module=False) + + # If epg not at site level (or) anp not at site level payload + if not mso_objects.get("site_anp_epg") or "anpRef" in payload: + # EPG at template level but not at site level. Create payload at site level for EPG + new_epg = dict( + epgRef=dict( + schemaId=mso_schema.id, + templateName=template, + anpName=anp, + epgName=epg, + ) + ) + + # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload + if "anpRef" not in payload: + op_path = "{0}/{1}/epgs/-".format(op_path, anp) + payload = new_epg + else: + # If anp in payload, anp exists at site level. Update payload with EPG payload + payload["epgs"] = [new_epg] + else: + # If anp and epg exists at site level + op_path = "{0}/{1}/epgs/{2}/staticPorts".format(op_path, anp, epg) + mso.existing = mso_objects.get("site_anp_epg").details.get("staticPorts", []) + + staticport_list = [] + unique_paths = [] + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + mso.sent = mso.existing = [] + ops.append(dict(op="remove", path=op_path)) + + elif state == "present": + for static_port in static_ports: + path_type = static_port.get("type") or module_path_type + pod = static_port.get("pod") or module_pod + leaf = static_port.get("leaf") or module_leaf + fex = static_port.get("fex") or module_fex + path = static_port.get("path") or module_path # Note :path has to be diffent in each leaf for every static port in the list. + vlan = static_port.get("vlan") or module_vlan + primary_micro_segment_vlan = static_port.get("primary_micro_segment_vlan") or module_primary_micro_segment_vlan + deployment_immediacy = static_port.get("deployment_immediacy") or module_deployment_immediacy + mode = static_port.get("mode") or module_mode + + required_dict = {"pod": pod, "leaf": leaf, "path": path, "vlan": vlan} + if None in required_dict.values(): + res = [key for key in required_dict.keys() if required_dict[key] is None] + mso.fail_json(msg="state is present but all of the following are missing: {0}.".format(", ".join(res))) + else: + if path_type == "port" and fex is not None: + # Select port path for fex if fex param is used + portpath = "topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]".format(pod, leaf, fex, path) + elif path_type == "vpc": + portpath = "topology/{0}/protpaths-{1}/pathep-[{2}]".format(pod, leaf, path) + else: + portpath = "topology/{0}/paths-{1}/pathep-[{2}]".format(pod, leaf, path) + + new_leaf = dict( + deploymentImmediacy=deployment_immediacy, + mode=mode, + path=portpath, + portEncapVlan=vlan, + type=path_type, + ) + + if primary_micro_segment_vlan: + new_leaf.update(microSegVlan=primary_micro_segment_vlan) + + # validate and append staticports to staticport_list if path variable is different + if portpath in unique_paths: + mso.fail_json(msg="Each leaf in a pod of a static port should have an unique path.") + else: + unique_paths.append(portpath) + staticport_list.append(new_leaf) + + # If payload is empty, anp and EPG already exist at site level + if not payload: + payload = staticport_list + elif "anpRef" not in payload: # If anp already exists at site level + payload["staticPorts"] = staticport_list + else: + payload["epgs"][0]["staticPorts"] = staticport_list + + mso.proposed = staticport_list + mso.sent = payload + + if mso.existing: + ops.append(dict(op="replace", path=op_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(mso_schema.path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py new file mode 100644 index 000000000..c684a27be --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py @@ -0,0 +1,476 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Nirav Katarmal (@nkatarmal-crest) <nirav.katarmal@crestdatasys.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg_domain +short_description: Manage site-local EPG domains in schema template +description: +- Manage site-local EPG domains in schema template on Cisco ACI Multi-Site. +author: +- Nirav Katarmal (@nkatarmal-crest) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG. + type: str + required: true + domain_association_type: + description: + - The type of domain to associate. + type: str + choices: [ vmmDomain, l3ExtDomain, l2ExtDomain, physicalDomain, fibreChannelDomain ] + domain_profile: + description: + - The domain profile name. + type: str + deployment_immediacy: + description: + - The deployment immediacy of the domain. + - C(immediate) means B(Deploy immediate). + - C(lazy) means B(deploy on demand). + type: str + choices: [ immediate, lazy ] + resolution_immediacy: + description: + - Determines when the policies should be resolved and available. + - Defaults to C(lazy) when unset during creation. + type: str + choices: [ immediate, lazy, pre-provision ] + micro_seg_vlan_type: + description: + - Virtual LAN type for microsegmentation. This attribute can only be used with vmmDomain domain association. + - vlan is currently the only accepted value. + type: str + micro_seg_vlan: + description: + - Virtual LAN for microsegmentation. This attribute can only be used with vmmDomain domain association. + type: int + port_encap_vlan_type: + description: + - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association. + - vlan is currently the only accepted value. + type: str + port_encap_vlan: + description: + - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association. + type: int + vlan_encap_mode: + description: + - Which VLAN enacap mode to use. This attribute can only be used with vmmDomain domain association. + type: str + choices: [ static, dynamic ] + allow_micro_segmentation: + description: + - Specifies microsegmentation is enabled or not. This attribute can only be used with vmmDomain domain association. + type: bool + switch_type: + description: + - Which switch type to use with this domain association. This attribute can only be used with vmmDomain domain association. + type: str + switching_mode: + description: + - Which switching mode to use with this domain association. This attribute can only be used with vmmDomain domain association. + type: str + enhanced_lagpolicy_name: + description: + - EPG enhanced lagpolicy name. This attribute can only be used with vmmDomain domain association. + type: str + enhanced_lagpolicy_dn: + description: + - Distinguished name of EPG lagpolicy. This attribute can only be used with vmmDomain domain association. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_anp_epg +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new domain to a site EPG + cisco.mso.mso_schema_site_anp_epg_domain: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + delegate_to: localhost + +- name: Remove a domain from a site EPG + cisco.mso.mso_schema_site_anp_epg_domain: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: absent + delegate_to: localhost + +- name: Query a domain associated with a specific site EPG + cisco.mso.mso_schema_site_anp_epg_domain: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + state: query + delegate_to: localhost + register: query_result + +- name: Query all domains associated with a site EPG + cisco.mso.mso_schema_site_anp_epg_domain: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + domain_association_type=dict(type="str", choices=["vmmDomain", "l3ExtDomain", "l2ExtDomain", "physicalDomain", "fibreChannelDomain"]), + domain_profile=dict(type="str"), + deployment_immediacy=dict(type="str", choices=["immediate", "lazy"]), + resolution_immediacy=dict(type="str", choices=["immediate", "lazy", "pre-provision"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + micro_seg_vlan_type=dict(type="str"), + micro_seg_vlan=dict(type="int"), + port_encap_vlan_type=dict(type="str"), + port_encap_vlan=dict(type="int"), + vlan_encap_mode=dict(type="str", choices=["static", "dynamic"]), + allow_micro_segmentation=dict(type="bool"), + switch_type=dict(type="str"), + switching_mode=dict(type="str"), + enhanced_lagpolicy_name=dict(type="str"), + enhanced_lagpolicy_dn=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["domain_association_type", "domain_profile", "deployment_immediacy", "resolution_immediacy"]], + ["state", "present", ["domain_association_type", "domain_profile", "deployment_immediacy", "resolution_immediacy"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + domain_association_type = module.params.get("domain_association_type") + domain_profile = module.params.get("domain_profile") + deployment_immediacy = module.params.get("deployment_immediacy") + resolution_immediacy = module.params.get("resolution_immediacy") + state = module.params.get("state") + micro_seg_vlan_type = module.params.get("micro_seg_vlan_type") + micro_seg_vlan = module.params.get("micro_seg_vlan") + port_encap_vlan_type = module.params.get("port_encap_vlan_type") + port_encap_vlan = module.params.get("port_encap_vlan") + vlan_encap_mode = module.params.get("vlan_encap_mode") + allow_micro_segmentation = module.params.get("allow_micro_segmentation") + switch_type = module.params.get("switch_type") + switching_mode = module.params.get("switching_mode") + enhanced_lagpolicy_name = module.params.get("enhanced_lagpolicy_name") + enhanced_lagpolicy_dn = module.params.get("enhanced_lagpolicy_dn") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json( + msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. " + "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list)) + ) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + payload = dict() + ops = [] + op_path = "" + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]] + anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]] + if anp not in anps_in_temp: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps))) + else: + # Update anp index at template level + template_anp_idx = anps_in_temp.index(anp) + + # If anp not at site level but exists at template level + if anp_ref not in anps: + op_path = "/sites/{0}/anps/-".format(site_template) + payload.update( + anpRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + ), + ) + + else: + # Update anp index at site level + anp_idx = anps.index(anp_ref) + + # Get EPG + epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) + + # If anp exists at site level + if "anpRef" not in payload: + epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]] + + # If anp already at site level AND if epg not at site level (or) anp not at site level? + if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload: + epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]] + + # If EPG not at template level - Fail + if epg not in epgs_in_temp: + mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ", ".join(epgs_in_temp), epg_ref)) + + # EPG at template level but not at site level. Create payload at site level for EPG + else: + new_epg = dict( + epgRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + epgName=epg, + ) + ) + + # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload + if "anpRef" not in payload: + op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp) + payload = new_epg + else: + # If anp in payload, anp exists at site level. Update payload with EPG payload + payload["epgs"] = [new_epg] + + # Update index of EPG at site level + else: + epg_idx = epgs.index(epg_ref) + + if domain_association_type == "vmmDomain": + domain_dn = "uni/vmmp-VMware/dom-{0}".format(domain_profile) + elif domain_association_type == "l3ExtDomain": + domain_dn = "uni/l3dom-{0}".format(domain_profile) + elif domain_association_type == "l2ExtDomain": + domain_dn = "uni/l2dom-{0}".format(domain_profile) + elif domain_association_type == "physicalDomain": + domain_dn = "uni/phys-{0}".format(domain_profile) + elif domain_association_type == "fibreChannelDomain": + domain_dn = "uni/fc-{0}".format(domain_profile) + else: + domain_dn = "" + + # Get Domains + # If anp at site level and epg is at site level + if "anpRef" not in payload and "epgRef" not in payload: + domains = [dom.get("dn") for dom in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["domainAssociations"]] + if domain_dn in domains: + domain_idx = domains.index(domain_dn) + domain_path = "/sites/{0}/anps/{1}/epgs/{2}/domainAssociations/{3}".format(site_template, anp, epg, domain_idx) + mso.existing = schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["domainAssociations"][domain_idx] + + if state == "query": + if domain_association_type is None or domain_profile is None: + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["domainAssociations"] + elif not mso.existing: + mso.fail_json( + msg="Domain association '{domain_association_type}/{domain_profile}' not found".format( + domain_association_type=domain_association_type, domain_profile=domain_profile + ) + ) + mso.exit_json() + + domains_path = "/sites/{0}/anps/{1}/epgs/{2}/domainAssociations".format(site_template, anp, epg) + ops = [] + new_domain = dict( + dn=domain_dn, + domainType=domain_association_type, + deploymentImmediacy=deployment_immediacy, # keeping for backworths compatibility + deployImmediacy=deployment_immediacy, # rename of deploymentImmediacy + resolutionImmediacy=resolution_immediacy, + ) + + if domain_association_type == "vmmDomain": + vmmDomainProperties = {} + if micro_seg_vlan_type and micro_seg_vlan: + microSegVlan = dict(vlanType=micro_seg_vlan_type, vlan=micro_seg_vlan) + vmmDomainProperties["microSegVlan"] = microSegVlan + elif not micro_seg_vlan_type and micro_seg_vlan: + mso.fail_json(msg="micro_seg_vlan_type is required when micro_seg_vlan is provided.") + elif micro_seg_vlan_type and not micro_seg_vlan: + mso.fail_json(msg="micro_seg_vlan is required when micro_seg_vlan_type is provided.") + + if port_encap_vlan_type and port_encap_vlan: + portEncapVlan = dict(vlanType=port_encap_vlan_type, vlan=port_encap_vlan) + vmmDomainProperties["portEncapVlan"] = portEncapVlan + elif not port_encap_vlan_type and port_encap_vlan: + mso.fail_json(msg="port_encap_vlan_type is required when port_encap_vlan is provided.") + elif port_encap_vlan_type and not port_encap_vlan: + mso.fail_json(msg="port_encap_vlan is required when port_encap_vlan_type is provided.") + + if vlan_encap_mode: + vmmDomainProperties["vlanEncapMode"] = vlan_encap_mode + + if allow_micro_segmentation: + vmmDomainProperties["allowMicroSegmentation"] = allow_micro_segmentation + if switch_type: + vmmDomainProperties["switchType"] = switch_type + if switching_mode: + vmmDomainProperties["switchingMode"] = switching_mode + + if enhanced_lagpolicy_name and enhanced_lagpolicy_dn: + enhancedLagPol = dict(name=enhanced_lagpolicy_name, dn=enhanced_lagpolicy_dn) + epgLagPol = dict(enhancedLagPol=enhancedLagPol) + vmmDomainProperties["epgLagPol"] = epgLagPol + elif not enhanced_lagpolicy_name and enhanced_lagpolicy_dn: + mso.fail_json(msg="enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided.") + elif enhanced_lagpolicy_name and not enhanced_lagpolicy_dn: + mso.fail_json(msg="enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided.") + + if vmmDomainProperties: + new_domain["vmmDomainProperties"] = vmmDomainProperties + properties = ["allowMicroSegmentation", "epgLagPol", "switchType", "switchingMode", "vlanEncapMode", "portEncapVlan", "microSegVlan"] + for property in properties: + if property in vmmDomainProperties: + new_domain[property] = vmmDomainProperties[property] + + # If payload is empty, anp and EPG already exist at site level + if not payload: + op_path = domains_path + "/-" + payload = new_domain + + # If payload exists + else: + # If anp already exists at site level...(AND payload != epg as well?) + if "anpRef" not in payload: + payload["domainAssociations"] = [new_domain] + else: + payload["epgs"][0]["domainAssociations"] = [new_domain] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=domain_path)) + elif state == "present": + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=domain_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = new_domain + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py new file mode 100644 index 000000000..ffd3c682e --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py @@ -0,0 +1,392 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg_selector +short_description: Manage site-local EPG selector in schema templates +description: +- Manage EPG selector in schema template on Cisco ACI Multi-Site. +author: +- Cindy Zhao (@cizhao) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + required: true + selector: + description: + - The name of the selector. + type: str + expressions: + description: + - Expressions associated to this selector. + type: list + elements: dict + suboptions: + type: + description: + - The type of the expression. + - The type is custom or is one of region, zone and ip_address + - The type can be zone only when the site is AWS. + required: true + type: str + aliases: [ tag ] + operator: + description: + - The operator associated to the expression. + - Operator has_key or does_not_have_key is only available for custom type / tag + required: true + type: str + choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ] + value: + description: + - The value associated to the expression. + - If the operator is in or not_in, the value should be a comma separated string. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_site_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a selector to a site EPG + cisco.mso.mso_schema_site_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + selector: selector_1 + expressions: + - type: expression_1 + operator: in + value: test + state: present + delegate_to: localhost + +- name: Remove a Selector from a site EPG + cisco.mso.mso_schema_site_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + selector: selector_1 + state: absent + delegate_to: localhost + +- name: Query a specific Selector + cisco.mso.mso_schema_site_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + selector: selector_1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Selectors + cisco.mso.mso_schema_site_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: Site 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec + +EXPRESSION_KEYS = { + "ip_address": "ipAddress", + "region": "region", + "zone": "zone", +} + +EXPRESSION_OPERATORS = { + "not_in": "notIn", + "not_equals": "notEquals", + "has_key": "keyExist", + "does_not_have_key": "keyNotExist", + "in": "in", + "equals": "equals", +} + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + selector=dict(type="str"), + expressions=dict(type="list", elements="dict", options=mso_expression_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["selector"]], + ["state", "present", ["selector"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + selector = module.params.get("selector") + expressions = module.params.get("expressions") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get site + site_id = mso.lookup_site(site) + + # Get cloud type + site_type = mso.get_obj("sites", name=site).get("cloudProviders")[0] + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + payload = dict() + ops = [] + op_path = "" + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]] + anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]] + if anp not in anps_in_temp: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps_in_temp))) + else: + # Get anp index at template level + template_anp_idx = anps_in_temp.index(anp) + + # If anp not at site level but exists at template level + if anp_ref not in anps: + op_path = "/sites/{0}/anps/-".format(site_template) + payload.update( + anpRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + ), + ) + + else: + # Get anp index at site level + anp_idx = anps.index(anp_ref) + + # Get EPG + epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) + + # If anp exists at site level + if "anpRef" not in payload: + epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]] + + # If anp already at site level AND if epg not at site level (or) anp not at site level? + if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload: + epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]] + + # If EPG not at template level - Fail + if epg not in epgs_in_temp: + mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1}".format(epg, ", ".join(epgs_in_temp))) + + # EPG at template level but not at site level. Create payload at site level for EPG + else: + new_epg = dict( + epgRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + epgName=epg, + ) + ) + + # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload + if "anpRef" not in payload: + op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp) + payload = new_epg + else: + # If anp in payload, anp exists at site level. Update payload with EPG payload + payload["epgs"] = [new_epg] + + # Get index of EPG at site level + else: + epg_idx = epgs.index(epg_ref) + + # Get selectors + # If anp at site level and epg is at site level + if "anpRef" not in payload and "epgRef" not in payload: + if selector and " " in selector: + mso.fail_json(msg="There should not be any space in selector name.") + selectors = [s.get("name") for s in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"]] + if selector in selectors: + selector_idx = selectors.index(selector) + selector_path = "/sites/{0}/anps/{1}/epgs/{2}/selectors/{3}".format(site_template, anp, epg, selector_idx) + mso.existing = schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"][selector_idx] + + if state == "query": + if "anpRef" in payload: + mso.fail_json(msg="Anp '{anp}' does not exist in site level.".format(anp=anp)) + if "epgRef" in payload: + mso.fail_json(msg="Epg '{epg}' does not exist in site level.".format(epg=epg)) + if selector is None: + mso.existing = schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"] + elif not mso.existing: + mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector)) + mso.exit_json() + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=selector_path)) + elif state == "present": + # Get expressions + all_expressions = [] + if expressions: + for expression in expressions: + type = expression.get("type") + operator = expression.get("operator") + value = expression.get("value") + if " " in type: + mso.fail_json(msg="There should not be any space in 'type' attribute of expression '{0}'".format(type)) + if operator in ["has_key", "does_not_have_key"] and value: + mso.fail_json(msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, type)) + if operator in ["not_in", "in", "equals", "not_equals"] and not value: + mso.fail_json(msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, type)) + if type in ["region", "zone", "ip_address"]: + if type == "zone" and site_type != "aws": + mso.fail_json(msg="Type 'zone' is only supported for aws") + if operator in ["has_key", "does_not_have_key"]: + mso.fail_json(msg="Operator '{0}' is not supported when expression type is '{1}'".format(operator, type)) + type = EXPRESSION_KEYS.get(type) + else: + type = "Custom:" + type + all_expressions.append( + dict( + key=type, + operator=EXPRESSION_OPERATORS.get(operator), + value=value, + ) + ) + new_selector = dict( + name=selector, + expressions=all_expressions, + ) + + selectors_path = "/sites/{0}/anps/{1}/epgs/{2}/selectors/-".format(site_template, anp, epg) + + # if payload is empty, anp and epg already exist at site level + if not payload: + op_path = selectors_path + payload = new_selector + # if payload exist + else: + # if anp already exists at site level + if "anpRef" not in payload: + payload["selectors"] = [new_selector] + else: + payload["epgs"][0]["selectors"] = [new_selector] + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=selector_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = new_selector + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py new file mode 100644 index 000000000..01e2ac386 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg_staticleaf +short_description: Manage site-local EPG static leafs in schema template +description: +- Manage site-local EPG static leafs in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG. + type: str + required: true + pod: + description: + - The pod of the static leaf. + type: str + leaf: + description: + - The path of the static leaf. + type: str + aliases: [ name ] + vlan: + description: + - The VLAN id of the static leaf. + type: int + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_anp_epg +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new static leaf to a site EPG + cisco.mso.mso_schema_site_anp_epg_staticleaf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + leaf: Leaf1 + vlan: 123 + state: present + delegate_to: localhost + +- name: Remove a static leaf from a site EPG + cisco.mso.mso_schema_site_anp_epg_staticleaf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + leaf: Leaf1 + state: absent + delegate_to: localhost + +- name: Query a specific site EPG static leaf + cisco.mso.mso_schema_site_anp_epg_staticleaf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + leaf: Leaf1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site EPG static leafs + cisco.mso.mso_schema_site_anp_epg_staticleaf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + pod=dict(type="str"), # This parameter is not required for querying all objects + leaf=dict(type="str", aliases=["name"]), + vlan=dict(type="int"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["pod", "leaf", "vlan"]], + ["state", "present", ["pod", "leaf", "vlan"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + pod = module.params.get("pod") + leaf = module.params.get("leaf") + vlan = module.params.get("vlan") + state = module.params.get("state") + + leafpath = "topology/{0}/node-{1}".format(pod, leaf) + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ", ".join(sites))) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj.get("sites")[site_idx]["anps"]] + if anp_ref not in anps: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps))) + anp_idx = anps.index(anp_ref) + + # Get EPG + epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) + epgs = [e.get("epgRef") for e in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"]] + if epg_ref not in epgs: + mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ", ".join(epgs))) + epg_idx = epgs.index(epg_ref) + + # Get Leaf + leafs = [(leaf.get("path"), leaf.get("portEncapVlan")) for leaf in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticLeafs"]] + if (leafpath, vlan) in leafs: + leaf_idx = leafs.index((leafpath, vlan)) + # FIXME: Changes based on index are DANGEROUS + leaf_path = "/sites/{0}/anps/{1}/epgs/{2}/staticLeafs/{3}".format(site_template, anp, epg, leaf_idx) + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticLeafs"][leaf_idx] + + if state == "query": + if leaf is None or vlan is None: + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticLeafs"] + elif not mso.existing: + mso.fail_json(msg="Static leaf '{leaf}/{vlan}' not found".format(leaf=leaf, vlan=vlan)) + mso.exit_json() + + leafs_path = "/sites/{0}/anps/{1}/epgs/{2}/staticLeafs".format(site_template, anp, epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=leaf_path)) + + elif state == "present": + payload = dict( + path=leafpath, + portEncapVlan=vlan, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=leaf_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=leafs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py new file mode 100644 index 000000000..d05336c52 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py @@ -0,0 +1,444 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg_staticport +short_description: Manage site-local EPG static ports in schema template +description: +- Manage site-local EPG static ports in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG. + type: str + required: true + type: + description: + - The path type of the static port + - vpc is used for a Virtual Port Channel + - dpc is used for a Direct Port Channel + - port is used for a single interface + type: str + choices: [ port, vpc, dpc ] + default: port + pod: + description: + - The pod of the static port. + type: str + leaf: + description: + - The leaf of the static port. + type: str + fex: + description: + - The fex id of the static port. + type: str + path: + description: + - The path of the static port. + type: str + vlan: + description: + - The port encap VLAN id of the static port. + type: int + deployment_immediacy: + description: + - The deployment immediacy of the static port. + - C(immediate) means B(Deploy immediate). + - C(lazy) means B(deploy on demand). + type: str + choices: [ immediate, lazy ] + default: lazy + mode: + description: + - The mode of the static port. + - C(native) means B(Access (802.1p)). + - C(regular) means B(Trunk). + - C(untagged) means B(Access (untagged)). + type: str + choices: [ native, regular, untagged ] + default: untagged + primary_micro_segment_vlan: + description: + - Primary micro-seg VLAN of static port. + type: int + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing an object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_anp_epg +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new static port to a site EPG + cisco.mso.mso_schema_site_anp_epg_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + deployment_immediacy: immediate + state: present + delegate_to: localhost + +- name: Add a new static fex port to a site EPG + mso_schema_site_anp_epg_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + fex: 151 + path: eth1/1 + vlan: 126 + deployment_immediacy: lazy + state: present + delegate_to: localhost + +- name: Add a new static VPC to a site EPG + mso_schema_site_anp_epg_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + pod: pod-1 + leaf: 101-102 + path: ansible_polgrp + vlan: 127 + type: vpc + mode: untagged + deployment_immediacy: lazy + state: present + delegate_to: localhost + +- name: Remove a static port from a site EPG + cisco.mso.mso_schema_site_anp_epg_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + path: eth1/1 + state: absent + delegate_to: localhost + +- name: Query a specific site EPG static port + cisco.mso.mso_schema_site_anp_epg_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + type: port + pod: pod-1 + leaf: 101 + path: eth1/1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site EPG static ports + cisco.mso.mso_schema_site_anp_epg_staticport: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + type=dict(type="str", default="port", choices=["port", "vpc", "dpc"]), + pod=dict(type="str"), # This parameter is not required for querying all objects + leaf=dict(type="str"), # This parameter is not required for querying all objects + fex=dict(type="str"), # This parameter is not required for querying all objects + path=dict(type="str"), # This parameter is not required for querying all objects + vlan=dict(type="int"), # This parameter is not required for querying all objects + primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects + deployment_immediacy=dict(type="str", default="lazy", choices=["immediate", "lazy"]), + mode=dict(type="str", default="untagged", choices=["native", "regular", "untagged"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["type", "pod", "leaf", "path", "vlan"]], + ["state", "present", ["type", "pod", "leaf", "path", "vlan"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + path_type = module.params.get("type") + pod = module.params.get("pod") + leaf = module.params.get("leaf") + fex = module.params.get("fex") + path = module.params.get("path") + vlan = module.params.get("vlan") + primary_micro_segment_vlan = module.params.get("primary_micro_segment_vlan") + deployment_immediacy = module.params.get("deployment_immediacy") + mode = module.params.get("mode") + state = module.params.get("state") + + if path_type == "port" and fex is not None: + # Select port path for fex if fex param is used + portpath = "topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]".format(pod, leaf, fex, path) + elif path_type == "vpc": + portpath = "topology/{0}/protpaths-{1}/pathep-[{2}]".format(pod, leaf, path) + else: + portpath = "topology/{0}/paths-{1}/pathep-[{2}]".format(pod, leaf, path) + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json( + msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. " + "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list)) + ) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + payload = dict() + ops = [] + op_path = "" + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]] + anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]] + if anp not in anps_in_temp: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps))) + else: + # Update anp index at template level + template_anp_idx = anps_in_temp.index(anp) + + # If anp not at site level but exists at template level + if anp_ref not in anps: + op_path = "/sites/{0}/anps/-".format(site_template) + payload.update( + anpRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + ), + ) + + else: + # Update anp index at site level + anp_idx = anps.index(anp_ref) + + # Get EPG + epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) + + # If anp exists at site level + if "anpRef" not in payload: + epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]] + + # If anp already at site level AND if epg not at site level (or) anp not at site level + if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload: + epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]] + + # If EPG not at template level - Fail + if epg not in epgs_in_temp: + mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ", ".join(epgs_in_temp), epg_ref)) + + # EPG at template level but not at site level. Create payload at site level for EPG + else: + new_epg = dict( + epgRef=dict( + schemaId=schema_id, + templateName=template, + anpName=anp, + epgName=epg, + ) + ) + + # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload + if "anpRef" not in payload: + op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp) + payload = new_epg + else: + # If anp in payload, anp exists at site level. Update payload with EPG payload + payload["epgs"] = [new_epg] + + # Update index of EPG at site level + else: + epg_idx = epgs.index(epg_ref) + + # Get Leaf + # If anp at site level and epg is at site level + if "anpRef" not in payload and "epgRef" not in payload: + portpaths = [p.get("path") for p in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"]] + if portpath in portpaths: + portpath_idx = portpaths.index(portpath) + port_path = "/sites/{0}/anps/{1}/epgs/{2}/staticPorts/{3}".format(site_template, anp, epg, portpath_idx) + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"][portpath_idx] + + if state == "query": + if leaf is None or vlan is None: + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"] + elif not mso.existing: + mso.fail_json(msg="Static port '{portpath}' not found".format(portpath=portpath)) + mso.exit_json() + + ports_path = "/sites/{0}/anps/{1}/epgs/{2}/staticPorts".format(site_template, anp, epg) + ops = [] + new_leaf = dict( + deploymentImmediacy=deployment_immediacy, + mode=mode, + path=portpath, + portEncapVlan=vlan, + type=path_type, + ) + if primary_micro_segment_vlan: + new_leaf.update(microSegVlan=primary_micro_segment_vlan) + + # If payload is empty, anp and EPG already exist at site level + if not payload: + op_path = ports_path + "/-" + payload = new_leaf + + # If payload exists + else: + # If anp already exists at site level + if "anpRef" not in payload: + payload["staticPorts"] = [new_leaf] + else: + payload["epgs"][0]["staticPorts"] = [new_leaf] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=port_path)) + + elif state == "present": + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=port_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = new_leaf + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py new file mode 100644 index 000000000..e5aaeba37 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py @@ -0,0 +1,281 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_anp_epg_subnet +short_description: Manage site-local EPG subnets in schema template +description: +- Manage site-local EPG subnets in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG. + type: str + required: true + subnet: + description: + - The IP range in CIDR notation. + type: str + required: true + aliases: [ ip ] + description: + description: + - The description of this subnet. + type: str + scope: + description: + - The scope of the subnet. + type: str + default: private + choices: [ private, public ] + shared: + description: + - Whether this subnet is shared between VRFs. + type: bool + default: false + no_default_gateway: + description: + - Whether this subnet has a default gateway. + type: bool + default: false + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_anp_epg +- module: cisco.mso.mso_schema_template_anp_epg_subnet +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new subnet to a site EPG + cisco.mso.mso_schema_site_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + subnet: 10.0.0.0/24 + state: present + delegate_to: localhost + +- name: Remove a subnet from a site EPG + cisco.mso.mso_schema_site_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + subnet: 10.0.0.0/24 + state: absent + delegate_to: localhost + +- name: Query a specific site EPG subnet + cisco.mso.mso_schema_site_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + epg: EPG1 + subnet: 10.0.0.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site EPG subnets + cisco.mso.mso_schema_site_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + anp: ANP1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_epg_subnet_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + argument_spec.update(mso_epg_subnet_spec()) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet"]], + ["state", "present", ["subnet"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + subnet = module.params.get("subnet") + description = module.params.get("description") + scope = module.params.get("scope") + shared = module.params.get("shared") + no_default_gateway = module.params.get("no_default_gateway") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ", ".join(sites))) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get ANP + anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) + anps = [a.get("anpRef") for a in schema_obj.get("sites")[site_idx]["anps"]] + if anp_ref not in anps: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps))) + anp_idx = anps.index(anp_ref) + + # Get EPG + epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) + epgs = [e.get("epgRef") for e in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"]] + if epg_ref not in epgs: + mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ", ".join(epgs))) + epg_idx = epgs.index(epg_ref) + + # Get Subnet + subnets = [s.get("ip") for s in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"]] + if subnet in subnets: + subnet_idx = subnets.index(subnet) + # FIXME: Changes based on index are DANGEROUS + subnet_path = "/sites/{0}/anps/{1}/epgs/{2}/subnets/{3}".format(site_template, anp, epg, subnet_idx) + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"][subnet_idx] + + if state == "query": + if subnet is None: + mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"] + elif not mso.existing: + mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet)) + mso.exit_json() + + subnets_path = "/sites/{0}/anps/{1}/epgs/{2}/subnets".format(site_template, anp, epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=subnet_path)) + + elif state == "present": + if not mso.existing: + if description is None: + description = subnet + if scope is None: + scope = "private" + if shared is None: + shared = False + if no_default_gateway is None: + no_default_gateway = False + + payload = dict( + ip=subnet, + description=description, + scope=scope, + shared=shared, + noDefaultGateway=no_default_gateway, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=subnet_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py new file mode 100644 index 000000000..35c352fb6 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_bd +short_description: Manage site-local Bridge Domains (BDs) in schema template +description: +- Manage site-local BDs in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + bd: + description: + - The name of the BD to manage. + type: str + aliases: [ name ] + host_route: + description: + - Whether host-based routing is enabled. + type: bool + svi_mac: + description: + - SVI MAC Address + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_site_bd_l3out +- module: cisco.mso.mso_schema_site_bd_subnet +- module: cisco.mso.mso_schema_template_bd +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site BD + cisco.mso.mso_schema_site_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + state: present + delegate_to: localhost + +- name: Remove a site BD + cisco.mso.mso_schema_site_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + state: absent + delegate_to: localhost + +- name: Query a specific site BD + cisco.mso.mso_schema_site_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site BDs + cisco.mso.mso_schema_site_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + bd=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + host_route=dict(type="bool"), + svi_mac=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["bd"]], + ["state", "present", ["bd"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + bd = module.params.get("bd") + host_route = module.params.get("host_route") + svi_mac = module.params.get("svi_mac") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get BD + bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd) + bds = [v.get("bdRef") for v in schema_obj.get("sites")[site_idx]["bds"]] + if bd is not None and bd_ref in bds: + bd_idx = bds.index(bd_ref) + bd_path = "/sites/{0}/bds/{1}".format(site_template, bd) + mso.existing = schema_obj.get("sites")[site_idx]["bds"][bd_idx] + mso.existing["bdRef"] = mso.dict_from_ref(mso.existing.get("bdRef")) + + if state == "query": + if bd is None: + mso.existing = schema_obj.get("sites")[site_idx]["bds"] + for bd in mso.existing: + bd["bdRef"] = mso.dict_from_ref(bd.get("bdRef")) + elif not mso.existing: + mso.fail_json(msg="BD '{bd}' not found".format(bd=bd)) + mso.exit_json() + + bds_path = "/sites/{0}/bds".format(site_template) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=bd_path)) + + elif state == "present": + if not mso.existing: + if host_route is None: + host_route = False + + payload = dict( + bdRef=dict( + schemaId=schema_id, + templateName=template, + bdName=bd, + ), + hostBasedRouting=host_route, + ) + if svi_mac is not None: + payload.update(mac=svi_mac) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=bd_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=bds_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py new file mode 100644 index 000000000..8f9581294 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py @@ -0,0 +1,255 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_bd_l3out +short_description: Manage site-local BD l3out's in schema template +description: +- Manage site-local BDs l3out's in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Anvitha Jain (@anvitha-jain) +- Akini Ross (@akinross) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + bd: + description: + - The name of the BD. + type: str + required: true + aliases: [ name ] + l3out: + description: + - The l3out associated to this BD. + type: dict + suboptions: + name: + description: + - The name of the l3out to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced l3out. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced l3out. + - If this parameter is unspecified, it defaults to the current schema. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_bd +- module: cisco.mso.mso_schema_template_bd +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site BD l3out + cisco.mso.mso_schema_site_bd_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + l3out: + name: L3out1 + state: present + delegate_to: localhost + +- name: Add a new site BD l3out with different schema and template + cisco.mso.mso_schema_site_bd_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + l3out: + name: L3out1 + schema: Schema2 + template: Template2 + state: present + delegate_to: localhost + +- name: Remove a site BD l3out + cisco.mso.mso_schema_site_bd_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + l3out: + name: L3out1 + state: absent + delegate_to: localhost + +- name: Query a specific site BD l3out + cisco.mso.mso_schema_site_bd_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + l3out: + name: L3out1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site BD l3outs + cisco.mso.mso_schema_site_bd_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec +from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + bd=dict(type="str", required=True), + l3out=dict(type="dict", options=mso_reference_spec(), aliases=["name"]), # This parameter is not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["l3out"]], + ["state", "present", ["l3out"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + bd = module.params.get("bd") + l3out = module.params.get("l3out") + state = module.params.get("state") + + mso = MSOModule(module) + + mso_schema = MSOSchema(mso, schema, template, site) + mso_objects = mso_schema.schema_objects + + mso_schema.set_template_bd(bd) + mso_schema.set_site_bd(bd, fail_module=False) + + bd_path = "/sites/{0}-{1}/bds".format(mso_objects.get("site").details.get("siteId"), template) + ops = [] + payload = dict() + + if l3out: + l3out_schema_id = mso.lookup_schema(l3out.get("schema")) if l3out.get("schema") else mso_schema.id + l3out_template = l3out.get("template") if l3out.get("template") else template + l3out_ref = mso.l3out_ref(schema_id=l3out_schema_id, template=l3out_template, l3out=l3out.get("name")) + if not mso_objects.get("site_bd"): + payload = dict(bdRef=dict(schemaId=mso_schema.id, templateName=template, bdName=bd), l3Outs=[l3out.get("name")], l3OutRefs=[l3out_ref]) + else: + mso_objects.get("site_bd").details["bdRef"] = dict(schemaId=mso_schema.id, templateName=template, bdName=bd) + l3out_refs = mso_objects.get("site_bd").details.get("l3OutRefs", []) + l3outs = mso_objects.get("site_bd").details.get("l3Outs", []) + # check on name because refs are handled differently between versions + if l3out.get("name") in l3outs: + mso.existing = mso.make_reference(l3out, "l3out", l3out_schema_id, l3out_template) + + if state == "query": + if l3out is None: + if "l3OutRefs" in mso_objects.get("site_bd", {}).details.keys(): + mso.existing = [mso.dict_from_ref(l3) for l3 in mso_objects.get("site_bd", {}).details.get("l3OutRefs", [])] + else: + mso.existing = [dict(l3outName=l3) for l3 in mso_objects.get("site_bd", {}).details.get("l3Outs", [])] + elif not mso.existing: + mso.fail_json(msg="L3out '{0}' not found".format(l3out.get("name"))) + mso.exit_json() + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + if l3out.get("name") in l3outs: + del l3outs[l3outs.index(l3out.get("name"))] + if l3out_ref in l3out_refs: + del l3out_refs[l3out_refs.index(l3out_ref)] + ops.append(dict(op="replace", path="{0}/{1}".format(bd_path, bd), value=mso_objects.get("site_bd").details)) + + elif state == "present": + if payload: + ops.append(dict(op="add", path="{0}/-".format(bd_path), value=payload)) + elif not mso.existing: + l3outs.append(l3out.get("name")) + l3out_refs.append(l3out_ref) + ops.append(dict(op="replace", path="{0}/{1}".format(bd_path, bd), value=mso_objects.get("site_bd").details)) + mso.existing = mso.make_reference(l3out, "l3out", l3out_schema_id, l3out_template) + + if not module.check_mode: + mso.request(mso_schema.path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py new file mode 100644 index 000000000..c4ab52eec --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_bd_subnet +short_description: Manage site-local BD subnets in schema template +description: +- Manage site-local BD subnets in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + bd: + description: + - The name of the BD. + type: str + required: true + aliases: [ name ] + subnet: + description: + - The IP range in CIDR notation. + type: str + aliases: [ ip ] + description: + description: + - The description of this subnet. + type: str + scope: + description: + - The scope of the subnet. + type: str + choices: [ private, public ] + shared: + description: + - Whether this subnet is shared between VRFs. + type: bool + default: false + no_default_gateway: + description: + - Whether this subnet has a default gateway. + type: bool + default: false + querier: + description: + - Whether this subnet is an IGMP querier. + type: bool + default: false + primary: + description: + - Treat as Primary Subnet. + - There can be only one primary subnet per address family under a BD. + - This option can only be used on versions of MSO that are 3.1.1h or greater. + type: bool + default: false + is_virtual_ip: + description: + - Treat as Virtual IP Address + type: bool + default: false + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_bd +- module: cisco.mso.mso_schema_template_bd +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site BD subnet + cisco.mso.mso_schema_site_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + subnet: 11.11.11.0/24 + state: present + delegate_to: localhost + +- name: Remove a site BD subnet + cisco.mso.mso_schema_site_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + subnet: 11.11.11.0/24 + state: absent + delegate_to: localhost + +- name: Query a specific site BD subnet + cisco.mso.mso_schema_site_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + subnet: 11.11.11.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site BD subnets + cisco.mso.mso_schema_site_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + bd: BD1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_subnet_spec +from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update(mso_subnet_spec()) + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + bd=dict(type="str", aliases=["name"], required=True), + subnet=dict(type="str", aliases=["ip"]), + description=dict(type="str"), + scope=dict(type="str", choices=["private", "public"]), + shared=dict(type="bool", default=False), + no_default_gateway=dict(type="bool", default=False), + querier=dict(type="bool", default=False), + primary=dict(type="bool", default=False), + is_virtual_ip=dict(type="bool", default=False), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet"]], + ["state", "present", ["subnet"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + bd = module.params.get("bd") + ip = module.params.get("subnet") + description = module.params.get("description") + scope = module.params.get("scope") + shared = module.params.get("shared") + no_default_gateway = module.params.get("no_default_gateway") + querier = module.params.get("querier") + primary = module.params.get("primary") + is_virtual_ip = module.params.get("is_virtual_ip") + state = module.params.get("state") + + mso = MSOModule(module) + + mso_schema = MSOSchema(mso, schema, template, site) + mso_objects = mso_schema.schema_objects + + mso_schema.set_template_bd(bd) + if mso_objects.get("template_bd") and mso_objects.get("template_bd").details.get("l2Stretch") is True and state == "present": + mso.fail_json( + msg="The l2Stretch of template bd should be false in order to create a site bd subnet. " "Set l2Stretch as false using mso_schema_template_bd" + ) + + if state == "query": + mso_schema.set_site_bd(bd) + if not ip: + mso.existing = mso_objects.get("site_bd").details.get("subnets") + else: + mso_schema.set_site_bd_subnet(ip) + mso.existing = mso_objects.get("site_bd_subnet").details + mso.exit_json() + + mso_schema.set_site_bd(bd, fail_module=False) + + subnet = None + if mso_objects.get("site_bd"): + mso_schema.set_site_bd_subnet(ip, fail_module=False) + subnet = mso_objects.get("site_bd_subnet") + + mso.previous = mso.existing = subnet.details if subnet else mso.existing + + bd_path = "/sites/{0}-{1}/bds".format(mso_objects.get("site").details.get("siteId"), template) + subnet_path = "{0}/{1}/subnets".format(bd_path, bd) + ops = [] + + if state == "absent": + if subnet: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=subnet_path)) + + elif state == "present": + if not mso_objects.get("site_bd"): + bd_payload = dict( + bdRef=dict( + schemaId=mso_schema.id, + templateName=template, + bdName=bd, + ), + hostBasedRouting=False, + ) + ops.append(dict(op="add", path=bd_path + "/-", value=bd_payload)) + + if not subnet: + if description is None: + description = ip + if scope is None: + scope = "private" + + subnet_payload = dict( + ip=ip, + description=description, + scope=scope, + shared=shared, + noDefaultGateway=no_default_gateway, + virtual=is_virtual_ip, + querier=querier, + primary=primary, + ) + + mso.sanitize(subnet_payload, collate=True) + + if subnet: + ops.append(dict(op="replace", path="{0}/{1}".format(subnet_path, subnet.index), value=mso.sent)) + else: + ops.append(dict(op="add", path="{0}/-".format(subnet_path), value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(mso_schema.path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py new file mode 100644 index 000000000..dff4cf152 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_external_epg +short_description: Manage External EPG in schema of sites +description: +- Manage External EPG in schema of sites on Cisco ACI Multi-Site. +- This module can only be used on versions of MSO that are 3.3 or greater. +author: +- Anvitha Jain (@anvitha-jain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + l3out: + description: + - The L3Out associated with the external epg. + - Required when site is of type on-premise. + type: str + external_epg: + description: + - The name of the External EPG to be managed. + type: str + aliases: [ name ] + site: + description: + - The name of the site. + type: str + required: true + route_reachability: + description: + - Configures if an external EPG route is pointing to the internet or to an external remote network. + - Only available when associated with an azure site. + type: str + choices: [ internet, site-ext ] + default: internet + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_external_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a Site External EPG + cisco.mso.mso_schema_site_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + l3out: L3out1 + state: present + delegate_to: localhost + +- name: Remove a Site External EPG + cisco.mso.mso_schema_site_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + l3out: L3out1 + state: absent + delegate_to: localhost + +- name: Query a Site External EPG + cisco.mso.mso_schema_site_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + l3out: L3out1 + state: query + delegate_to: localhost + +- name: Query all Site External EPGs + cisco.mso.mso_schema_site_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + site=dict(type="str", required=True), + l3out=dict(type="str"), + external_epg=dict(type="str", aliases=["name"]), + route_reachability=dict(type="str", default="internet", choices=["internet", "site-ext"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["external_epg"]], + ["state", "present", ["external_epg"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template") + site = module.params.get("site") + external_epg = module.params.get("external_epg") + l3out = module.params.get("l3out") + route_reachability = module.params.get("route_reachability") + state = module.params.get("state") + + mso = MSOModule(module) + + mso_schema = MSOSchema(mso, schema, template, site) + mso_objects = mso_schema.schema_objects + + mso_schema.set_template_external_epg(external_epg, fail_module=False) + + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(mso_objects.get("site").details.get("siteId"), template) + + payload = dict() + op_path = "/sites/{0}/externalEpgs/-".format(site_template) + + # Get template External EPG + if mso_objects.get("template_external_epg") is not None: + ext_epg_ref = mso_objects.get("template_external_epg").details.get("externalEpgRef") + external_epgs = [e.get("externalEpgRef") for e in mso_objects.get("site").details.get("externalEpgs")] + + # Get Site External EPG + if ext_epg_ref in external_epgs: + external_epg_idx = external_epgs.index(ext_epg_ref) + mso.existing = mso_objects.get("site").details.get("externalEpgs")[external_epg_idx] + op_path = "/sites/{0}/externalEpgs/{1}".format(site_template, external_epg) + + ops = [] + l3out_dn = "" + + if state == "query": + if external_epg is None: + mso.existing = mso_objects.get("site").details.get("externalEpgs") + elif not mso.existing: + mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg)) + mso.exit_json() + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=op_path)) + + elif state == "present": + # Get external EPGs type from template level and verify template_external_epg type. + if mso_objects.get("template_external_epg").details.get("extEpgType") != "cloud": + if l3out is not None: + path = "tenants/{0}".format(mso_objects.get("template").details.get("tenantId")) + tenant_name = mso.request(path, method="GET").get("name") + l3out_dn = "uni/tn-{0}/out-{1}".format(tenant_name, l3out) + else: + mso.fail_json(msg="L3Out cannot be empty when template external EPG type is 'on-premise'.") + + payload = dict( + externalEpgRef=dict( + schemaId=mso_schema.id, + templateName=template, + externalEpgName=external_epg, + ), + l3outDn=l3out_dn, + l3outRef=dict( + schemaId=mso_schema.id, + templateName=template, + l3outName=l3out, + ), + routeReachabilityInternetType=route_reachability, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=op_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(mso_schema.path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py new file mode 100644 index 000000000..001ebf2b5 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py @@ -0,0 +1,291 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_external_epg_selector +short_description: Manage External EPG selector in schema of cloud sites +description: +- Manage External EPG selector in schema of cloud sites on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + external_epg: + description: + - The name of the External EPG to be managed. + type: str + required: true + site: + description: + - The name of the cloud site. + type: str + required: true + selector: + description: + - The name of the selector. + type: str + expressions: + description: + - Expressions associated to this selector. + type: list + elements: dict + suboptions: + type: + description: + - The name of the expression which in this case is always IP address. + required: true + type: str + choices: [ ip_address ] + operator: + description: + - The operator associated with the expression which in this case is always equals. + required: true + type: str + choices: [ equals ] + value: + description: + - The value of the IP Address / Subnet associated with the expression. + required: true + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_external_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a selector to an External EPG + cisco.mso.mso_schema_site_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: ansible_test + template: Template1 + site: azure_ansible_test + external_epg: ext1 + selector: test + expressions: + - type: ip_address + operator: equals + value: 10.0.0.0 + state: present + delegate_to: localhost + +- name: Remove a Selector + cisco.mso.mso_schema_site_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: ansible_test + template: Template1 + site: azure_ansible_test + external_epg: ext1 + selector: test + state: absent + delegate_to: localhost + +- name: Query a specific Selector + cisco.mso.mso_schema_site_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: ansible_test + template: Template1 + site: azure_ansible_test + external_epg: ext1 + selector: selector_1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Selectors + cisco.mso.mso_schema_site_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: ansible_test + template: Template1 + site: azure_ansible_test + external_epg: ext1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec_ext_epg + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + site=dict(type="str", required=True), + external_epg=dict(type="str", required=True), + selector=dict(type="str"), + expressions=dict(type="list", elements="dict", options=mso_expression_spec_ext_epg()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + site = module.params.get("site") + external_epg = module.params.get("external_epg") + selector = module.params.get("selector") + expressions = module.params.get("expressions") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json( + msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. " + "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list)) + ) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + payload = dict() + op_path = "" + + # Get External EPG + ext_epg_ref = mso.ext_epg_ref(schema_id=schema_id, template=template, external_epg=external_epg) + external_epgs = [e.get("externalEpgRef") for e in schema_obj.get("sites")[site_idx]["externalEpgs"]] + + if ext_epg_ref not in external_epgs: + op_path = "/sites/{0}/externalEpgs/-".format(site_template) + payload = dict( + externalEpgRef=dict( + schemaId=schema_id, + templateName=template, + externalEpgName=external_epg, + ), + l3outDn="", + ) + + else: + external_epg_idx = external_epgs.index(ext_epg_ref) + + # Get Selector + selectors = [s.get("name") for s in schema_obj["sites"][site_idx]["externalEpgs"][external_epg_idx]["subnets"]] + if selector in selectors: + selector_idx = selectors.index(selector) + selector_path = "/sites/{0}/externalEpgs/{1}/subnets/{2}".format(site_template, external_epg, selector_idx) + mso.existing = schema_obj["sites"][site_idx]["externalEpgs"][external_epg_idx]["subnets"][selector_idx] + + selectors_path = "/sites/{0}/externalEpgs/{1}/subnets/-".format(site_template, external_epg) + ops = [] + + if state == "query": + if selector is None: + mso.existing = schema_obj["sites"][site_idx]["externalEpgs"][external_epg_idx]["subnets"] + elif not mso.existing: + mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector)) + mso.exit_json() + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=selector_path)) + + elif state == "present": + # Get expressions + types = dict(ip_address="ipAddress") + all_expressions = [] + if expressions: + for expression in expressions: + type_val = expression.get("type") + operator = expression.get("operator") + value = expression.get("value") + all_expressions.append( + dict( + key=types.get(type_val), + operator=operator, + value=value, + ) + ) + else: + mso.fail_json(msg="Missing expressions in selector") + + subnets = dict(name=selector, ip=all_expressions[0]["value"]) + + if not external_epgs: + payload["subnets"] = [subnets] + else: + payload = subnets + op_path = selectors_path + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=selector_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py new file mode 100644 index 000000000..4741e4bd7 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py @@ -0,0 +1,246 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_l3out +short_description: Manage site-local layer3 Out (L3Outs) in schema template +description: +- Manage site-local L3Outs in schema template on Cisco ACI Multi-Site. +- This module can only be used on versions of MSO that are 3.0 or greater. +- NOTE - Usage of this module for version lesser than 3.0 might break the MSO. +author: +- Anvitha Jain (@anvitha-jain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The VRF associated to this L3out. + type: dict + suboptions: + name: + description: + - The name of the VRF to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + l3out: + description: + - The name of the l3out to manage. + type: str + aliases: [ name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_template_l3out +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site L3Out + cisco.mso.mso_schema_site_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + l3out: L3out1 + vrf: + name: vrfName + template: TemplateName + schema: schemaName + state: present + delegate_to: localhost + +- name: Remove a site L3Out + cisco.mso.mso_schema_site_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + l3out: L3out1 + state: absent + delegate_to: localhost + +- name: Query a specific site L3Out + cisco.mso.mso_schema_site_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + l3out: L3out1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site l3outs + cisco.mso.mso_schema_site_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="dict", options=mso_reference_spec()), + l3out=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["l3out"]], + ["state", "present", ["l3out", "vrf"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + l3out = module.params.get("l3out") + vrf = module.params.get("vrf") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided template '{0}' is not associated to site".format(template)) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get l3out + l3out_ref = mso.l3out_ref(schema_id=schema_id, template=template, l3out=l3out) + l3outs = [v.get("l3outRef") for v in schema_obj.get("sites")[site_idx]["intersiteL3outs"]] + + if l3out is not None and l3out_ref in l3outs: + l3out_idx = l3outs.index(l3out_ref) + l3out_path = "/sites/{0}/intersiteL3outs/{1}".format(site_template, l3out) + mso.existing = schema_obj.get("sites")[site_idx]["intersiteL3outs"][l3out_idx] + + if state == "query": + if l3out is None: + mso.existing = schema_obj.get("sites")[site_idx]["intersiteL3outs"] + for l3out in mso.existing: + l3out["l3outRef"] = mso.dict_from_ref(l3out.get("l3outRef")) + elif not mso.existing: + mso.fail_json(msg="L3Out '{l3out}' not found".format(l3out=l3out)) + mso.exit_json() + + l3outs_path = "/sites/{0}/intersiteL3outs".format(site_template) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=l3out_path)) + + elif state == "present": + vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template) + + payload = dict( + l3outRef=dict( + schemaId=schema_id, + templateName=template, + l3outName=l3out, + ), + vrfRef=vrf_ref, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=l3out_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=l3outs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py new file mode 100644 index 000000000..9495a501d --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_service_graph +short_description: Manage Service Graph in schema sites +description: +- Manage Service Graph in schema sites on Cisco ACI Multi-Site. +- This module is only supported in MSO/NDO version 3.3 and above. +author: +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + tenant: + description: + - The name of the tenant. + type: str + service_graph: + description: + - The name of the Service Graph to manage. + type: str + aliases: [ name ] + devices: + description: + - A list of devices to be associated with the Service Graph. + type: list + elements: dict + suboptions: + name: + description: + - The name of the device + required: true + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a Service Graph + cisco.mso.mso_schema_site_service_graph_node: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + service_graph: SG1 + site: site1 + tenant: tenant1 + devices: + - name: ansible_test_firewall + - name: ansible_test_adc + - name: ansible_test_other + state: present + delegate_to: localhost + +- name: Remove a Service Graph + cisco.mso.mso_schema_site_service_graph_node: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + service_graph: SG1 + site: site1 + state: absent + delegate_to: localhost + +- name: Query a specific Service Graph + cisco.mso.mso_schema_site_service_graph_node: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + service_graph: SG1 + site: site1 + state: query + delegate_to: localhost + +- name: Query all Service Graphs + cisco.mso.mso_schema_site_service_graph_node: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + site: site1 + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_node_device_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + service_graph=dict(type="str", aliases=["name"]), + tenant=dict(type="str"), + site=dict(type="str", required=True), + devices=dict(type="list", elements="dict", options=mso_service_graph_node_device_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["service_graph"]], + ["state", "present", ["service_graph", "devices"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + service_graph = module.params.get("service_graph") + devices = module.params.get("devices") + site = module.params.get("site") + tenant = module.params.get("tenant") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = schema_obj.get("templates") + template_names = [t.get("name") for t in templates] + if template not in template_names: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(template_names)) + ) + template_idx = template_names.index(template) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + mso.existing = {} + service_graph_idx = None + + # Get Service Graph + service_graph_ref = mso.service_graph_ref(schema_id=schema_id, template=template, service_graph=service_graph) + service_graph_refs = [f.get("serviceGraphRef") for f in schema_obj.get("sites")[site_idx]["serviceGraphs"]] + if service_graph is not None and service_graph_ref in service_graph_refs: + service_graph_idx = service_graph_refs.index(service_graph_ref) + mso.existing = schema_obj.get("sites")[site_idx]["serviceGraphs"][service_graph_idx] + + if state == "query": + if service_graph is None: + mso.existing = schema_obj.get("sites")[site_idx]["serviceGraphs"] + elif service_graph is not None and service_graph_idx is None: + mso.fail_json(msg="Service Graph '{service_graph}' not found".format(service_graph=service_graph)) + mso.exit_json() + + service_graphs_path = "/sites/{0}/serviceGraphs/-".format(site_template) + service_graph_path = "/sites/{0}/serviceGraphs/{1}".format(site_template, service_graph) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=service_graph_path)) + + elif state == "present": + devices_payload = [] + service_graphs = templates[template_idx]["serviceGraphs"] + for graph in service_graphs: + if graph.get("name") == service_graph: + service_node_types_from_template = graph["serviceNodes"] + user_number_devices = len(devices) + number_of_nodes_in_template = len(service_node_types_from_template) + if user_number_devices != number_of_nodes_in_template: + mso.fail_json( + msg="Service Graph '{0}' has '{1}' service node type(s) but '{2}' service node(s) were given for the service graph".format( + service_graph, number_of_nodes_in_template, user_number_devices + ) + ) + + if devices is not None: + service_node_type_names_from_template = [type.get("name") for type in service_node_types_from_template] + for index, device in enumerate(devices): + template_node_type = service_node_type_names_from_template[index] + apic_type = "OTHERS" + if template_node_type == "firewall": + apic_type = "FW" + elif template_node_type == "load-balancer": + apic_type = "ADC" + query_device_data = mso.lookup_service_node_device(site_id, tenant, device.get("name"), apic_type) + devices_payload.append( + dict( + device=dict( + dn=query_device_data.get("dn"), + funcTyp=query_device_data.get("funcType"), + ), + serviceNodeRef=dict( + serviceNodeName=template_node_type, + serviceGraphName=service_graph, + templateName=template, + schemaId=schema_id, + ), + ), + ) + + payload = dict( + serviceGraphRef=dict( + serviceGraphName=service_graph, + templateName=template, + schemaId=schema_id, + ), + serviceNodes=devices_payload, + ) + + mso.sanitize(payload, collate=True) + + if not mso.existing: + ops.append(dict(op="add", path=service_graphs_path, value=payload)) + else: + ops.append(dict(op="replace", path=service_graph_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py new file mode 100644 index 000000000..a0b864a8b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_vrf +short_description: Manage site-local VRFs in schema template +description: +- Manage site-local VRFs in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The name of the VRF to manage. + type: str + aliases: [ name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_template_vrf +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site VRF + cisco.mso.mso_schema_site_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + state: present + delegate_to: localhost + +- name: Remove a site VRF + cisco.mso.mso_schema_site_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + state: absent + delegate_to: localhost + +- name: Query a specific site VRF + cisco.mso.mso_schema_site_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site VRFs + cisco.mso.mso_schema_site_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["vrf"]], + ["state", "present", ["vrf"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ", ".join(sites))) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get VRF + vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf) + vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]] + if vrf is not None and vrf_ref in vrfs: + vrf_idx = vrfs.index(vrf_ref) + vrf_path = "/sites/{0}/vrfs/{1}".format(site_template, vrf) + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx] + + if state == "query": + if vrf is None: + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"] + elif not mso.existing: + mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf)) + mso.exit_json() + + vrfs_path = "/sites/{0}/vrfs".format(site_template) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=vrf_path)) + + elif state == "present": + payload = dict( + vrfRef=dict( + schemaId=schema_id, + templateName=template, + vrfName=vrf, + ), + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=vrf_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=vrfs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py new file mode 100644 index 000000000..5ac5804e6 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py @@ -0,0 +1,274 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_vrf_region +short_description: Manage site-local VRF regions in schema template +description: +- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site. +author: +- Anvitha Jain (@anvitha-jain) +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The name of the VRF. + type: str + required: true + region: + description: + - The name of the region to manage. + type: str + aliases: [ name ] + vpn_gateway_router: + description: + - Whether VPN Gateway Router is enabled or not. + type: bool + container_overlay: + description: + - The name of the context profile type. + - This is supported on versions of MSO that are 3.3 or greater. + type: bool + underlay_context_profile: + description: + - The name of the context profile type. + - This parameter can only be added when container_overlay is True. + - This is supported on versions of MSO that are 3.3 or greater. + type: dict + suboptions: + vrf: + description: + - The name of the VRF to associate with underlay context profile. + type: str + required: true + region: + description: + - The name of the region associated with underlay context profile VRF. + type: str + required: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- Due to restrictions of the MSO REST API, this module cannot create empty region (i.e. regions without cidrs) + Use the M(cisco.mso.mso_schema_site_vrf_region_cidr) to automatically create regions with cidrs. +seealso: +- module: cisco.mso.mso_schema_site_vrf +- module: cisco.mso.mso_schema_template_vrf +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Remove VPN Gateway Router at site VRF Region + cisco.mso.mso_schema_site_vrf_region: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + vpn_gateway_router: false + state: present + delegate_to: localhost + +- name: Remove a site VRF region + cisco.mso.mso_schema_site_vrf_region: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + state: absent + delegate_to: localhost + +- name: Query a specific site VRF region + cisco.mso.mso_schema_site_vrf_region: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site VRF regions + cisco.mso.mso_schema_site_vrf_region: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", required=True), + region=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + vpn_gateway_router=dict(type="bool"), + container_overlay=dict(type="bool"), + underlay_context_profile=dict( + type="dict", + options=dict( + vrf=dict(type="str", required=True), + region=dict(type="str", required=True), + ), + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["region"]], + ["state", "present", ["region"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + region = module.params.get("region") + vpn_gateway_router = module.params.get("vpn_gateway_router") + container_overlay = module.params.get("container_overlay") + underlay_context_profile = module.params.get("underlay_context_profile") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get VRF + vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf) + vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]] + vrfs_name = [mso.dict_from_ref(v).get("vrfName") for v in vrfs] + if vrf_ref not in vrfs: + mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ", ".join(vrfs_name))) + vrf_idx = vrfs.index(vrf_ref) + + # Get Region + regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]] + if region is not None and region in regions: + region_idx = regions.index(region) + region_path = "/sites/{0}/vrfs/{1}/regions/{2}".format(site_template, vrf, region) + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx] + + if state == "query": + if region is None: + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"] + elif not mso.existing: + mso.fail_json(msg="Region '{region}' not found".format(region=region)) + mso.exit_json() + + regions_path = "/sites/{0}/vrfs/{1}/regions".format(site_template, vrf) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=region_path)) + + elif state == "present": + payload = dict( + name=region, + isVpnGatewayRouter=vpn_gateway_router, + ) + + if container_overlay: + payload["contextProfileType"] = "container-overlay" + if mso.existing: + underlay_dict = dict( + vrfRef=dict(schemaId=schema_id, templateName=template, vrfName=underlay_context_profile["vrf"]), + regionName=underlay_context_profile["region"], + ) + payload["underlayCtxProfile"] = underlay_dict + + mso.sanitize(payload, collate=True) + if mso.existing: + ops.append(dict(op="replace", path=region_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=regions_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py new file mode 100644 index 000000000..ef409f710 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_vrf_region_cidr +short_description: Manage site-local VRF region CIDRs in schema template +description: +- Manage site-local VRF region CIDRs in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Lionel Hercot (@lhercot) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The name of the VRF. + type: str + required: true + region: + description: + - The name of the region. + type: str + required: true + cidr: + description: + - The name of the region CIDR to manage. + type: str + aliases: [ ip ] + primary: + description: + - Whether this is the primary CIDR. + type: bool + default: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_vrf_region +- module: cisco.mso.mso_schema_site_vrf_region_cidr_subnet +- module: cisco.mso.mso_schema_template_vrf +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site VRF region CIDR + cisco.mso.mso_schema_site_vrf_region_cidr: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + state: present + delegate_to: localhost + +- name: Remove a site VRF region CIDR + cisco.mso.mso_schema_site_vrf_region_cidr: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + state: absent + delegate_to: localhost + +- name: Query a specific site VRF region CIDR + cisco.mso.mso_schema_site_vrf_region_cidr: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site VRF region CIDR + cisco.mso.mso_schema_site_vrf_region_cidr: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", required=True), + region=dict(type="str", required=True), + cidr=dict(type="str", aliases=["ip"]), # This parameter is not required for querying all objects + primary=dict(type="bool", default=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["cidr"]], + ["state", "present", ["cidr"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + region = module.params.get("region") + cidr = module.params.get("cidr") + primary = module.params.get("primary") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + payload = dict() + op_path = "" + new_cidr = dict( + ip=cidr, + primary=primary, + ) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + all_sites = schema_obj.get("sites") + sites = [] + if all_sites is not None: + sites = [(s.get("siteId"), s.get("templateName")) for s in all_sites] + + # Get VRF + vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf) + template_vrfs = [a.get("name") for a in schema_obj["templates"][template_idx]["vrfs"]] + if vrf not in template_vrfs: + mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ", ".join(template_vrfs))) + + # if site-template does not exist, create it + if (site_id, template) not in sites: + op_path = "/sites/-" + payload.update( + siteId=site_id, + templateName=template, + vrfs=[ + dict( + vrfRef=dict( + schemaId=schema_id, + templateName=template, + vrfName=vrf, + ), + regions=[dict(name=region, cidrs=[new_cidr])], + ) + ], + ) + + else: + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # If vrf not at site level but exists at template level + vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]] + if vrf_ref not in vrfs: + op_path = "/sites/{0}/vrfs/-".format(site_template) + payload.update( + vrfRef=dict( + schemaId=schema_id, + templateName=template, + vrfName=vrf, + ), + regions=[dict(name=region, cidrs=[new_cidr])], + ) + else: + # Update vrf index at site level + vrf_idx = vrfs.index(vrf_ref) + + # Get Region + regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]] + if region not in regions: + op_path = "/sites/{0}/vrfs/{1}/regions/-".format(site_template, vrf) + payload.update(name=region, cidrs=[new_cidr]) + else: + region_idx = regions.index(region) + + # Get CIDR + cidrs = [c.get("ip") for c in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"]] + if cidr is not None: + if cidr in cidrs: + cidr_idx = cidrs.index(cidr) + # FIXME: Changes based on index are DANGEROUS + cidr_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}".format(site_template, vrf, region, cidr_idx) + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx] + op_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/-".format(site_template, vrf, region) + payload = new_cidr + + if state == "query": + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) + elif vrf_ref not in vrfs: + mso.fail_json(msg="Provided vrf '{0}' does not exist at site level.".format(vrf)) + elif not regions or region not in regions: + mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ", ".join(regions))) + elif cidr is None and not payload: + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"] + elif not mso.existing: + mso.fail_json(msg="CIDR IP '{cidr}' not found".format(cidr=cidr)) + mso.exit_json() + + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=cidr_path)) + + elif state == "present": + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=cidr_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=op_path, value=mso.sent)) + + mso.existing = new_cidr + + if not module.check_mode and mso.previous != mso.existing: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py new file mode 100644 index 000000000..85a00ea9c --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_vrf_region_cidr_subnet +short_description: Manage site-local VRF regions in schema template +description: +- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Lionel Hercot (@lhercot) +- Anvitha Jain (@anvitha-jain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The name of the VRF. + type: str + required: true + region: + description: + - The name of the region. + type: str + required: true + cidr: + description: + - The IP range of for the region CIDR. + type: str + required: true + subnet: + description: + - The IP subnet of this region CIDR. + type: str + aliases: [ ip ] + private_link_label: + description: + - The private link label used to represent this subnet. + - This parameter is available for MSO version greater than 3.3. + type: str + zone: + description: + - The name of the zone for the region CIDR subnet. + - This argument is required for AWS sites. + type: str + aliases: [ name ] + vgw: + description: + - Whether this subnet is used for the Azure Gateway in Azure. + - Whether this subnet is used for the Transit Gateway Attachment in AWS. + type: bool + aliases: [ hub_network ] + hosted_vrf: + description: + - The name of hosted vrf associated with region CIDR subnet. + - This is supported on versions of MSO that are 3.3 or greater. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_vrf_region_cidr +- module: cisco.mso.mso_schema_template_vrf +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site VRF region CIDR subnet + cisco.mso.mso_schema_site_vrf_region_cidr_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + subnet: 14.14.14.2/24 + zone: us-west-1a + state: present + delegate_to: localhost + +- name: Remove a site VRF region CIDR subnet + cisco.mso.mso_schema_site_vrf_region_cidr_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + subnet: 14.14.14.2/24 + state: absent + delegate_to: localhost + +- name: Query a specific site VRF region CIDR subnet + cisco.mso.mso_schema_site_vrf_region_cidr_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + subnet: 14.14.14.2/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all site VRF region CIDR subnet + cisco.mso.mso_schema_site_vrf_region_cidr_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + cidr: 14.14.14.1/24 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", required=True), + region=dict(type="str", required=True), + cidr=dict(type="str", required=True), + subnet=dict(type="str", aliases=["ip"]), # This parameter is not required for querying all objects + private_link_label=dict(type="str"), + zone=dict(type="str", aliases=["name"]), + vgw=dict(type="bool", aliases=["hub_network"]), + hosted_vrf=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet"]], + ["state", "present", ["subnet"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + region = module.params.get("region") + cidr = module.params.get("cidr") + subnet = module.params.get("subnet") + private_link_label = module.params.get("private_link_label") + zone = module.params.get("zone") + hosted_vrf = module.params.get("hosted_vrf") + vgw = module.params.get("vgw") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json( + msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. " + "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list)) + ) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get VRF at site level + vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf) + vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]] + + # If vrf not at site level but exists at template level + if vrf_ref not in vrfs: + mso.fail_json(msg="Provided vrf '{0}' does not exist at site level." " Use mso_schema_site_vrf_region_cidr to create it.".format(vrf)) + vrf_idx = vrfs.index(vrf_ref) + + # Get Region + regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]] + if region not in regions: + mso.fail_json( + msg="Provided region '{0}' does not exist. Existing regions: {1}." + " Use mso_schema_site_vrf_region_cidr to create it.".format(region, ", ".join(regions)) + ) + region_idx = regions.index(region) + + # Get CIDR + cidrs = [c.get("ip") for c in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"]] + if cidr not in cidrs: + mso.fail_json( + msg="Provided CIDR IP '{0}' does not exist. Existing CIDR IPs: {1}." + " Use mso_schema_site_vrf_region_cidr to create it.".format(cidr, ", ".join(cidrs)) + ) + cidr_idx = cidrs.index(cidr) + + # Get Subnet + subnets = [s.get("ip") for s in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]["subnets"]] + if subnet is not None and subnet in subnets: + subnet_idx = subnets.index(subnet) + # FIXME: Changes based on index are DANGEROUS + subnet_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets/{4}".format(site_template, vrf, region, cidr_idx, subnet_idx) + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]["subnets"][subnet_idx] + + if state == "query": + if subnet is None: + mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]["subnets"] + elif not mso.existing: + mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet)) + mso.exit_json() + + subnets_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets".format(site_template, vrf, region, cidr_idx) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=subnet_path)) + + elif state == "present": + payload = dict(ip=subnet, zone="") + + if zone is not None: + payload["zone"] = zone + if vgw is True: + payload["usage"] = "gateway" + if private_link_label is not None: + payload["privateLinkLabel"] = dict(name=private_link_label) + if hosted_vrf is not None: + payload["vrfRef"] = dict(schemaId=schema_id, templateName=template, vrfName=hosted_vrf) + payload["inEditing"] = "false" + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=subnet_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py new file mode 100644 index 000000000..9df7bab4f --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py @@ -0,0 +1,245 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_site_vrf_region_hub_network +short_description: Manage site-local VRF region hub network in schema template +description: +- Manage site-local VRF region hub network in schema template on Cisco ACI Multi-Site. +- The 'Hub Network' feature was introduced in Multi-Site Orchestrator (MSO) version 3.0(1) for AWS and version 3.0(2) for Azure. +author: +- Cindy Zhao (@cizhao) +options: + schema: + description: + - The name of the schema. + type: str + required: true + site: + description: + - The name of the site. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The name of the VRF. + type: str + required: true + region: + description: + - The name of the region. + type: str + required: true + hub_network: + description: + - The hub network to be managed. + type: dict + suboptions: + name: + description: + - The name of the hub network. + - The hub-default is the default created hub network. + type: str + required: true + tenant: + description: + - The tenant name of the hub network. + type: str + required: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. + This can cause silent corruption on concurrent access when changing/removing on object as + the wrong object may be referenced. This module is affected by this deficiency. +seealso: +- module: cisco.mso.mso_schema_site_vrf_region +- module: cisco.mso.mso_schema_template_vrf +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site VRF region hub network + cisco.mso.mso_schema_site_vrf_region_hub_network: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-default + tenant: infra + state: present + delegate_to: localhost + +- name: Remove a site VRF region hub network + cisco.mso.mso_schema_site_vrf_region_hub_network: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + state: absent + delegate_to: localhost + +- name: Query site VRF region hub network + cisco.mso.mso_schema_site_vrf_region_hub_network: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + site: Site1 + template: Template1 + vrf: VRF1 + region: us-west-1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_hub_network_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + site=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", required=True), + region=dict(type="str", required=True), + hub_network=dict(type="dict", options=mso_hub_network_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["hub_network"]], + ], + ) + + schema = module.params.get("schema") + site = module.params.get("site") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + region = module.params.get("region") + hub_network = module.params.get("hub_network") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + + # Get site + site_id = mso.lookup_site(site) + + # Get site_idx + if not schema_obj.get("sites"): + mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) + sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")] + if (site_id, template) not in sites: + mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) + + # Schema-access uses indexes + site_idx = sites.index((site_id, template)) + # Path-based access uses site_id-template + site_template = "{0}-{1}".format(site_id, template) + + # Get VRF + vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf) + vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]] + vrfs_name = [mso.dict_from_ref(v).get("vrfName") for v in vrfs] + if vrf_ref not in vrfs: + mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ", ".join(vrfs_name))) + vrf_idx = vrfs.index(vrf_ref) + + # Get Region + regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]] + if region not in regions: + mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ", ".join(regions))) + region_idx = regions.index(region) + # Get Region object + region_obj = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx] + region_path = "/sites/{0}/vrfs/{1}/regions/{2}".format(site_template, vrf, region) + + # Get hub network + existing_hub_network = region_obj.get("cloudRsCtxProfileToGatewayRouterP") + if existing_hub_network is not None: + mso.existing = existing_hub_network + + if state == "query": + if not mso.existing: + mso.fail_json(msg="Hub network not found") + mso.exit_json() + + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=region_path + "/cloudRsCtxProfileToGatewayRouterP")) + ops.append(dict(op="replace", path=region_path + "/isTGWAttachment", value=False)) + + elif state == "present": + new_hub_network = dict( + name=hub_network.get("name"), + tenantName=hub_network.get("tenant"), + ) + payload = region_obj + payload.update( + cloudRsCtxProfileToGatewayRouterP=new_hub_network, + isTGWAttachment=True, + ) + + mso.sanitize(payload, collate=True) + + ops.append(dict(op="replace", path=region_path, value=mso.sent)) + + mso.existing = new_hub_network + + if not module.check_mode and mso.previous != mso.existing: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py new file mode 100644 index 000000000..6f4ece9ca --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template +short_description: Manage templates in schemas +description: +- Manage templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + tenant: + description: + - The tenant used for this template. + type: str + required: true + schema: + description: + - The name of the schema. + type: str + required: true + schema_description: + description: + - The description of Schema is supported on versions of MSO that are 3.3 or greater. + type: str + template_description: + description: + - The description of template is supported on versions of MSO that are 3.3 or greater. + type: str + template: + description: + - The name of the template. + type: str + aliases: [ name ] + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- Due to restrictions of the MSO REST API this module creates schemas when needed, and removes them when the last template has been removed. +seealso: +- module: cisco.mso.mso_schema +- module: cisco.mso.mso_schema_site +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new template to a schema + cisco.mso.mso_schema_template: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: Tenant 1 + schema: Schema 1 + template: Template 1 + state: present + delegate_to: localhost + +- name: Remove a template from a schema + cisco.mso.mso_schema_template: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: Tenant 1 + schema: Schema 1 + template: Template 1 + state: absent + delegate_to: localhost + +- name: Query a template + cisco.mso.mso_schema_template: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: Tenant 1 + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all templates + cisco.mso.mso_schema_template: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: Tenant 1 + schema: Schema 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + tenant=dict(type="str", required=True), + schema=dict(type="str", required=True), + schema_description=dict(type="str"), + template_description=dict(type="str"), + template=dict(type="str", aliases=["name"]), + display_name=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["template"]], + ["state", "present", ["template"]], + ], + ) + + tenant = module.params.get("tenant") + schema = module.params.get("schema") + schema_description = module.params.get("schema_description") + template_description = module.params.get("template_description") + template = module.params.get("template") + if template is not None: + template = template.replace(" ", "") + display_name = module.params.get("display_name") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_obj = mso.get_obj("schemas", displayName=schema) + + mso.existing = {} + if schema_obj: + # Schema exists + schema_path = "schemas/{id}".format(**schema_obj) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template: + if template in templates: + template_idx = templates.index(template) + mso.existing = schema_obj.get("templates")[template_idx] + else: + mso.existing = schema_obj.get("templates") + else: + schema_path = "schemas" + + if state == "query": + if not mso.existing: + if template: + mso.fail_json(msg="Template '{0}' not found".format(template)) + else: + mso.existing = [] + mso.exit_json() + + template_path = "/templates/{0}".format(template) + ops = [] + + mso.previous = mso.existing + if state == "absent": + mso.proposed = mso.sent = {} + if not schema_obj: + # There was no schema to begin with + pass + elif len(templates) == 1 and mso.existing: + # There is only one tenant, remove schema + mso.existing = {} + if not module.check_mode: + mso.request(schema_path, method="DELETE") + elif mso.existing: + # Remove existing template + mso.existing = {} + ops.append(dict(op="remove", path=template_path)) + + elif state == "present": + tenant_id = mso.lookup_tenant(tenant) + + if display_name is None: + display_name = mso.existing.get("displayName", template) + + if not schema_obj: + # Schema does not exist, so we have to create it + payload = dict( + displayName=schema, + templates=[ + dict( + name=template, + displayName=display_name, + tenantId=tenant_id, + ) + ], + sites=[], + ) + + if schema_description is not None: + payload.update(description=schema_description) + if template_description is not None: + payload["templates"][0].update(description=template_description) + + mso.existing = payload.get("templates")[0] + + if not module.check_mode: + mso.request(schema_path, method="POST", data=payload) + + elif mso.existing: + # Template exists, so we have to update it + payload = dict( + name=template, + displayName=display_name, + description=template_description, + tenantId=tenant_id, + ) + + mso.sanitize(payload, collate=True) + + ops.append(dict(op="replace", path=template_path + "/displayName", value=display_name)) + ops.append(dict(op="replace", path=template_path + "/tenantId", value=tenant_id)) + + mso.existing = mso.proposed + else: + # Template does not exist, so we have to add it + payload = dict( + name=template, + displayName=display_name, + tenantId=tenant_id, + ) + + mso.sanitize(payload, collate=True) + + ops.append(dict(op="add", path="/templates/-", value=payload)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py new file mode 100644 index 000000000..9922750f8 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_anp +short_description: Manage Application Network Profiles (ANPs) in schema templates +description: +- Manage ANPs in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP to manage. + type: str + aliases: [ name ] + description: + description: + - The description of ANP is supported on versions of MSO that are 3.3 or greater. + type: str + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new ANP + cisco.mso.mso_schema_template_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + state: present + delegate_to: localhost + +- name: Remove an ANP + cisco.mso.mso_schema_template_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + state: absent + delegate_to: localhost + +- name: Query a specific ANPs + cisco.mso.mso_schema_template_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all ANPs + cisco.mso.mso_schema_template_anp: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + description=dict(type="str"), + display_name=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["anp"]], + ["state", "present", ["anp"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + description = module.params.get("description") + display_name = module.params.get("display_name") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get ANP + anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]] + + if anp is not None and anp in anps: + anp_idx = anps.index(anp) + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx] + + if state == "query": + if anp is None: + mso.existing = schema_obj.get("templates")[template_idx]["anps"] + elif not mso.existing: + mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp)) + mso.exit_json() + + anps_path = "/templates/{0}/anps".format(template) + anp_path = "/templates/{0}/anps/{1}".format(template, anp) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=anp_path)) + + elif state == "present": + if display_name is None and not mso.existing: + display_name = anp + + epgs = [] + if mso.existing: + epgs = None + + payload = dict( + name=anp, + displayName=display_name, + epgs=epgs, + ) + + if description is not None: + payload.update(description=description) + + mso.sanitize(payload, collate=True) + + if mso.existing: + if display_name is not None: + ops.append(dict(op="replace", path=anp_path + "/displayName", value=display_name)) + else: + ops.append(dict(op="add", path=anps_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if "anpRef" in mso.previous: + del mso.previous["anpRef"] + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py new file mode 100644 index 000000000..6c021e7f7 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py @@ -0,0 +1,471 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_anp_epg +short_description: Manage Endpoint Groups (EPGs) in schema templates +description: +- Manage EPGs in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Anvitha Jain (@anvitha-jain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + aliases: [ name ] + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + description: + description: + - The description as displayed on the MSO web interface. + - The description is supported on versions of MSO that are 3.3 or greater. + type: str +# contracts: +# description: +# - A list of contracts associated to this ANP. +# type: list + bd: + description: + - The BD associated to this ANP. + type: dict + suboptions: + name: + description: + - The name of the BD to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced BD. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced BD. + type: str + vrf: + description: + - The VRF associated to this ANP. + type: dict + suboptions: + name: + description: + - The name of the VRF to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced VRF. + type: str + subnets: + description: + - The subnets associated to this ANP. + type: list + elements: dict + suboptions: + subnet: + description: + - The IP range in CIDR notation. + type: str + required: true + aliases: [ ip ] + description: + description: + - The description of this subnet. + type: str + scope: + description: + - The scope of the subnet. + type: str + default: private + choices: [ private, public ] + shared: + description: + - Whether this subnet is shared between VRFs. + type: bool + default: false + no_default_gateway: + description: + - Whether this subnet has a default gateway. + type: bool + default: false + useg_epg: + description: + - Whether this is a USEG EPG. + type: bool +# useg_epg_attributes: +# description: +# - A dictionary consisting of USEG attributes. +# type: dict + intra_epg_isolation: + description: + - Whether intra EPG isolation is enforced. + - When not specified, this parameter defaults to C(unenforced). + type: str + choices: [ enforced, unenforced ] + intersite_multicast_source: + description: + - Whether intersite multicast source is enabled. + - When not specified, this parameter defaults to C(no). + type: bool + proxy_arp: + description: + - Whether proxy arp is enabled. + - When not specified, this parameter defaults to C(no). + type: bool + preferred_group: + description: + - Whether this EPG is added to preferred group or not. + - When not specified, this parameter defaults to C(no). + type: bool + qos_level: + description: + - Quality of Service (QoS) allows you to classify the network traffic in the fabric. + - It helps prioritize and police the traffic flow to help avoid congestion in the network. + - The Contract QoS Level parameter is supported on versions of MSO that are 3.1 or greater. + type: str + epg_type: + description: + - The EPG type parameter is supported on versions of MSO that are 3.3 or greater. + type: str + choices: [ application, service ] + deployment_type: + description: + - The deployment_type parameter indicates how and where the service is deployed. + - This parameter is available only when epg_type is service. + type: str + choices: [ cloud_native, cloud_native_managed, third_party ] + access_type: + description: + - This parameter indicates how the service will be accessed. + - It is only available when epg_type is service. + type: str + choices: [ private, public, public_and_private ] + service_type: + description: + - The service_type parameter refers to the type of cloud services. + - Only certain deployment types, and certain access types within each deployment type, are supported for each service type. + - This parameter is available only when epg_type is service. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_anp +- module: cisco.mso.mso_schema_template_anp_epg_subnet +- module: cisco.mso.mso_schema_template_bd +- module: cisco.mso.mso_schema_template_contract_filter +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new EPG + cisco.mso.mso_schema_template_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + bd: + name: bd1 + vrf: + name: vrf1 + state: present + delegate_to: localhost + +- name: Add a new EPG with preferred group. + cisco.mso.mso_schema_template_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + state: present + preferred_group: true + delegate_to: localhost + +- name: Remove an EPG + cisco.mso.mso_schema_template_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + bd: + name: bd1 + vrf: + name: vrf1 + state: absent + delegate_to: localhost + +- name: Query a specific EPG + cisco.mso.mso_schema_template_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + bd: + name: bd1 + vrf: + name: vrf1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all EPGs + cisco.mso.mso_schema_template_anp_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + bd: + name: bd1 + vrf: + name: vrf1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_epg_subnet_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + description=dict(type="str"), + bd=dict(type="dict", options=mso_reference_spec()), + vrf=dict(type="dict", options=mso_reference_spec()), + display_name=dict(type="str"), + useg_epg=dict(type="bool"), + intra_epg_isolation=dict(type="str", choices=["enforced", "unenforced"]), + intersite_multicast_source=dict(type="bool"), + proxy_arp=dict(type="bool"), + subnets=dict(type="list", elements="dict", options=mso_epg_subnet_spec()), + qos_level=dict(type="str"), + epg_type=dict(type="str", choices=["application", "service"]), + deployment_type=dict(type="str", choices=["cloud_native", "cloud_native_managed", "third_party"]), + service_type=dict(type="str"), + access_type=dict(type="str", choices=["private", "public", "public_and_private"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + preferred_group=dict(type="bool"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["epg"]], + ["state", "present", ["epg"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + description = module.params.get("description") + display_name = module.params.get("display_name") + bd = module.params.get("bd") + if bd is not None and bd.get("template") is not None: + bd["template"] = bd.get("template").replace(" ", "") + vrf = module.params.get("vrf") + if vrf is not None and vrf.get("template") is not None: + vrf["template"] = vrf.get("template").replace(" ", "") + useg_epg = module.params.get("useg_epg") + intra_epg_isolation = module.params.get("intra_epg_isolation") + intersite_multicast_source = module.params.get("intersite_multicast_source") + proxy_arp = module.params.get("proxy_arp") + subnets = module.params.get("subnets") + qos_level = module.params.get("qos_level") + epg_type = module.params.get("epg_type") + deployment_type = module.params.get("deployment_type") + service_type = module.params.get("service_type") + access_type = module.params.get("access_type") + state = module.params.get("state") + preferred_group = module.params.get("preferred_group") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get ANP + anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]] + if anp not in anps: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps))) + anp_idx = anps.index(anp) + + # Get EPG + epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]] + if epg is not None and epg in epgs: + epg_idx = epgs.index(epg) + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx] + + if state == "query": + if epg is None: + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"] + elif not mso.existing: + mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg)) + + if "bdRef" in mso.existing: + mso.existing["bdRef"] = mso.dict_from_ref(mso.existing["bdRef"]) + if "vrfRef" in mso.existing: + mso.existing["vrfRef"] = mso.dict_from_ref(mso.existing["vrfRef"]) + mso.exit_json() + + epgs_path = "/templates/{0}/anps/{1}/epgs".format(template, anp) + epg_path = "/templates/{0}/anps/{1}/epgs/{2}".format(template, anp, epg) + service_path = "{0}/cloudServiceEpgConfig".format(epg_path) + ops = [] + cloud_service_epg_config = {} + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=epg_path)) + + elif state == "present": + bd_ref = mso.make_reference(bd, "bd", schema_id, template) + vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template) + subnets = mso.make_subnets(subnets, is_bd_subnet=False) + + if display_name is None and not mso.existing: + display_name = epg + + payload = dict( + name=epg, + displayName=display_name, + uSegEpg=useg_epg, + intraEpg=intra_epg_isolation, + mCastSource=intersite_multicast_source, + proxyArp=proxy_arp, + # FIXME: Missing functionality + # uSegAttrs=[], + subnets=subnets, + bdRef=bd_ref, + preferredGroup=preferred_group, + vrfRef=vrf_ref, + ) + if description is not None: + payload.update(description=description) + if qos_level is not None: + payload.update(prio=qos_level) + if epg_type is not None: + payload.update(epgType=epg_type) + + mso.sanitize(payload, collate=True) + + if mso.existing: + # Clean contractRef to fix api issue + for contract in mso.sent.get("contractRelationships"): + contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef")) + ops.append(dict(op="replace", path=epg_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=epgs_path + "/-", value=mso.sent)) + + if epg_type == "service": + access_type_map = { + "private": "Private", + "public": "Public", + "public_and_private": "PublicAndPrivate", + } + deployment_type_map = { + "cloud_native": "CloudNative", + "cloud_native_managed": "CloudNativeManaged", + "third_party": "Third-party", + } + if cloud_service_epg_config != {}: + cloud_service_epg_config.update( + dict(deploymentType=deployment_type_map[deployment_type], serviceType=service_type, accessType=access_type_map[access_type]) + ) + ops.append(dict(op="replace", path=service_path, value=cloud_service_epg_config)) + else: + cloud_service_epg_config.update( + dict(deploymentType=deployment_type_map[deployment_type], serviceType=service_type, accessType=access_type_map[access_type]) + ) + ops.append(dict(op="add", path=service_path, value=cloud_service_epg_config)) + + mso.existing = mso.proposed + + if "epgRef" in mso.previous: + del mso.previous["epgRef"] + if "bdRef" in mso.previous and mso.previous["bdRef"] != "": + mso.previous["bdRef"] = mso.dict_from_ref(mso.previous["bdRef"]) + if "vrfRef" in mso.previous and mso.previous["bdRef"] != "": + mso.previous["vrfRef"] = mso.dict_from_ref(mso.previous["vrfRef"]) + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py new file mode 100644 index 000000000..d8c881d5d --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_anp_epg_contract +short_description: Manage EPG contracts in schema templates +description: +- Manage EPG contracts in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + required: true + contract: + description: + - A contract associated to this EPG. + type: dict + suboptions: + name: + description: + - The name of the Contract to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced BD. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced BD. + type: str + type: + description: + - The type of contract. + type: str + required: true + choices: [ consumer, provider ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_anp_epg +- module: cisco.mso.mso_schema_template_contract_filter +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a contract to an EPG + cisco.mso.mso_schema_template_anp_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + contract: + name: Contract 1 + type: consumer + state: present + delegate_to: localhost + +- name: Remove a Contract + cisco.mso.mso_schema_template_anp_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + contract: + name: Contract 1 + state: absent + delegate_to: localhost + +- name: Query a specific Contract + cisco.mso.mso_schema_template_anp_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + contract: + name: Contract 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Contracts + cisco.mso.mso_schema_template_anp_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + contract=dict(type="dict", options=mso_contractref_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["contract"]], + ["state", "present", ["contract"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + contract = module.params.get("contract") + if contract is not None and contract.get("template") is not None: + contract["template"] = contract.get("template").replace(" ", "") + state = module.params.get("state") + + mso = MSOModule(module) + + if contract: + if contract.get("schema") is None: + contract["schema"] = schema + contract["schema_id"] = mso.lookup_schema(contract.get("schema")) + if contract.get("template") is None: + contract["template"] = template + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get ANP + anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]] + if anp not in anps: + mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps))) + anp_idx = anps.index(anp) + + # Get EPG + epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]] + if epg not in epgs: + mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs))) + epg_idx = epgs.index(epg) + + # Get Contract + if contract: + contracts = [ + (c.get("contractRef"), c.get("relationshipType")) + for c in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"] + ] + contract_ref = mso.contract_ref(**contract) + if (contract_ref, contract.get("type")) in contracts: + contract_idx = contracts.index((contract_ref, contract.get("type"))) + contract_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships/{3}".format(template, anp, epg, contract_idx) + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"][contract_idx] + + if state == "query": + if not contract: + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"] + elif not mso.existing: + mso.fail_json(msg="Contract '{0}' not found".format(contract_ref)) + + if "contractRef" in mso.existing: + mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef")) + mso.exit_json() + + contracts_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships".format(template, anp, epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=contract_path)) + + elif state == "present": + payload = dict( + relationshipType=contract.get("type"), + contractRef=dict( + contractName=contract.get("name"), + templateName=contract.get("template"), + schemaId=contract.get("schema_id"), + ), + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=contract_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=contracts_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if "contractRef" in mso.previous: + mso.previous["contractRef"] = mso.dict_from_ref(mso.previous.get("contractRef")) + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py new file mode 100644 index 000000000..bd98fc321 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py @@ -0,0 +1,277 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_anp_epg_selector +short_description: Manage EPG selector in schema templates +description: +- Manage EPG selector in schema templates on Cisco ACI Multi-Site. +author: +- Cindy Zhao (@cizhao) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + required: true + selector: + description: + - The name of the selector. + type: str + expressions: + description: + - Expressions associated to this selector. + type: list + elements: dict + suboptions: + type: + description: + - The name of the expression. + required: true + type: str + aliases: [ tag ] + operator: + description: + - The operator associated to the expression. + required: true + type: str + choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ] + value: + description: + - The value associated to the expression. + - If the operator is in or not_in, the value should be a comma separated str. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_anp_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a selector to an EPG + cisco.mso.mso_schema_template_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + selector: selector_1 + expressions: + - type: expression_1 + operator: in + value: test + state: present + delegate_to: localhost + +- name: Remove a Selector + cisco.mso.mso_schema_template_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + selector: selector_1 + state: absent + delegate_to: localhost + +- name: Query a specific Selector + cisco.mso.mso_schema_template_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + selector: selector_1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Selectors + cisco.mso.mso_schema_template_anp_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec + +EXPRESSION_KEYS = { + "not_in": "notIn", + "not_equals": "notEquals", + "has_key": "keyExist", + "does_not_have_key": "keyNotExist", + "in": "in", + "equals": "equals", +} + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + selector=dict(type="str"), + expressions=dict(type="list", elements="dict", options=mso_expression_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["selector"]], + ["state", "present", ["selector"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + selector = module.params.get("selector") + expressions = module.params.get("expressions") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + template_idx = templates.index(template) + + # Get ANP + anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]] + if anp not in anps: + mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=", ".join(anps))) + anp_idx = anps.index(anp) + + # Get EPG + epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]] + if epg not in epgs: + mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs))) + epg_idx = epgs.index(epg) + + # Get Selector + if selector and " " in selector: + mso.fail_json(msg="There should not be any space in selector name.") + selectors = [s.get("name") for s in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"]] + if selector in selectors: + selector_idx = selectors.index(selector) + selector_path = "/templates/{0}/anps/{1}/epgs/{2}/selectors/{3}".format(template, anp, epg, selector_idx) + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"][selector_idx] + + if state == "query": + if selector is None: + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"] + elif not mso.existing: + mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector)) + mso.exit_json() + + selectors_path = "/templates/{0}/anps/{1}/epgs/{2}/selectors/-".format(template, anp, epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=selector_path)) + + elif state == "present": + # Get expressions + all_expressions = [] + if expressions: + for expression in expressions: + tag = expression.get("type") + operator = expression.get("operator") + value = expression.get("value") + if " " in tag: + mso.fail_json(msg="There should not be any space in 'type' attribute of expression '{0}'".format(tag)) + if operator in ["has_key", "does_not_have_key"] and value: + mso.fail_json(msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, tag)) + if operator in ["not_in", "in", "equals", "not_equals"] and not value: + mso.fail_json(msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, tag)) + all_expressions.append( + dict( + key="Custom:" + tag, + operator=EXPRESSION_KEYS.get(operator), + value=value, + ) + ) + + payload = dict( + name=selector, + expressions=all_expressions, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=selector_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=selectors_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py new file mode 100644 index 000000000..b060ddd5e --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py @@ -0,0 +1,256 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_anp_epg_subnet +short_description: Manage EPG subnets in schema templates +description: +- Manage EPG subnets in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + anp: + description: + - The name of the ANP. + type: str + required: true + epg: + description: + - The name of the EPG to manage. + type: str + required: true + subnet: + description: + - The IP range in CIDR notation. + type: str + required: true + aliases: [ ip ] + description: + description: + - The description of this subnet. + type: str + scope: + description: + - The scope of the subnet. + type: str + default: private + choices: [ private, public ] + shared: + description: + - Whether this subnet is shared between VRFs. + type: bool + default: false + no_default_gateway: + description: + - Whether this subnet has a default gateway. + type: bool + default: false + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new subnet to an EPG + cisco.mso.mso_schema_template_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + subnet: 10.0.0.0/24 + state: present + delegate_to: localhost + +- name: Remove a subnet from an EPG + cisco.mso.mso_schema_template_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + subnet: 10.0.0.0/24 + state: absent + delegate_to: localhost + +- name: Query a specific EPG subnet + cisco.mso.mso_schema_template_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + epg: EPG 1 + subnet: 10.0.0.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all EPGs subnets + cisco.mso.mso_schema_template_anp_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + anp: ANP 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_epg_subnet_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + anp=dict(type="str", required=True), + epg=dict(type="str", required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + argument_spec.update(mso_epg_subnet_spec()) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet"]], + ["state", "present", ["subnet"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + anp = module.params.get("anp") + epg = module.params.get("epg") + subnet = module.params.get("subnet") + description = module.params.get("description") + scope = module.params.get("scope") + shared = module.params.get("shared") + no_default_gateway = module.params.get("no_default_gateway") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + template_idx = templates.index(template) + + # Get ANP + anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]] + if anp not in anps: + mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=", ".join(anps))) + anp_idx = anps.index(anp) + + # Get EPG + epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]] + if epg not in epgs: + mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs))) + epg_idx = epgs.index(epg) + + # Get Subnet + subnets = [s.get("ip") for s in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"]] + if subnet in subnets: + subnet_idx = subnets.index(subnet) + # FIXME: Changes based on index are DANGEROUS + subnet_path = "/templates/{0}/anps/{1}/epgs/{2}/subnets/{3}".format(template, anp, epg, subnet_idx) + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"][subnet_idx] + + if state == "query": + if subnet is None: + mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"] + elif not mso.existing: + mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet)) + mso.exit_json() + + subnets_path = "/templates/{0}/anps/{1}/epgs/{2}/subnets".format(template, anp, epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.existing = {} + ops.append(dict(op="remove", path=subnet_path)) + + elif state == "present": + if not mso.existing: + if description is None: + description = subnet + if scope is None: + scope = "private" + if shared is None: + shared = False + if no_default_gateway is None: + no_default_gateway = False + + payload = dict( + ip=subnet, + description=description, + scope=scope, + shared=shared, + noDefaultGateway=no_default_gateway, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=subnet_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py new file mode 100644 index 000000000..0793e844d --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py @@ -0,0 +1,566 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_bd +short_description: Manage Bridge Domains (BDs) in schema templates +description: +- Manage BDs in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + - Display Name of template for operations can only be used in some versions of mso. + - Use the name of template instead of Display Name to avoid discrepency. + type: str + required: true + bd: + description: + - The name of the BD to manage. + type: str + aliases: [ name ] + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + description: + description: + - The description of BD is supported on versions of MSO that are 3.3 or greater. + type: str + vrf: + description: + - The VRF associated to this BD. This is required only when creating a new BD. + type: dict + suboptions: + name: + description: + - The name of the VRF to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current template. + type: str + dhcp_policy: + description: + - The DHCP Policy + type: dict + suboptions: + name: + description: + - The name of the DHCP Relay Policy + type: str + required: true + version: + description: + - The version of DHCP Relay Policy + type: int + required: true + dhcp_option_policy: + description: + - The DHCP Option Policy + type: dict + suboptions: + name: + description: + - The name of the DHCP Option Policy + type: str + required: true + version: + description: + - The version of the DHCP Option Policy + type: int + required: true + dhcp_policies: + description: + - A list DHCP Policies to be assciated with the BD + - This option can only be used on versions of MSO that are 3.1.1h or greater. + type: list + elements: dict + suboptions: + name: + description: + - The name of the DHCP Relay Policy + type: str + required: true + version: + description: + - The version of DHCP Relay Policy + type: int + required: true + dhcp_option_policy: + description: + - The DHCP Option Policy + type: dict + suboptions: + name: + description: + - The name of the DHCP Option Policy + type: str + required: true + version: + description: + - The version of the DHCP Option Policy + type: int + required: true + subnets: + description: + - The subnets associated to this BD. + type: list + elements: dict + suboptions: + subnet: + description: + - The IP range in CIDR notation. + type: str + required: true + aliases: [ ip ] + description: + description: + - The description of this subnet. + type: str + scope: + description: + - The scope of the subnet. + type: str + default: private + choices: [ private, public ] + shared: + description: + - Whether this subnet is shared between VRFs. + type: bool + default: false + no_default_gateway: + description: + - Whether this subnet has a default gateway. + type: bool + default: false + querier: + description: + - Whether this subnet is an IGMP querier. + type: bool + default: false + virtual: + description: + - Treat as Virtual IP Address. + type: bool + default: false + primary: + description: + - Treat as Primary Subnet. + - There can be only one primary subnet per address family under a BD. + - This option can only be used on versions of MSO that are 3.1.1h or greater. + type: bool + default: false + intersite_bum_traffic: + description: + - Whether to allow intersite BUM traffic. + type: bool + optimize_wan_bandwidth: + description: + - Whether to optimize WAN bandwidth. + type: bool + layer2_stretch: + description: + - Whether to enable L2 stretch. + type: bool + default: true + layer2_unknown_unicast: + description: + - Layer2 unknown unicast. + type: str + choices: [ flood, proxy ] + layer3_multicast: + description: + - Whether to enable L3 multicast. + type: bool + unknown_multicast_flooding: + description: + - Unknown Multicast Flooding can either be Flood or Optimized Flooding. + type: str + choices: [ flood, optimized_flooding ] + multi_destination_flooding: + description: + - Multi-Destination Flooding can either be Flood in BD, Drop or Flood in Encapsulation. + - Flood in Encapsulation is only supported on versions of MSO that are 3.3 or greater. + type: str + choices: [ flood_in_bd, drop, encap-flood ] + ipv6_unknown_multicast_flooding: + description: + - IPv6 Unknown Multicast Flooding can either be Flood or Optimized Flooding + type: str + choices: [ flood, optimized_flooding ] + arp_flooding: + description: + - ARP Flooding + type: bool + virtual_mac_address: + description: + - Virtual MAC Address + type: str + unicast_routing: + description: + - Unicast Routing + - This option can only be used on versions of MSO that are 3.1.1h or greater. + type: bool + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new BD + cisco.mso.mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + vrf: + name: VRF1 + state: present + delegate_to: localhost + +- name: Add a new BD from another Schema + mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + vrf: + name: VRF1 + schema: Schema Origin + template: Template Origin + state: present + delegate_to: localhost + +- name: Add bd with options available on version 3.1 + mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + vrf: + name: vrf1 + schema: Test + template: Template1 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + state: present + +- name: Add bd with options available on version 3.1.1h or greater + mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + unicast_routing: true + subnets: + - subnet: 10.0.0.128/24 + primary: true + - subnet: 10.0.1.254/24 + description: 1234567890 + virtual: true + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + vrf: + name: vrf1 + schema: Schema1 + template: Template1 + dhcp_policies: + - name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + - name: ansible_test2 + version: 1 + dhcp_option_policy: + name: ansible_test_option2 + version: 1 + - name: ansible_test3 + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + state: present + delegate_to: localhost + +- name: Remove a BD + cisco.mso.mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD1 + state: absent + delegate_to: localhost + +- name: Query a specific BD + cisco.mso.mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all BDs + cisco.mso.mso_schema_template_bd: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_bd_subnet_spec, mso_dhcp_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + bd=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + display_name=dict(type="str"), + description=dict(type="str"), + intersite_bum_traffic=dict(type="bool"), + optimize_wan_bandwidth=dict(type="bool"), + layer2_stretch=dict(type="bool", default="true"), + layer2_unknown_unicast=dict(type="str", choices=["flood", "proxy"]), + layer3_multicast=dict(type="bool"), + vrf=dict(type="dict", options=mso_reference_spec()), + dhcp_policy=dict(type="dict", options=mso_dhcp_spec()), + dhcp_policies=dict(type="list", elements="dict", options=mso_dhcp_spec()), + subnets=dict(type="list", elements="dict", options=mso_bd_subnet_spec()), + unknown_multicast_flooding=dict(type="str", choices=["optimized_flooding", "flood"]), + multi_destination_flooding=dict(type="str", choices=["flood_in_bd", "drop", "encap-flood"]), + ipv6_unknown_multicast_flooding=dict(type="str", choices=["optimized_flooding", "flood"]), + arp_flooding=dict(type="bool"), + virtual_mac_address=dict(type="str"), + unicast_routing=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["bd"]], + ["state", "present", ["bd", "vrf"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + bd = module.params.get("bd") + display_name = module.params.get("display_name") + description = module.params.get("description") + intersite_bum_traffic = module.params.get("intersite_bum_traffic") + optimize_wan_bandwidth = module.params.get("optimize_wan_bandwidth") + layer2_stretch = module.params.get("layer2_stretch") + layer2_unknown_unicast = module.params.get("layer2_unknown_unicast") + layer3_multicast = module.params.get("layer3_multicast") + vrf = module.params.get("vrf") + if vrf is not None and vrf.get("template") is not None: + vrf["template"] = vrf.get("template").replace(" ", "") + dhcp_policy = module.params.get("dhcp_policy") + dhcp_policies = module.params.get("dhcp_policies") + subnets = module.params.get("subnets") + unknown_multicast_flooding = module.params.get("unknown_multicast_flooding") + multi_destination_flooding = module.params.get("multi_destination_flooding") + ipv6_unknown_multicast_flooding = module.params.get("ipv6_unknown_multicast_flooding") + arp_flooding = module.params.get("arp_flooding") + virtual_mac_address = module.params.get("virtual_mac_address") + unicast_routing = module.params.get("unicast_routing") + state = module.params.get("state") + + mso = MSOModule(module) + + # Map choices + if unknown_multicast_flooding == "optimized_flooding": + unknown_multicast_flooding = "opt-flood" + if ipv6_unknown_multicast_flooding == "optimized_flooding": + ipv6_unknown_multicast_flooding = "opt-flood" + if multi_destination_flooding == "flood_in_bd": + multi_destination_flooding = "bd-flood" + + if layer2_unknown_unicast == "flood": + arp_flooding = True + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get BDs + bds = [b.get("name") for b in schema_obj.get("templates")[template_idx]["bds"]] + + if bd is not None and bd in bds: + bd_idx = bds.index(bd) + mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx] + + if state == "query": + if bd is None: + mso.existing = schema_obj.get("templates")[template_idx]["bds"] + elif not mso.existing: + mso.fail_json(msg="BD '{bd}' not found".format(bd=bd)) + mso.exit_json() + + bds_path = "/templates/{0}/bds".format(template) + bd_path = "/templates/{0}/bds/{1}".format(template, bd) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=bd_path)) + + elif state == "present": + vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template) + subnets = mso.make_subnets(subnets) + dhcp_label = mso.make_dhcp_label(dhcp_policy) + dhcp_labels = mso.make_dhcp_label(dhcp_policies) + + if display_name is None and not mso.existing: + display_name = bd + if subnets is None and not mso.existing: + subnets = [] + + payload = dict( + name=bd, + displayName=display_name, + intersiteBumTrafficAllow=intersite_bum_traffic, + optimizeWanBandwidth=optimize_wan_bandwidth, + l2UnknownUnicast=layer2_unknown_unicast, + l2Stretch=layer2_stretch, + l3MCast=layer3_multicast, + subnets=subnets, + vrfRef=vrf_ref, + dhcpLabel=dhcp_label, + unkMcastAct=unknown_multicast_flooding, + multiDstPktAct=multi_destination_flooding, + v6unkMcastAct=ipv6_unknown_multicast_flooding, + vmac=virtual_mac_address, + arpFlood=arp_flooding, + ) + + if dhcp_labels: + payload.update(dhcpLabels=dhcp_labels) + + if unicast_routing is not None: + payload.update(unicastRouting=unicast_routing) + + if description: + payload.update(description=description) + + mso.sanitize(payload, collate=True, required=["dhcpLabel", "dhcpLabels"]) + if mso.existing: + ops.append(dict(op="replace", path=bd_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=bds_path + "/-", value=mso.sent)) + mso.existing = mso.proposed + + if "bdRef" in mso.previous: + del mso.previous["bdRef"] + if "vrfRef" in mso.previous: + mso.previous["vrfRef"] = mso.vrf_dict_from_ref(mso.previous.get("vrfRef")) + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py new file mode 100644 index 000000000..64fe360ea --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py @@ -0,0 +1,245 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_bd_dhcp_policy +short_description: Manage BD DHCP Policy in schema templates +description: +- Manage BD DHCP policies in schema templates on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + bd: + description: + - The name of the BD to manage. + type: str + required: true + dhcp_policy: + description: + - The DHCP Policy + type: str + aliases: [ name ] + version: + description: + - The version of DHCP Relay Policy. + type: int + dhcp_option_policy: + description: + - The DHCP Option Policy. + type: dict + suboptions: + name: + description: + - The name of the DHCP Option Policy. + type: str + required: true + version: + description: + - The version of the DHCP Option Policy. + type: int + required: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- This module can only be used on versions of MSO that are 3.1.1h or greater. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new DHCP policy to a BD + cisco.mso.mso_schema_template_bd_dhcp_policy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + state: present + delegate_to: localhost + +- name: Remove a DHCP policy from a BD + cisco.mso.mso_schema_template_bd_dhcp_policy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + name: ansible_test + version: 1 + state: absent + delegate_to: localhost + +- name: Query a specific BD DHCP Policy + cisco.mso.mso_schema_template_bd_dhcp_policy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + name: ansible_test + state: query + delegate_to: localhost + register: query_result + +- name: Query all BD DHCP Policies + cisco.mso.mso_schema_template_bd_dhcp_policy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_dhcp_option_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + bd=dict(type="str", required=True), + dhcp_policy=dict(type="str", aliases=["name"]), + version=dict(type="int"), + dhcp_option_policy=dict(type="dict", options=mso_dhcp_option_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["dhcp_policy"]], + ["state", "present", ["dhcp_policy", "version"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + bd = module.params.get("bd") + dhcp_policy = module.params.get("dhcp_policy") + dhcp_option_policy = module.params.get("dhcp_option_policy") + version = module.params.get("version") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get BD + bds = [b.get("name") for b in schema_obj.get("templates")[template_idx]["bds"]] + if bd not in bds: + mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ", ".join(bds))) + bd_idx = bds.index(bd) + + # Check if DHCP policy already exists + if dhcp_policy: + check_policy = mso.get_obj("policies/dhcp/relay", name=dhcp_policy, key="DhcpRelayPolicies") + if check_policy: + pass + else: + mso.fail_json(msg="DHCP policy '{dhcp_policy}' does not exist".format(dhcp_policy=dhcp_policy)) + + # Check if DHCP option policy already exists + if dhcp_option_policy: + check_option_policy = mso.get_obj("policies/dhcp/option", name=dhcp_option_policy.get("name"), key="DhcpRelayPolicies") + if check_option_policy: + pass + else: + mso.fail_json(msg="DHCP option policy '{dhcp_option_policy}' does not exist".format(dhcp_option_policy=dhcp_option_policy.get("name"))) + + # Get DHCP policies + dhcp_policies = [s.get("name") for s in schema_obj.get("templates")[template_idx]["bds"][bd_idx]["dhcpLabels"]] + if dhcp_policy in dhcp_policies: + dhcp_idx = dhcp_policies.index(dhcp_policy) + # FIXME: Changes based on index are DANGEROUS + dhcp_policy_path = "/templates/{0}/bds/{1}/dhcpLabels/{2}".format(template, bd, dhcp_idx) + mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["dhcpLabels"][dhcp_idx] + + if state == "query": + if dhcp_policy is None: + mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["dhcpLabels"] + elif not mso.existing: + mso.fail_json(msg="DHCP policy not associated with the bd") + mso.exit_json() + + dhcp_policy_paths = "/templates/{0}/bds/{1}/dhcpLabels".format(template, bd) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=dhcp_policy_path)) + + elif state == "present": + payload = dict( + name=dhcp_policy, + version=version, + dhcpOptionLabel=dhcp_option_policy, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=dhcp_policy_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=dhcp_policy_paths + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py new file mode 100644 index 000000000..cc55eea7b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_bd_subnet +short_description: Manage BD subnets in schema templates +description: +- Manage BD subnets in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + bd: + description: + - The name of the BD to manage. + type: str + required: true + subnet: + description: + - The IP range in CIDR notation. + type: str + aliases: [ ip ] + description: + description: + - The description of this subnet. + type: str + is_virtual_ip: + description: + - Treat as Virtual IP Address + type: bool + default: false + scope: + description: + - The scope of the subnet. + type: str + choices: [ private, public ] + shared: + description: + - Whether this subnet is shared between VRFs. + type: bool + default: false + no_default_gateway: + description: + - Whether this subnet has a default gateway. + type: bool + default: false + querier: + description: + - Whether this subnet is an IGMP querier. + type: bool + default: false + primary: + description: + - Treat as Primary Subnet. + - There can be only one primary subnet per address family under a BD. + - This option can only be used on versions of MSO that are 3.1.1h or greater. + type: bool + default: false + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- Due to restrictions of the MSO REST API concurrent modifications to BD subnets can be dangerous and corrupt data. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new subnet to a BD + cisco.mso.mso_schema_template_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + subnet: 10.0.0.0/24 + state: present + delegate_to: localhost + +- name: Remove a subset from a BD + cisco.mso.mso_schema_template_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + subnet: 10.0.0.0/24 + state: absent + delegate_to: localhost + +- name: Query a specific BD subnet + cisco.mso.mso_schema_template_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + subnet: 10.0.0.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all BD subnets + cisco.mso.mso_schema_template_bd_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + bd: BD 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + bd=dict(type="str", required=True), + subnet=dict(type="str", aliases=["ip"]), + description=dict(type="str"), + is_virtual_ip=dict(type="bool", default=False), + scope=dict(type="str", choices=["private", "public"]), + shared=dict(type="bool", default=False), + no_default_gateway=dict(type="bool", default=False), + querier=dict(type="bool", default=False), + primary=dict(type="bool", default=False), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet"]], + ["state", "present", ["subnet"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + bd = module.params.get("bd") + subnet = module.params.get("subnet") + description = module.params.get("description") + is_virtual_ip = module.params.get("is_virtual_ip") + scope = module.params.get("scope") + shared = module.params.get("shared") + no_default_gateway = module.params.get("no_default_gateway") + querier = module.params.get("querier") + primary = module.params.get("primary") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get BD + bds = [b.get("name") for b in schema_obj.get("templates")[template_idx]["bds"]] + if bd not in bds: + mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ", ".join(bds))) + bd_idx = bds.index(bd) + + # Get Subnet + subnets = [s.get("ip") for s in schema_obj.get("templates")[template_idx]["bds"][bd_idx]["subnets"]] + if subnet in subnets: + subnet_idx = subnets.index(subnet) + # FIXME: Changes based on index are DANGEROUS + subnet_path = "/templates/{0}/bds/{1}/subnets/{2}".format(template, bd, subnet_idx) + mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["subnets"][subnet_idx] + + if state == "query": + if subnet is None: + mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["subnets"] + elif not mso.existing: + mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet)) + mso.exit_json() + + subnets_path = "/templates/{0}/bds/{1}/subnets".format(template, bd) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=subnet_path)) + + elif state == "present": + if not mso.existing: + if description is None: + description = subnet + if scope is None: + scope = "private" + + payload = dict( + ip=subnet, + description=description, + virtual=is_virtual_ip, + scope=scope, + shared=shared, + noDefaultGateway=no_default_gateway, + querier=querier, + primary=primary, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=subnet_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py new file mode 100644 index 000000000..0cd41779f --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py @@ -0,0 +1,222 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_clone +short_description: Clone templates +description: +- Clone templates on Cisco ACI Multi-Site. +- Clones only template objects and not site objects. +author: +- Anvitha Jain (@anvitha-jain) +options: + source_schema: + description: + - The name of the source_schema. + type: str + destination_schema: + description: + - The name of the destination_schema. + type: str + destination_tenant: + description: + - The name of the destination_schema. + type: str + source_template_name: + description: + - The name of the source template. + type: str + destination_template_name: + description: + - The name of the destination template. + type: str + destination_template_display_name: + description: + - The display name of the destination template. + type: str + state: + description: + - Use C(clone) for adding. + type: str + choices: [ clone ] + default: clone +seealso: +- module: cisco.mso.mso_schema +- module: cisco.mso.mso_schema_clone +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Clone template in the same schema + cisco.mso.mso_schema_template_clone: + host: mso_host + username: admin + password: SomeSecretPassword + source_schema: Schema1 + destination_schema: Schema1 + destination_tenant: ansible_test + source_template_name: Template1 + destination_template_name: Template1_clone + destination_template_display_name: Template1_clone + state: clone + delegate_to: localhost + +- name: Clone template to different schema + cisco.mso.mso_schema_template_clone: + host: mso_host + username: admin + password: SomeSecretPassword + source_schema: Schema1 + destination_schema: Schema2 + destination_tenant: ansible_test + source_template_name: Template2 + destination_template_name: Cloned_template_1 + destination_template_display_name: Cloned_template_1 + state: clone + delegate_to: localhost + +- name: Clone template in the same schema but different tenant attached + cisco.mso.mso_schema_template_clone: + host: mso_host + username: admin + password: SomeSecretPassword + source_schema: Schema1 + destination_schema: Schema1 + destination_tenant: common + source_template_name: Template1_clone + destination_template_name: Template1_clone_2 + state: clone + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_4_UNIQUE_IDENTIFIERS +import json + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + source_schema=dict(type="str"), + destination_schema=dict(type="str"), + destination_tenant=dict(type="str"), + source_template_name=dict(type="str"), + destination_template_name=dict(type="str"), + destination_template_display_name=dict(type="str"), + state=dict(type="str", default="clone", choices=["clone"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "clone", ["source_schema", "source_template_name"]], + ], + ) + + source_schema = module.params.get("source_schema") + destination_schema = module.params.get("destination_schema") + destination_tenant = module.params.get("destination_tenant") + source_template_name = module.params.get("source_template_name") + destination_template_name = module.params.get("destination_template_name") + destination_template_display_name = module.params.get("destination_template_display_name") + state = module.params.get("state") + + mso = MSOModule(module) + + source_schema_id = None + destination_schema_id = None + destination_tenant_id = None + ops = [] + + if destination_schema is None: + destination_schema = source_schema + + if destination_template_name is None: + destination_template_name = source_template_name + + if destination_template_display_name is None: + destination_template_display_name = destination_template_name + + # Check if source and destination template are named differently if in same schema + if source_schema == destination_schema: + if source_template_name == destination_template_name: + mso.fail_json(msg="Source and destination templates in the same schema cannot have same names.") + + # Get source schema id and destination schema id + schema_summary = mso.query_objs("schemas/list-identity", key="schemas") + + for schema in schema_summary: + if schema.get("displayName") == source_schema: + source_schema_id = schema.get("id") + + if schema.get("displayName") == destination_schema: + destination_schema_id = schema.get("id") + for template in schema.get("templates"): + if template.get("name") == destination_template_name: + mso.fail_json(msg="Template with the name '{0}' already exists. Please use another name.".format(destination_template_name)) + + if source_schema_id is None: + mso.fail_json(msg="Schema with the name '{0}' does not exist.".format(source_schema)) + elif destination_schema_id is None: + mso.fail_json(msg="Schema with the name '{0}' does not exist.".format(destination_schema)) + + # Get destination schema details before change + destination_schema_path = "schemas/{0}".format(destination_schema_id) + mso.existing = mso.query_obj(destination_schema_path, displayName=destination_schema) + + if state == "clone": + # Get destination tenant id + if destination_tenant is not None: + destination_tenant_id = mso.lookup_tenant(destination_tenant) + + # Get source schema details + source_schema_path = "schemas/{0}".format(source_schema_id) + source_schema_obj = mso.query_obj(source_schema_path, displayName=source_schema) + + source_template_path = "/{0}/templates/{1}".format(source_schema_path, source_template_name) + destination_template_path = "/{0}/templates/{1}".format(destination_schema_path, destination_template_name) + + source_templates = source_schema_obj.get("templates") + new_template = None + for template in source_templates: + if template.get("name") == source_template_name: + new_template = json.loads(json.dumps(template).replace(source_template_path, destination_template_path)) + new_template["name"] = destination_template_name + new_template["displayName"] = destination_template_display_name + if destination_tenant_id is not None: + new_template["tenantId"] = destination_tenant_id + mso.delete_keys_from_dict(new_template, NDO_4_UNIQUE_IDENTIFIERS) + break + + if new_template is None: + mso.fail_json(msg="Source template with the name '{0}' does not exist.".format(source_template_name)) + + new_template = mso.recursive_dict_from_ref(new_template) + mso.previous = mso.existing + + ops.append(dict(op="add", path="/templates/-", value=new_template)) + if not module.check_mode: + mso.request(destination_schema_path, method="PATCH", data=ops) + + mso.existing = mso.query_obj(destination_schema_path, displayName=destination_schema) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py new file mode 100644 index 000000000..11bf08731 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py @@ -0,0 +1,396 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_contract_filter +short_description: Manage contract filters in schema templates +description: +- Manage contract filters in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + contract: + description: + - The name of the contract to manage. + type: str + required: true + description: + description: + - The description of contract is supported on versions of MSO/NDO that are 3.3 or greater. + type: str + contract_display_name: + description: + - The name as displayed on the MSO web interface. + - This defaults to the contract name when unset on creation. + type: str + contract_filter_type: + description: + - DEPRECATION WARNING, contract_filter_type will not be used anymore and is deduced from filter_type. + - The type of filters defined in this contract. + - This defaults to C(both-way) when unset on creation. + default: both-way + type: str + choices: [ both-way, one-way ] + contract_scope: + description: + - The scope of the contract. + - This defaults to C(vrf) when unset on creation. + type: str + choices: [ application-profile, global, tenant, vrf ] + filter: + description: + - The filter to associate with this contract. + type: str + aliases: [ name ] + filter_template: + description: + - The template name in which the filter is located. + type: str + filter_schema: + description: + - The schema name in which the filter is located. + type: str + filter_type: + description: + - The type of filter to manage. + - Prior to MSO/NDO 3.3 remove and re-apply contract to change the filter type. + type: str + choices: [ both-way, consumer-to-provider, provider-to-consumer ] + default: both-way + aliases: [ type ] + filter_directives: + description: + - A list of filter directives. + type: list + elements: str + choices: [ log, none, policy_compression ] + qos_level: + description: + - The Contract QoS Level parameter is supported on versions of MSO/NDO that are 3.3 or greater. + type: str + choices: [ unspecified, level1, level2, level3, level4, level5, level6 ] + action: + description: + - The filter action parameter is supported on versions of MSO/NDO that are 3.3 or greater. + type: str + choices: [ permit, deny ] + priority: + description: + - The filter priority override parameter is supported on versions of MSO/NDO that are 3.3 or greater. + type: str + choices: [ default, lowest_priority, medium_priority, highest_priority ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_filter_entry +notes: +- Due to restrictions of the MSO/NDO REST API this module creates contracts when needed, and removes them when the last filter has been removed. +- Due to restrictions of the MSO/NDO REST API concurrent modifications to contract filters can be dangerous and corrupt data. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new contract filter + cisco.mso.mso_schema_template_contract_filter: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + contract_scope: global + filter: Filter 1 + state: present + delegate_to: localhost + +- name: Remove a contract filter + cisco.mso.mso_schema_template_contract_filter: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + filter: Filter 1 + state: absent + delegate_to: localhost + +- name: Query a specific contract filter + cisco.mso.mso_schema_template_contract_filter: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + filter: Filter 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all contract filters + cisco.mso.mso_schema_template_contract_filter: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.constants import FILTER_KEY_MAP, PRIORITY_MAP + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + contract=dict(type="str", required=True), + description=dict(type="str"), + contract_display_name=dict(type="str"), + contract_scope=dict(type="str", choices=["application-profile", "global", "tenant", "vrf"]), + # Deprecated input: contract_filter_type is deduced from filter_type + contract_filter_type=dict(type="str", default="both-way", choices=["both-way", "one-way"]), + filter=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + filter_directives=dict(type="list", elements="str", choices=["log", "none", "policy_compression"]), + filter_template=dict(type="str"), + filter_schema=dict(type="str"), + filter_type=dict(type="str", default="both-way", choices=list(FILTER_KEY_MAP), aliases=["type"]), + qos_level=dict(type="str", choices=["unspecified", "level1", "level2", "level3", "level4", "level5", "level6"]), + action=dict(type="str", choices=["permit", "deny"]), + priority=dict(type="str", choices=["default", "lowest_priority", "medium_priority", "highest_priority"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["filter"]], + ["state", "present", ["filter"]], + ], + ) + + schema = module.params.get("schema") + template_name = module.params.get("template").replace(" ", "") + contract_name = module.params.get("contract") + contract_display_name = module.params.get("contract_display_name") + description = module.params.get("description") + # Deprecated input: contract_filter_type is deduced from filter_type. + # contract_filter_type = module.params.get('contract_filter_type') + contract_scope = module.params.get("contract_scope") + filter_name = module.params.get("filter") + filter_directives = module.params.get("filter_directives") + filter_template = module.params.get("filter_template") + filter_schema = module.params.get("filter_schema") + filter_type = module.params.get("filter_type") + filter_action = module.params.get("action") + filter_priority = module.params.get("priority") + qos_level = module.params.get("qos_level") + + state = module.params.get("state") + + mso = MSOModule(module) + + # Initialize variables + ops = [] + filter_obj = None + filter_key = FILTER_KEY_MAP.get(filter_type) + filter_template = template_name if filter_template is None else filter_template.replace(" ", "") + filter_schema = schema if filter_schema is None else filter_schema + filter_schema_id = mso.lookup_schema(filter_schema) + contract_filter_type = "bothWay" if filter_type == "both-way" else "oneWay" + + # Set path defaults, when object (contract or filter) is found append /{name} to base paths + base_contract_path = "/templates/{0}/contracts".format(template_name) + base_filter_path = "{0}/{1}/{2}".format(base_contract_path, contract_name, filter_key) + contract_path = "{0}/-".format(base_contract_path) + filter_path = "{0}/-".format(base_filter_path) + + # Get schema information. + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template by unique identifier "name". + template_obj = next((item for item in schema_obj.get("templates") if item.get("name") == template_name), None) + if not template_obj: + existing_templates = [t.get("name") for t in schema_obj.get("templates")] + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template_name, ", ".join(existing_templates))) + + filter_ref = mso.filter_ref(schema_id=filter_schema_id, template=filter_template, filter=filter_name) + + # Get contract by unique identifier "name". + contract_obj = next((item for item in template_obj.get("contracts") if item.get("name") == contract_name), None) + if contract_obj: + if contract_obj.get("filterType") != contract_filter_type: + mso.fail_json( + msg="Current filter type '{0}' for contract '{1}' is not allowed to change to '{2}'.".format( + contract_obj.get("filterType"), contract_name, contract_filter_type + ) + ) + contract_path = "{0}/{1}".format(base_contract_path, contract_name) + if filter_name: + # Get filter by unique identifier "filterRef". + filter_obj = next((item for item in contract_obj.get(filter_key) if item.get("filterRef") == filter_ref), None) + if filter_obj: + filter_path = "{0}/{1}".format(base_filter_path, filter_name) + mso.update_filter_obj(contract_obj, filter_obj, filter_type) + mso.existing = filter_obj + + if state == "query": + if not contract_obj: + existing_contracts = [c.get("name") for c in template_obj.get("contracts")] + mso.fail_json(msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format(contract_name, ", ".join(existing_contracts))) + + # If filter name is not provided, provide overview of all filter objects for the filter type. + if not filter_name: + mso.existing = contract_obj.get(filter_key) + for filter_obj in mso.existing: + mso.update_filter_obj(contract_obj, filter_obj, filter_type) + + elif not mso.existing: + mso.fail_json(msg="FilterRef '{filter_ref}' not found".format(filter_ref=filter_ref)) + + mso.exit_json() + + mso.previous = mso.existing + + if state == "absent": + # Contracts need at least one filter left, remove contract if remove would lead 0 filters remaining. + if contract_obj: + if len(contract_obj.get(filter_key)) == 1: + mso.existing = {} + ops.append(dict(op="remove", path=contract_path)) + elif len(contract_obj.get(filter_key)) > 1: + mso.existing = {} + ops.append(dict(op="remove", path=filter_path)) + + elif state == "present": + contract_scope = "context" if contract_scope == "vrf" else contract_scope + + # Initialize "present" state filter variables + if not filter_directives: + # Avoid validation error: "Bad Request: (0)(1)(0) 'directives' is undefined on object + if not filter_obj: + filter_directives = ["none"] + else: + filter_directives = filter_obj.get("directives", ["none"]) + + elif "policy_compression" in filter_directives: + filter_directives[filter_directives.index("policy_compression")] = "no_stats" + filter_payload = dict( + filterRef=dict( + filterName=filter_name, + templateName=filter_template, + schemaId=filter_schema_id, + ), + directives=filter_directives, + ) + if filter_action: + filter_payload.update(action=filter_action) + if filter_action == "deny" and filter_priority: + filter_payload.update(priorityOverride=PRIORITY_MAP.get(filter_priority)) + + # If contract exist the operation should be set to replace else operation is add to create new contract. + if contract_obj: + if contract_display_name: + ops.append(dict(op="replace", path=contract_path + "/displayName", value=contract_display_name)) + # Conditional statement 'description == ""' is needed to allow setting the description back to empty string. + if description or description == "": + ops.append(dict(op="replace", path=contract_path + "/description", value=description)) + if qos_level: + # Conditional statement is needed to determine if "prio" exist in contract object. + # An object can be created in 3.3 higher version without prio via the API. + # In the GUI a default is set to "unspecified" and thus prio is always configured via GUI. + # We can't set a default of "unspecified" because prior to version 3.3 qos_level is not supported, + # thus the logic is needed for both add and replace operation + if contract_obj.get("prio"): + ops.append(dict(op="replace", path=contract_path + "/prio", value=qos_level)) + else: + ops.append(dict(op="add", path=contract_path + "/prio", value=qos_level)) + if contract_scope: + ops.append(dict(op="replace", path=contract_path + "/scope", value=contract_scope)) + + # If filter exist the operation should be set to replace else operation is add to create new filter. + if filter_obj: + ops.append(dict(op="replace", path=filter_path, value=filter_payload)) + else: + ops.append(dict(op="add", path=filter_path, value=filter_payload)) + + else: + contract_display_name = contract_display_name if contract_display_name else contract_name + # If contract_scope is not provided default to context to match GUI behaviour on create new contract. + contract_scope = "context" if contract_scope is None else contract_scope + contract_payload = dict(name=contract_name, displayName=contract_display_name, filterType=contract_filter_type, scope=contract_scope) + if description: + contract_payload.update(description=description) + if qos_level: + contract_payload.update(prio=qos_level) + if filter_key == "filterRelationships": + contract_payload.update(filterRelationships=[filter_payload]) + elif filter_key == "filterRelationshipsConsumerToProvider": + contract_payload.update(filterRelationshipsConsumerToProvider=[filter_payload]) + elif filter_key == "filterRelationshipsProviderToConsumer": + contract_payload.update(filterRelationshipsProviderToConsumer=[filter_payload]) + ops.append(dict(op="add", path=contract_path, value=contract_payload)) + + mso.sanitize(filter_payload, collate=True, unwanted=["filterType", "contractScope", "contractFilterType"]) + + # Update existing with filter (mso.sent) and contract information. + mso.existing = mso.sent + mso.existing["displayName"] = contract_display_name if contract_display_name else contract_obj.get("displayName") + mso.existing["filterType"] = filter_type + mso.existing["contractScope"] = contract_scope if contract_scope else contract_obj.get("scope") + mso.existing["contractFilterType"] = contract_filter_type + # Conditional statement 'description == ""' is needed to allow setting the description back to empty string. + if description or (contract_obj and (contract_obj.get("description") or contract_obj.get("description") == "")): + mso.existing["description"] = description if description or description == "" else contract_obj.get("description") + # Conditional statement to check qos_level is defined or is present in the contract object. + # qos_level is not supported prior to 3.3 thus this check in place, GUI uses default of "unspecified" from 3.3. + # When default of "unspecified" is set, conditional statement can be simplified since "prio" always present. + if qos_level or (contract_obj and contract_obj.get("prio")): + mso.existing["prio"] = qos_level if qos_level else contract_obj.get("prio") + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py new file mode 100644 index 000000000..0e398843b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py @@ -0,0 +1,317 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_contract_service_graph +short_description: Manage the service graph association with a contract in schema template +description: +- Manage the service graph association with a contract in schema template on Cisco ACI Multi-Site. +- The Contract Service Graph parameter is supported on versions of MSO/NDO that are 3.3 or greater. +author: +- Akini Ross (@akinross) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + contract: + description: + - The name of the contract. + type: str + required: true + service_graph: + description: + - The service graph to associate with this contract. + type: str + service_graph_template: + description: + - The template name in which the service graph is located. + type: str + service_graph_schema: + description: + - The schema name in which the service graph is located. + type: str + service_nodes: + description: + - A list of nodes and their connector details associated with the Service Graph. + - The order of the list matches the node id ordering in GUI, so first entry in list will be match node 1. + type: list + elements: dict + suboptions: + provider: + description: + - The name of the Bridge Domain. + required: true + type: str + consumer: + description: + - The name of the Bridge Domain. + required: true + type: str + connector_object_type: + description: + - The connector ACI object type of the node. + type: str + default: bd + choices: [ bd ] + provider_schema: + description: + - The schema name in which the provider is located. + type: str + provider_template: + description: + - The template name in which the provider is located. + type: str + consumer_schema: + description: + - The schema name in which the consumer is located. + type: str + consumer_template: + description: + - The template name in which the consumer is located. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_contract_filter +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new contract service graph + cisco.mso.mso_schema_template_contract_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + service_graph: SG1 + service_graph_nodes: + - provider: b1 + consumer: b2 + filter: Filter 1 + state: present + delegate_to: localhost + +- name: Remove a contract service graph + cisco.mso.mso_schema_template_contract_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + service_graph: SG1 + state: absent + delegate_to: localhost + +- name: Query a contract service graph + cisco.mso.mso_schema_template_contract_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + contract: Contract 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_connector_spec +from ansible_collections.cisco.mso.plugins.module_utils.constants import SERVICE_NODE_CONNECTOR_MAP + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + contract=dict(type="str", required=True), + service_graph=dict(type="str"), + service_graph_template=dict(type="str"), + service_graph_schema=dict(type="str"), + service_nodes=dict(type="list", elements="dict", options=mso_service_graph_connector_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["service_graph"]], + ["state", "present", ["service_graph", "service_nodes"]], + ], + ) + + schema = module.params.get("schema") + template_name = module.params.get("template").replace(" ", "") + contract_name = module.params.get("contract") + service_graph_name = module.params.get("service_graph") + service_graph_template = module.params.get("service_graph_template") + service_graph_schema = module.params.get("service_graph_schema") + service_nodes = module.params.get("service_nodes") + + state = module.params.get("state") + + mso = MSOModule(module) + + # Initialize variables + ops = [] + service_graph_obj = None + + # Set path defaults for create logic, if object (contract or filter) is found replace the "-" for specific value + base_contract_path = "/templates/{0}/contracts".format(template_name) + service_graph_path = "{0}/{1}/serviceGraphRelationship".format(base_contract_path, contract_name) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + template_obj = next((item for item in schema_obj.get("templates") if item.get("name") == template_name), None) + if not template_obj: + mso.fail_json( + msg="Provided template '{0}' does not exist. Existing templates: {1}".format( + template_name, ", ".join([t.get("name") for t in schema_obj.get("templates")]) + ) + ) + + # Get contract + contract_obj = next((item for item in template_obj.get("contracts") if item.get("name") == contract_name), None) + if contract_obj: + # Get service graph if it exists in contract + if contract_obj.get("serviceGraphRelationship"): + service_graph_obj = contract_obj.get("serviceGraphRelationship") + mso.update_service_graph_obj(service_graph_obj) + mso.existing = service_graph_obj + else: + mso.fail_json( + msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format( + contract_name, ", ".join([c.get("name") for c in template_obj.get("contracts")]) + ) + ) + + if state == "query": + mso.exit_json() + + mso.previous = mso.existing + + if state == "absent": + if contract_obj.get("serviceGraphRelationship"): + mso.existing = {} + ops.append(dict(op="remove", path=service_graph_path)) + + elif state == "present": + service_nodes_relationship = [] + service_graph_template = service_graph_template.replace(" ", "") if service_graph_template else template_name + service_graph_schema = service_graph_schema if service_graph_schema else schema + service_graph_schema_id, service_graph_schema_path, service_graph_schema_obj = mso.query_schema(service_graph_schema) + + # Validation to check if amount of service graph nodes provided is matching the service graph template. + # The API allows providing more or less service graph nodes behaviour but the GUI does not. + service_graph_template_obj = next((item for item in service_graph_schema_obj.get("templates") if item.get("name") == service_graph_template), None) + if not service_graph_template_obj: + mso.fail_json( + msg="Provided template '{0}' does not exist. Existing templates: {1}".format( + template_name, ", ".join([t.get("name") for t in service_graph_schema_obj.get("templates")]) + ) + ) + service_graph_schema_obj = next((item for item in service_graph_template_obj.get("serviceGraphs") if item.get("name") == service_graph_name), None) + if service_graph_schema_obj: + if len(service_nodes) < len(service_graph_schema_obj.get("serviceNodes")): + mso.fail_json( + msg="Not enough service nodes defined, {0} service node(s) provided when {1} needed.".format( + len(service_nodes), len(service_graph_schema_obj.get("serviceNodes")) + ) + ) + elif len(service_nodes) > len(service_graph_schema_obj.get("serviceNodes")): + mso.fail_json( + msg="Too many service nodes defined, {0} service nodes provided when {1} needed.".format( + len(service_nodes), len(service_graph_schema_obj.get("serviceNodes")) + ) + ) + else: + mso.fail_json(msg="Provided service graph '{0}' does not exist.".format(service_graph_name)) + + for node_id, service_node in enumerate(service_nodes, 0): + # Consumer and provider share connector details (so provider/consumer could have separate details in future) + connector_details = SERVICE_NODE_CONNECTOR_MAP.get(service_node.get("connector_object_type")) + provider_schema = mso.lookup_schema(service_node.get("provider_schema")) if service_node.get("provider_schema") else schema_id + provider_template = service_node.get("provider_template").replace(" ", "") if service_node.get("provider_template") else template_name + consumer_schema = mso.lookup_schema(service_node.get("consumer_schema")) if service_node.get("consumer_schema") else schema_id + consumer_template = service_node.get("consumer_template").replace(" ", "") if service_node.get("consumer_template") else template_name + + service_nodes_relationship.append( + { + "serviceNodeRef": dict( + schemaId=service_graph_schema_id, + templateName=service_graph_template, + serviceGraphName=service_graph_name, + serviceNodeName=service_graph_schema_obj.get("serviceNodes")[node_id].get("name"), + ), + "providerConnector": { + "connectorType": connector_details.get("connector_type"), + "{0}Ref".format(connector_details.get("id")): { + "schemaId": provider_schema, + "templateName": provider_template, + "{0}Name".format(connector_details.get("id")): service_node.get("provider"), + }, + }, + "consumerConnector": { + "connectorType": connector_details.get("connector_type"), + "{0}Ref".format(connector_details.get("id")): { + "schemaId": consumer_schema, + "templateName": consumer_template, + "{0}Name".format(connector_details.get("id")): service_node.get("consumer"), + }, + }, + } + ) + + service_graph_payload = dict( + serviceGraphRef=dict(serviceGraphName=service_graph_name, templateName=service_graph_template, schemaId=service_graph_schema_id), + serviceNodesRelationship=service_nodes_relationship, + ) + + # If service graph exist the operation should be set to "replace" else operation is "add" to create new + if service_graph_obj: + ops.append(dict(op="replace", path=service_graph_path, value=service_graph_payload)) + else: + ops.append(dict(op="add", path=service_graph_path, value=service_graph_payload)) + + mso.existing = mso.sent = service_graph_payload + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py new file mode 100644 index 000000000..49df465c5 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py @@ -0,0 +1,147 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_deploy +short_description: Deploy schema templates to sites +description: +- Deploy schema templates to sites. +- Prior to deploy a schema validation is executed for MSO releases running on the ND platform. +- When schema validation fails, M(cisco.mso.mso_schema_template_deploy) fails and deploy will not be executed. +- DEPRECATED for NDO v4.1 and later. Use M(cisco.mso.ndo_schema_template_deploy) on NDO v4.1 and later. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + aliases: [ name ] + site: + description: + - The name of the site B(to undeploy). + type: str + state: + description: + - Use C(deploy) to deploy schema template. + - Use C(status) to get deployment status. + - Use C(undeploy) to deploy schema template from a site. + type: str + choices: [ deploy, status, undeploy ] + default: deploy +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_template +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Deploy a schema template + cisco.mso.mso_schema_template_deploy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: deploy + delegate_to: localhost + +- name: Undeploy a schema template + cisco.mso.mso_schema_template_deploy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + site: Site 1 + state: undeploy + delegate_to: localhost + +- name: Get deployment status + cisco.mso.mso_schema: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: status + delegate_to: localhost + register: status_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True, aliases=["name"]), + site=dict(type="str"), + state=dict(type="str", default="deploy", choices=["deploy", "status", "undeploy"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "undeploy", ["site"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + site = module.params.get("site") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema id + schema_id = mso.lookup_schema(schema) + + payload = dict( + schemaId=schema_id, + templateName=template, + ) + + qs = None + if state == "deploy": + if mso.platform == "nd": + mso.validate_schema(schema_id) + path = "execute/schema/{0}/template/{1}".format(schema_id, template) + elif state == "status": + path = "status/schema/{0}/template/{1}".format(schema_id, template) + elif state == "undeploy": + path = "execute/schema/{0}/template/{1}".format(schema_id, template) + site_id = mso.lookup_site(site) + qs = dict(undeploy=site_id) + + if not module.check_mode: + status = mso.request(path, method="GET", data=payload, qs=qs) + mso.exit_json(**status) + else: + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py new file mode 100644 index 000000000..707ad7320 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_deploy_status +short_description: Check query of objects before deployment to site +description: +- Check query of objects in a template of a schema +author: +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + aliases: [ name ] + template: + description: + - The name of the template. + type: str + site: + description: + - The name of the site. + type: str + state: + description: + - Use C(query) for listing query of objects. + type: str + choices: [ query ] + default: query +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" + +- name: Query status of objects in a template + cisco.mso.mso_schema_template_deploy_status: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query status of objects using site + cisco.mso.mso_schema_template_deploy_status: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + site: ansible_test + state: query + delegate_to: localhost + register: query_result + +- name: Query status of objects in a template associated with a site + cisco.mso.mso_schema_template_deploy_status: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + site: ansible_test + state: query + delegate_to: localhost + register: query_result + +- name: Query status of objects in all templates + cisco.mso.mso_schema_template_deploy_status: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", aliases=["name"]), + template=dict(type="str"), + site=dict(type="str"), + state=dict(type="str", default="query", choices=["query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "query", ["schema"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template") + if template is not None: + template = template.replace(" ", "") + site = module.params.get("site") + state = module.params.get("state") + + mso = MSOModule(module) + + schema_id = None + path = "schemas" + + get_schema = mso.get_obj(path, displayName=schema) + if get_schema: + schema_id = get_schema.get("id") + path = "schemas/{id}/policy-states".format(id=schema_id) + else: + mso.fail_json(msg="Schema '{0}' not found.".format(schema)) + + if state == "query": + get_data = mso.request(path, method="GET") + mso.existing = [] + if template: + for configuration_objects in get_data.get("policyStates"): + if configuration_objects.get("templateName") == template: + mso.existing.append(configuration_objects) + if not mso.existing: + mso.fail_json(msg="Template '{0}' not found.".format(template)) + + if site: + mso.existing.clear() + for configuration_objects in get_data.get("policyStates"): + if configuration_objects.get("siteId") == mso.lookup_site(site): + if template: + if configuration_objects.get("templateName") == template: + mso.existing = configuration_objects + else: + mso.existing.append(configuration_objects) + if template is not None and not mso.existing: + mso.fail_json(msg="Provided Template '{0}' not associated with Site '{1}'.".format(template, site)) + + if template is None and site is None: + mso.existing = get_data + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py new file mode 100644 index 000000000..ce201913f --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_external_epg +short_description: Manage external EPGs in schema templates +description: +- Manage external EPGs in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + external_epg: + description: + - The name of the external EPG to manage. + type: str + aliases: [ name, externalepg ] + description: + description: + - The description of external EPG is supported on versions of MSO that are 3.3 or greater. + type: str + type: + description: + - The type of external epg. + - anp needs to be associated with external epg when the type is cloud. + - l3out can be associated with external epg when the type is on-premise. + type: str + choices: [ on-premise, cloud ] + default: on-premise + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + vrf: + description: + - The VRF associated with the external epg. + type: dict + suboptions: + name: + description: + - The name of the VRF to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current template. + type: str + l3out: + description: + - The L3Out associated with the external epg. + type: dict + suboptions: + name: + description: + - The name of the L3Out to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced L3Out. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced L3Out. + - If this parameter is unspecified, it defaults to the current template. + type: str + anp: + description: + - The anp associated with the external epg. + type: dict + suboptions: + name: + description: + - The name of the anp to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced anp. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced anp. + - If this parameter is unspecified, it defaults to the current template. + type: str + preferred_group: + description: + - Preferred Group is enabled for this External EPG or not. + type: bool + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new external EPG + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + vrf: + name: VRF + schema: Schema 1 + template: Template 1 + state: present + delegate_to: localhost + +- name: Add a new external EPG with external epg in cloud + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + type: cloud + vrf: + name: VRF + schema: Schema 1 + template: Template 1 + anp: + name: ANP1 + schema: Schema 1 + template: Template 1 + state: present + delegate_to: localhost + +- name: Remove an external EPG + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: external EPG1 + state: absent + delegate_to: localhost + +- name: Query a specific external EPGs + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: external EPG1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all external EPGs + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + external_epg=dict(type="str", aliases=["name", "externalepg"]), # This parameter is not required for querying all objects + description=dict(type="str"), + display_name=dict(type="str"), + vrf=dict(type="dict", options=mso_reference_spec()), + l3out=dict(type="dict", options=mso_reference_spec()), + anp=dict(type="dict", options=mso_reference_spec()), + preferred_group=dict(type="bool"), + type=dict(type="str", default="on-premise", choices=["on-premise", "cloud"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["external_epg"]], + ["state", "present", ["external_epg", "vrf"]], + ["type", "cloud", ["anp"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + external_epg = module.params.get("external_epg") + description = module.params.get("description") + display_name = module.params.get("display_name") + vrf = module.params.get("vrf") + if vrf is not None and vrf.get("template") is not None: + vrf["template"] = vrf.get("template").replace(" ", "") + l3out = module.params.get("l3out") + if l3out is not None and l3out.get("template") is not None: + l3out["template"] = l3out.get("template").replace(" ", "") + anp = module.params.get("anp") + if anp is not None and anp.get("template") is not None: + anp["template"] = anp.get("template").replace(" ", "") + preferred_group = module.params.get("preferred_group") + type_ext_epg = module.params.get("type") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get external EPGs + external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]] + + if external_epg is not None and external_epg in external_epgs: + external_epg_idx = external_epgs.index(external_epg) + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx] + if "externalEpgRef" in mso.existing: + del mso.existing["externalEpgRef"] + if "vrfRef" in mso.existing: + mso.existing["vrfRef"] = mso.dict_from_ref(mso.existing.get("vrfRef")) + if "l3outRef" in mso.existing: + mso.existing["l3outRef"] = mso.dict_from_ref(mso.existing.get("l3outRef")) + if "anpRef" in mso.existing: + mso.existing["anpRef"] = mso.dict_from_ref(mso.existing.get("anpRef")) + + if state == "query": + if external_epg is None: + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"] + elif not mso.existing: + mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg)) + mso.exit_json() + + eepgs_path = "/templates/{0}/externalEpgs".format(template) + eepg_path = "/templates/{0}/externalEpgs/{1}".format(template, external_epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=eepg_path)) + + elif state == "present": + vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template) + l3out_ref = mso.make_reference(l3out, "l3out", schema_id, template) + anp_ref = mso.make_reference(anp, "anp", schema_id, template) + if display_name is None and not mso.existing: + display_name = external_epg + + payload = dict( + name=external_epg, + displayName=display_name, + vrfRef=vrf_ref, + preferredGroup=preferred_group, + ) + + if description is not None: + payload.update(description=description) + + if type_ext_epg == "cloud": + payload["extEpgType"] = "cloud" + payload["anpRef"] = anp_ref + else: + payload["l3outRef"] = l3out_ref + + mso.sanitize(payload, collate=True) + + if mso.existing: + # clean anpRef when anpRef is null + if "anpRef" in mso.existing and mso.existing.get("anpRef") is None: + del mso.existing["anpRef"] + # clean contractRef to fix api issue + for contract in mso.sent.get("contractRelationships"): + contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef")) + ops.append(dict(op="replace", path=eepg_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=eepgs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py new file mode 100644 index 000000000..4029175bc --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: mso_schema_template_external_epg_contract +short_description: Manage Extrnal EPG contracts in schema templates +description: +- Manage External EPG contracts in schema templates on Cisco ACI Multi-Site. +author: +- Devarshi Shah (@devarshishah3) +version_added: '0.0.8' +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + external_epg: + description: + - The name of the EPG to manage. + type: str + required: true + contract: + description: + - A contract associated to this EPG. + type: dict + suboptions: + name: + description: + - The name of the Contract to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced BD. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced BD. + type: str + type: + description: + - The type of contract. + type: str + required: true + choices: [ consumer, provider ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_external_epg +- module: cisco.mso.mso_schema_template_contract_filter +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a contract to an EPG + cisco.mso.mso_schema_template_external_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + epg: EPG 1 + contract: + name: Contract 1 + type: consumer + state: present + delegate_to: localhost + +- name: Remove a Contract + cisco.mso.mso_schema_template_external_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + epg: EPG 1 + contract: + name: Contract 1 + state: absent + delegate_to: localhost + +- name: Query a specific Contract + cisco.mso.mso_schema_template_external_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + epg: EPG 1 + contract: + name: Contract 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Contracts + cisco.mso.mso_schema_template_external_epg_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + external_epg=dict(type="str", required=True), + contract=dict(type="dict", options=mso_contractref_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["contract"]], + ["state", "present", ["contract"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + external_epg = module.params.get("external_epg") + contract = module.params.get("contract") + if contract is not None and contract.get("template") is not None: + contract["template"] = contract.get("template").replace(" ", "") + state = module.params.get("state") + + mso = MSOModule(module) + + if contract: + if contract.get("schema") is None: + contract["schema"] = schema + contract["schema_id"] = mso.lookup_schema(contract.get("schema")) + if contract.get("template") is None: + contract["template"] = template + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get EPG + epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]] + if external_epg not in epgs: + mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=external_epg, epgs=", ".join(epgs))) + epg_idx = epgs.index(external_epg) + + # Get Contract + if contract: + contracts = [ + (c.get("contractRef"), c.get("relationshipType")) + for c in schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["contractRelationships"] + ] + contract_ref = mso.contract_ref(**contract) + if (contract_ref, contract.get("type")) in contracts: + contract_idx = contracts.index((contract_ref, contract.get("type"))) + contract_path = "/templates/{0}/externalEpgs/{1}/contractRelationships/{2}".format(template, external_epg, contract_idx) + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["contractRelationships"][contract_idx] + + if state == "query": + if not contract: + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["contractRelationships"] + elif not mso.existing: + mso.fail_json(msg="Contract '{0}' not found".format(contract_ref)) + + if "contractRef" in mso.existing: + mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef")) + mso.exit_json() + + contracts_path = "/templates/{0}/externalEpgs/{1}/contractRelationships".format(template, external_epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=contract_path)) + + elif state == "present": + payload = dict( + relationshipType=contract.get("type"), + contractRef=dict( + contractName=contract.get("name"), + templateName=contract.get("template"), + schemaId=contract.get("schema_id"), + ), + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=contract_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=contracts_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if "contractRef" in mso.previous: + mso.previous["contractRef"] = mso.dict_from_ref(mso.previous.get("contractRef")) + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py new file mode 100644 index 000000000..7d4b93b27 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py @@ -0,0 +1,250 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_external_epg_selector +short_description: Manage External EPG selector in schema templates +description: +- Manage External EPG selector in schema templates on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +- Cindy Zhao (@cizhao) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + external_epg: + description: + - The name of the External EPG to be managed. + type: str + required: true + selector: + description: + - The name of the selector. + type: str + expressions: + description: + - Expressions associated to this selector. + type: list + elements: dict + suboptions: + type: + description: + - The name of the expression which in this case is always IP address. + required: true + type: str + choices: [ ip_address ] + operator: + description: + - The operator associated with the expression which in this case is always equals. + required: true + type: str + choices: [ equals ] + value: + description: + - The value of the IP Address / Subnet associated with the expression. + required: true + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_external_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a selector to an External EPG + cisco.mso.mso_schema_template_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: extEPG 1 + selector: selector_1 + expressions: + - type: ip_address + operator: equals + value: 10.0.0.0 + state: present + delegate_to: localhost + +- name: Remove a Selector + cisco.mso.mso_schema_template_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: extEPG 1 + selector: selector_1 + state: absent + delegate_to: localhost + +- name: Query a specific Selector + cisco.mso.mso_schema_template_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: extEPG 1 + selector: selector_1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Selectors + cisco.mso.mso_schema_template_external_epg_selector: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: extEPG 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec_ext_epg + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + external_epg=dict(type="str", required=True), + selector=dict(type="str"), + expressions=dict(type="list", elements="dict", options=mso_expression_spec_ext_epg()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["selector"]], + ["state", "present", ["selector"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + external_epg = module.params.get("external_epg") + selector = module.params.get("selector") + expressions = module.params.get("expressions") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + template_idx = templates.index(template) + + # Get External EPG + external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]] + if external_epg not in external_epgs: + mso.fail_json( + msg="Provided external epg '{external_epg}' does not exist. Existing epgs: {external_epgs}".format( + external_epg=external_epg, external_epgs=", ".join(external_epgs) + ) + ) + external_epg_idx = external_epgs.index(external_epg) + + # Get Selector + selectors = [s.get("name") for s in schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]["selectors"]] + if selector in selectors: + selector_idx = selectors.index(selector) + selector_path = "/templates/{0}/externalEpgs/{1}/selectors/{2}".format(template, external_epg, selector_idx) + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]["selectors"][selector_idx] + + if state == "query": + if selector is None: + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]["selectors"] + elif not mso.existing: + mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector)) + mso.exit_json() + + selectors_path = "/templates/{0}/externalEpgs/{1}/selectors/-".format(template, external_epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=selector_path)) + + elif state == "present": + # Get expressions + types = dict(ip_address="ipAddress") + all_expressions = [] + if expressions: + for expression in expressions: + type_val = expression.get("type") + operator = expression.get("operator") + value = expression.get("value") + all_expressions.append( + dict( + key=types.get(type_val), + operator=operator, + value=value, + ) + ) + + payload = dict( + name=selector, + expressions=all_expressions, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=selector_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=selectors_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.existing != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py new file mode 100644 index 000000000..c7512c2ee --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_external_epg_subnet +short_description: Manage External EPG subnets in schema templates +description: +- Manage External EPG subnets in schema templates on Cisco ACI Multi-Site. +author: +- Devarshi Shah (@devarshishah3) +- Anvitha Jain (@anvitha-jain) +version_added: '0.0.8' +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + external_epg: + description: + - The name of the External EPG to manage. + type: str + required: true + subnet: + description: + - The IP range in CIDR notation. + type: str + scope: + description: + - The scope parameter contains two sections 1. Route Control and 2. External EPG Classification. + - The existing Route Control parameters are C(export-rtctrl) for Export Route Control, C(import-rtctrl) for Import Route Control + - and C(shared-rtctrl) for Shared Route Control + - The existing External EPG Classification parameters are C(import-security) for External Subnets for External EPG + - and C(shared-security) for Shared Security Import + - The C(shared-security) for Shared Security Import can only be used when External Subnets for External EPG is present + type: list + elements: str + aggregate: + description: + - The aggregate option aggregates shared routes for the subnet. + - Use C(shared-rtctrl) to add Aggregate Shared Routes + - The C(shared-rtctrl) option can only be used when scope parameter Shared Route Control in the Route Control section is selected. + type: list + elements: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new subnet to an External EPG + cisco.mso.mso_schema_template_external_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: EPG 1 + subnet: 10.0.0.0/24 + state: present + delegate_to: localhost + +- name: Remove a subnet from an External EPG + cisco.mso.mso_schema_template_external_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: EPG 1 + subnet: 10.0.0.0/24 + state: absent + delegate_to: localhost + +- name: Query a specific External EPG subnet + cisco.mso.mso_schema_template_external_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: EPG 1 + subnet: 10.0.0.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all External EPGs subnets + cisco.mso.mso_schema_template_external_epg_subnet: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + external_epg=dict(type="str", required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + subnet=dict(type="str"), + scope=dict(type="list", elements="str", default=[]), + aggregate=dict(type="list", elements="str", default=[]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet"]], + ["state", "present", ["subnet"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + external_epg = module.params.get("external_epg") + subnet = module.params.get("subnet") + scope = module.params.get("scope") + aggregate = module.params.get("aggregate") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + template_idx = templates.index(template) + + # Get EPG + external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]] + if external_epg not in external_epgs: + mso.fail_json(msg="Provided External EPG '{epg}' does not exist. Existing epgs: {epgs}".format(epg=external_epg, epgs=", ".join(external_epgs))) + epg_idx = external_epgs.index(external_epg) + + # Get Subnet + subnets = [s.get("ip") for s in schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["subnets"]] + if subnet in subnets: + subnet_idx = subnets.index(subnet) + # FIXME: Changes based on index are DANGEROUS + subnet_path = "/templates/{0}/externalEpgs/{1}/subnets/{2}".format(template, external_epg, subnet_idx) + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["subnets"][subnet_idx] + + if state == "query": + if subnet is None: + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["subnets"] + elif not mso.existing: + mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet)) + mso.exit_json() + + subnets_path = "/templates/{0}/externalEpgs/{1}/subnets".format(template, external_epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.existing = {} + ops.append(dict(op="remove", path=subnet_path)) + + elif state == "present": + payload = dict( + ip=subnet, + scope=scope, + aggregate=aggregate, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=subnet_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py new file mode 100644 index 000000000..ce201913f --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_external_epg +short_description: Manage external EPGs in schema templates +description: +- Manage external EPGs in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + external_epg: + description: + - The name of the external EPG to manage. + type: str + aliases: [ name, externalepg ] + description: + description: + - The description of external EPG is supported on versions of MSO that are 3.3 or greater. + type: str + type: + description: + - The type of external epg. + - anp needs to be associated with external epg when the type is cloud. + - l3out can be associated with external epg when the type is on-premise. + type: str + choices: [ on-premise, cloud ] + default: on-premise + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + vrf: + description: + - The VRF associated with the external epg. + type: dict + suboptions: + name: + description: + - The name of the VRF to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current template. + type: str + l3out: + description: + - The L3Out associated with the external epg. + type: dict + suboptions: + name: + description: + - The name of the L3Out to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced L3Out. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced L3Out. + - If this parameter is unspecified, it defaults to the current template. + type: str + anp: + description: + - The anp associated with the external epg. + type: dict + suboptions: + name: + description: + - The name of the anp to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced anp. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced anp. + - If this parameter is unspecified, it defaults to the current template. + type: str + preferred_group: + description: + - Preferred Group is enabled for this External EPG or not. + type: bool + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new external EPG + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + vrf: + name: VRF + schema: Schema 1 + template: Template 1 + state: present + delegate_to: localhost + +- name: Add a new external EPG with external epg in cloud + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: External EPG 1 + type: cloud + vrf: + name: VRF + schema: Schema 1 + template: Template 1 + anp: + name: ANP1 + schema: Schema 1 + template: Template 1 + state: present + delegate_to: localhost + +- name: Remove an external EPG + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: external EPG1 + state: absent + delegate_to: localhost + +- name: Query a specific external EPGs + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + external_epg: external EPG1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all external EPGs + cisco.mso.mso_schema_template_external_epg: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + external_epg=dict(type="str", aliases=["name", "externalepg"]), # This parameter is not required for querying all objects + description=dict(type="str"), + display_name=dict(type="str"), + vrf=dict(type="dict", options=mso_reference_spec()), + l3out=dict(type="dict", options=mso_reference_spec()), + anp=dict(type="dict", options=mso_reference_spec()), + preferred_group=dict(type="bool"), + type=dict(type="str", default="on-premise", choices=["on-premise", "cloud"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["external_epg"]], + ["state", "present", ["external_epg", "vrf"]], + ["type", "cloud", ["anp"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + external_epg = module.params.get("external_epg") + description = module.params.get("description") + display_name = module.params.get("display_name") + vrf = module.params.get("vrf") + if vrf is not None and vrf.get("template") is not None: + vrf["template"] = vrf.get("template").replace(" ", "") + l3out = module.params.get("l3out") + if l3out is not None and l3out.get("template") is not None: + l3out["template"] = l3out.get("template").replace(" ", "") + anp = module.params.get("anp") + if anp is not None and anp.get("template") is not None: + anp["template"] = anp.get("template").replace(" ", "") + preferred_group = module.params.get("preferred_group") + type_ext_epg = module.params.get("type") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get external EPGs + external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]] + + if external_epg is not None and external_epg in external_epgs: + external_epg_idx = external_epgs.index(external_epg) + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx] + if "externalEpgRef" in mso.existing: + del mso.existing["externalEpgRef"] + if "vrfRef" in mso.existing: + mso.existing["vrfRef"] = mso.dict_from_ref(mso.existing.get("vrfRef")) + if "l3outRef" in mso.existing: + mso.existing["l3outRef"] = mso.dict_from_ref(mso.existing.get("l3outRef")) + if "anpRef" in mso.existing: + mso.existing["anpRef"] = mso.dict_from_ref(mso.existing.get("anpRef")) + + if state == "query": + if external_epg is None: + mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"] + elif not mso.existing: + mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg)) + mso.exit_json() + + eepgs_path = "/templates/{0}/externalEpgs".format(template) + eepg_path = "/templates/{0}/externalEpgs/{1}".format(template, external_epg) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=eepg_path)) + + elif state == "present": + vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template) + l3out_ref = mso.make_reference(l3out, "l3out", schema_id, template) + anp_ref = mso.make_reference(anp, "anp", schema_id, template) + if display_name is None and not mso.existing: + display_name = external_epg + + payload = dict( + name=external_epg, + displayName=display_name, + vrfRef=vrf_ref, + preferredGroup=preferred_group, + ) + + if description is not None: + payload.update(description=description) + + if type_ext_epg == "cloud": + payload["extEpgType"] = "cloud" + payload["anpRef"] = anp_ref + else: + payload["l3outRef"] = l3out_ref + + mso.sanitize(payload, collate=True) + + if mso.existing: + # clean anpRef when anpRef is null + if "anpRef" in mso.existing and mso.existing.get("anpRef") is None: + del mso.existing["anpRef"] + # clean contractRef to fix api issue + for contract in mso.sent.get("contractRelationships"): + contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef")) + ops.append(dict(op="replace", path=eepg_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=eepgs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py new file mode 100644 index 000000000..c0ab485a4 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py @@ -0,0 +1,369 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_filter_entry +short_description: Manage filter entries in schema templates +description: +- Manage filter entries in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +- Anvitha Jain (@anvitha-jain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + filter: + description: + - The name of the filter to manage. + - There should be no space in the filter name. APIC will throw an error if a space is provided in the filter name. + - See the C(filter_display_name) attribute if you want the display name of the filter to contain a space. + type: str + required: true + filter_description: + description: + - The description of this filter is supported on versions of MSO that are 3.3 or greater. + type: str + default: '' + filter_display_name: + description: + - The name as displayed on the MSO web interface. + type: str + entry: + description: + - The filter entry name to manage. + type: str + aliases: [ name ] + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + aliases: [ entry_display_name ] + filter_entry_description: + description: + - The description of this filter entry. + type: str + aliases: [ entry_description, description ] + default: '' + ethertype: + description: + - The ethernet type to use for this filter entry. + type: str + choices: [ arp, fcoe, ip, ipv4, ipv6, mac-security, mpls-unicast, trill, unspecified ] + ip_protocol: + description: + - The IP protocol to use for this filter entry. + type: str + choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, unspecified ] + tcp_session_rules: + description: + - A list of TCP session rules. + type: list + elements: str + choices: [ acknowledgement, established, finish, synchronize, reset, unspecified ] + source_from: + description: + - The source port range from. + type: str + source_to: + description: + - The source port range to. + type: str + destination_from: + description: + - The destination port range from. + type: str + destination_to: + description: + - The destination port range to. + type: str + arp_flag: + description: + - The ARP flag to use for this filter entry. + type: str + choices: [ reply, request, unspecified ] + stateful: + description: + - Whether this filter entry is stateful. + type: bool + fragments_only: + description: + - Whether this filter entry only matches fragments. + type: bool + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_contract_filter +notes: +- Due to restrictions of the MSO REST API this module creates filters when needed, and removes them when the last entry has been removed. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new filter entry + cisco.mso.mso_schema_template_filter_entry: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + filter: Filter 1 + state: present + delegate_to: localhost + +- name: Remove a filter entry + cisco.mso.mso_schema_template_filter_entry: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + filter: Filter 1 + state: absent + delegate_to: localhost + +- name: Query a specific filter entry + cisco.mso.mso_schema_template_filter_entry: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + filter: Filter 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all filter entries + cisco.mso.mso_schema_template_filter_entry: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + filter=dict(type="str", required=True), + filter_description=dict(type="str", default=""), + filter_display_name=dict(type="str"), + entry=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + filter_entry_description=dict(type="str", default="", aliases=["entry_description", "description"]), + display_name=dict(type="str", aliases=["entry_display_name"]), + ethertype=dict(type="str", choices=["arp", "fcoe", "ip", "ipv4", "ipv6", "mac-security", "mpls-unicast", "trill", "unspecified"]), + ip_protocol=dict(type="str", choices=["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"]), + tcp_session_rules=dict(type="list", elements="str", choices=["acknowledgement", "established", "finish", "synchronize", "reset", "unspecified"]), + source_from=dict(type="str"), + source_to=dict(type="str"), + destination_from=dict(type="str"), + destination_to=dict(type="str"), + arp_flag=dict(type="str", choices=["reply", "request", "unspecified"]), + stateful=dict(type="bool"), + fragments_only=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["entry"]], + ["state", "present", ["entry"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + filter_name = module.params.get("filter") + filter_display_name = module.params.get("filter_display_name") + filter_description = module.params.get("filter_description") + entry = module.params.get("entry") + display_name = module.params.get("display_name") + filter_entry_description = module.params.get("filter_entry_description") + ethertype = module.params.get("ethertype") + ip_protocol = module.params.get("ip_protocol") + tcp_session_rules = module.params.get("tcp_session_rules") + source_from = module.params.get("source_from") + source_to = module.params.get("source_to") + destination_from = module.params.get("destination_from") + destination_to = module.params.get("destination_to") + arp_flag = module.params.get("arp_flag") + if arp_flag == "request": + arp_flag = "req" + stateful = module.params.get("stateful") + fragments_only = module.params.get("fragments_only") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + template_idx = templates.index(template) + + # Get filters + mso.existing = {} + filter_idx = None + entry_idx = None + filters = [f.get("name") for f in schema_obj.get("templates")[template_idx]["filters"]] + if filter_name in filters: + filter_idx = filters.index(filter_name) + + entries = [f.get("name") for f in schema_obj.get("templates")[template_idx]["filters"][filter_idx]["entries"]] + if entry in entries: + entry_idx = entries.index(entry) + mso.existing = schema_obj.get("templates")[template_idx]["filters"][filter_idx]["entries"][entry_idx] + + if state == "query": + if entry is None: + if filter_idx is None: + mso.fail_json(msg="Filter '{filter}' not found".format(filter=filter_name)) + mso.existing = schema_obj.get("templates")[template_idx]["filters"][filter_idx]["entries"] + elif not mso.existing: + mso.fail_json(msg="Entry '{entry}' not found".format(entry=entry)) + mso.exit_json() + + filters_path = "/templates/{0}/filters".format(template) + filter_path = "/templates/{0}/filters/{1}".format(template, filter_name) + entries_path = "/templates/{0}/filters/{1}/entries".format(template, filter_name) + entry_path = "/templates/{0}/filters/{1}/entries/{2}".format(template, filter_name, entry) + ops = [] + + mso.previous = mso.existing + if state == "absent": + mso.proposed = mso.sent = {} + + if filter_idx is None: + # There was no filter to begin with + pass + elif entry_idx is None: + # There was no entry to begin with + pass + elif len(entries) == 1: + # There is only one entry, remove filter + mso.existing = {} + ops.append(dict(op="remove", path=filter_path)) + + else: + mso.existing = {} + ops.append(dict(op="remove", path=entry_path)) + + elif state == "present": + if not mso.existing: + if display_name is None: + display_name = entry + if ethertype is None: + ethertype = "unspecified" + if ip_protocol is None: + ip_protocol = "unspecified" + if tcp_session_rules is None: + tcp_session_rules = ["unspecified"] + if source_from is None: + source_from = "unspecified" + if source_to is None: + source_to = "unspecified" + if destination_from is None: + destination_from = "unspecified" + if destination_to is None: + destination_to = "unspecified" + if arp_flag is None: + arp_flag = "unspecified" + if stateful is None: + stateful = False + if fragments_only is None: + fragments_only = False + + payload = dict( + name=entry, + displayName=display_name, + description=filter_entry_description, + etherType=ethertype, + ipProtocol=ip_protocol, + tcpSessionRules=tcp_session_rules, + sourceFrom=source_from, + sourceTo=source_to, + destinationFrom=destination_from, + destinationTo=destination_to, + arpFlag=arp_flag, + stateful=stateful, + matchOnlyFragments=fragments_only, + ) + + mso.sanitize(payload, collate=True) + + if filter_idx is None: + # Filter does not exist, so we have to create it + if filter_display_name is None: + filter_display_name = filter_name + + payload = dict( + name=filter_name, + displayName=filter_display_name, + description=filter_description, + entries=[mso.sent], + ) + + ops.append(dict(op="add", path=filters_path + "/-", value=payload)) + + elif entry_idx is None: + # Entry does not exist, so we have to add it + ops.append(dict(op="add", path=entries_path + "/-", value=mso.sent)) + + else: + # Entry exists, we have to update it + for key, value in mso.sent.items(): + ops.append(dict(op="replace", path=entry_path + "/" + key, value=value)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py new file mode 100644 index 000000000..4b8c1b66d --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py @@ -0,0 +1,233 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_l3out +short_description: Manage l3outs in schema templates +description: +- Manage l3outs in schema templates on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + l3out: + description: + - The name of the l3out to manage. + type: str + aliases: [ name ] + description: + description: + - The description of l3out is supported on versions of MSO that are 3.3 or greater. + type: str + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + vrf: + description: + - The VRF associated to this L3out. + type: dict + suboptions: + name: + description: + - The name of the VRF to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced VRF. + - If this parameter is unspecified, it defaults to the current schema. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new L3out + cisco.mso.mso_schema_template_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + schema: Schema 1 + template: Template 1 + l3out: L3out 1 + vrf: + name: vrfName + schema: vrfSchema + template: vrfTemplate + state: present + delegate_to: localhost + +- name: Remove an L3out + cisco.mso.mso_schema_template_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + l3out: L3out 1 + state: absent + delegate_to: localhost + +- name: Query a specific L3outs + cisco.mso.mso_schema_template_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + schema: Schema 1 + template: Template 1 + l3out: L3out 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all L3outs + cisco.mso.mso_schema_template_l3out: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + l3out=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + description=dict(type="str"), + display_name=dict(type="str"), + vrf=dict(type="dict", options=mso_reference_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["l3out"]], + ["state", "present", ["l3out", "vrf"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + l3out = module.params.get("l3out") + description = module.params.get("description") + display_name = module.params.get("display_name") + vrf = module.params.get("vrf") + if vrf is not None and vrf.get("template") is not None: + vrf["template"] = vrf.get("template").replace(" ", "") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema objects + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get L3out + l3outs = [l3.get("name") for l3 in schema_obj.get("templates")[template_idx]["intersiteL3outs"]] + + if l3out is not None and l3out in l3outs: + l3out_idx = l3outs.index(l3out) + mso.existing = schema_obj.get("templates")[template_idx]["intersiteL3outs"][l3out_idx] + + if state == "query": + if l3out is None: + mso.existing = schema_obj.get("templates")[template_idx]["intersiteL3outs"] + elif not mso.existing: + mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out)) + mso.exit_json() + + l3outs_path = "/templates/{0}/intersiteL3outs".format(template) + l3out_path = "/templates/{0}/intersiteL3outs/{1}".format(template, l3out) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=l3out_path)) + + elif state == "present": + vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template) + + if display_name is None and not mso.existing: + display_name = l3out + + payload = dict( + name=l3out, + displayName=display_name, + vrfRef=vrf_ref, + ) + + if description is not None: + payload.update(description=description) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=l3out_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=l3outs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py new file mode 100644 index 000000000..d0e15b8d0 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py @@ -0,0 +1,246 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_migrate +short_description: Migrate Bridge Domains (BDs) and EPGs between templates +description: +- Migrate BDs and EPGs between templates of same and different schemas. +author: +- Anvitha Jain (@anvitha-jain) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + bds: + description: + - The name of the BDs to migrate. + type: list + elements: str + epgs: + description: + - The name of the EPGs and the ANP it is in to migrate. + type: list + elements: dict + suboptions: + epg: + description: + - The name of the EPG to migrate. + type: str + required: true + anp: + description: + - The name of the anp to migrate. + type: str + required: true + target_schema: + description: + - The name of the target_schema. + type: str + required: true + target_template: + description: + - The name of the target_template. + type: str + required: true + state: + description: + - Use C(present) for adding. + type: str + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Migration of objects between templates of same schema + mso_schema_template_migrate: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + target_schema: Schema 1 + target_template: Template 2 + bds: + - BD + epgs: + - epg: EPG1 + anp: ANP + state: present + delegate_to: localhost + +- name: Migration of objects between templates of different schema + mso_schema_template_migrate: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + target_schema: Schema 2 + target_template: Template 2 + bds: + - BD + epgs: + - epg: EPG1 + anp: ANP + state: present + delegate_to: localhost + +- name: Migration of BD object between templates of same schema + mso_schema_template_migrate: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + target_schema: Schema 1 + target_template: Template 2 + bds: + - BD + - BD1 + state: present + delegate_to: localhost + +- name: Migration of BD object between templates of different schema + mso_schema_template_migrate: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + target_schema: Schema 2 + target_template: Template 2 + bds: + - BD + - BD1 + state: present + delegate_to: localhost + +- name: Migration of EPG objects between templates of same schema + mso_schema_template_migrate: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + target_schema: Schema 2 + target_template: Template 2 + epgs: + - epg: EPG1 + anp: ANP + - epg: EPG2 + anp: ANP2 + state: present + delegate_to: localhost + +- name: Migration of EPG objects between templates of different schema + mso_schema_template_migrate: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + target_schema: Schema 2 + target_template: Template 2 + epgs: + - epg: EPG1 + anp: ANP + - epg: EPG2 + anp: ANP2 + state: present + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_object_migrate_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + bds=dict(type="list", elements="str"), + epgs=dict(type="list", elements="dict", options=mso_object_migrate_spec()), + target_schema=dict(type="str", required=True), + target_template=dict(type="str", required=True), + state=dict(type="str", default="present"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + target_schema = module.params.get("target_schema") + target_template = module.params.get("target_template").replace(" ", "") + bds = module.params.get("bds") + epgs = module.params.get("epgs") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema_id + schema_id = mso.lookup_schema(schema) + + target_schema_id = mso.lookup_schema(target_schema) + + if state == "present": + if schema_id is not None: + bds_payload = [] + if bds is not None: + for bd in bds: + bds_payload.append(dict(name=bd)) + + anp_dict = {} + if epgs is not None: + for epg in epgs: + if epg.get("anp") in anp_dict: + anp_dict[epg.get("anp")].append(dict(name=epg.get("epg"))) + else: + anp_dict[epg.get("anp")] = [dict(name=epg.get("epg"))] + + anps_payload = [] + for anp, epgs_payload in anp_dict.items(): + anps_payload.append(dict(name=anp, epgs=epgs_payload)) + + payload = dict( + targetSchemaId=target_schema_id, + targetTemplateName=target_template, + bds=bds_payload, + anps=anps_payload, + ) + + template = template.replace(" ", "%20") + + target_template = target_template.replace(" ", "%20") # removes API error for extra space + + mso.existing = mso.request(path="migrate/schema/{0}/template/{1}".format(schema_id, template), method="POST", data=payload) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py new file mode 100644 index 000000000..70fadd804 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py @@ -0,0 +1,270 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_service_graph +short_description: Manage Service Graph in schema templates +description: +- Manage Service Graph in schema templates on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + service_graph: + description: + - The name of the Service Graph to manage. + type: str + aliases: [ name ] + description: + description: + - The description of Service Graph. + type: str + default: '' + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + service_nodes: + description: + - A list of node types to be associated with the Service Graph. + type: list + elements: dict + suboptions: + type: + description: + - The type of node + required: true + type: str + filter_after_first_node: + description: + - The filter applied after the first node. + type: str + choices: [ allow_all, filters_from_contract ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new Service Graph + cisco.mso.mso_schema_template_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + service_graph: graph1 + service_nodes: + - type: firewall + - type: other + - type: load-balancer + state: present + delegate_to: localhost + +- name: Remove a Service Graph + cisco.mso.mso_schema_template_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + service_graph: graph1 + state: absent + delegate_to: localhost + +- name: Query a specific Service Graph + cisco.mso.mso_schema_template_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + service_graph: graph1 + state: query + delegate_to: localhost + +- name: Query all Service Graphs + cisco.mso.mso_schema_template_service_graph: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema1 + template: Template1 + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_node_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + service_graph=dict(type="str", aliases=["name"]), + description=dict(type="str", default=""), + display_name=dict(type="str"), + service_nodes=dict(type="list", elements="dict", options=mso_service_graph_node_spec()), + filter_after_first_node=dict(type="str", choices=["allow_all", "filters_from_contract"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["service_graph"]], + ["state", "present", ["service_graph", "service_nodes"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + service_graph = module.params.get("service_graph") + display_name = module.params.get("display_name") + description = module.params.get("description") + service_nodes = module.params.get("service_nodes") + filter_after_first_node = module.params.get("filter_after_first_node") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json( + msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates)) + ) + template_idx = templates.index(template) + + mso.existing = {} + service_graph_idx = None + + # Get Service Graphs + service_graphs = [f.get("name") for f in schema_obj.get("templates")[template_idx]["serviceGraphs"]] + if service_graph in service_graphs: + service_graph_idx = service_graphs.index(service_graph) + mso.existing = schema_obj.get("templates")[template_idx]["serviceGraphs"][service_graph_idx] + + if state == "query": + if service_graph is None: + mso.existing = schema_obj.get("templates")[template_idx]["serviceGraphs"] + if service_graph is not None and service_graph_idx is None: + mso.fail_json(msg="Service Graph '{service_graph}' not found".format(service_graph=service_graph)) + mso.exit_json() + + service_graphs_path = "/templates/{0}/serviceGraphs/-".format(template) + service_graph_path = "/templates/{0}/serviceGraphs/{1}".format(template, service_graph) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=service_graph_path)) + + elif state == "present": + nodes_payload = [] + service_node_index = 0 + + if filter_after_first_node == "allow_all": + filter_after_first_node = "allow-all" + elif filter_after_first_node == "filters_from_contract": + filter_after_first_node = "filters-from-contract" + + if display_name is None: + display_name = service_graph + + # Get service nodes + query_node_data = mso.query_service_node_types() + service_node_types = [f.get("name") for f in query_node_data] + if service_nodes is not None: + for node in service_nodes: + node_name = node.get("type") + if node_name in service_node_types: + service_node_index = service_node_index + 1 + for node_data in query_node_data: + if node_data["name"] == node_name: + payload = dict( + name=node_name, + serviceNodeTypeId=node_data.get("id"), + index=service_node_index, + serviceNodeRef=dict( + serviceNodeName=node_name, + serviceGraphName=service_graph, + templateName=template, + schemaId=schema_id, + ), + ) + if node_data.get("uuid"): + payload.update(uuid=node_data.get("uuid")) + nodes_payload.append(payload) + else: + mso.fail_json( + "Provided service node type '{node_name}' does not exist. Existing service node types include: {node_types}".format( + node_name=node_name, node_types=", ".join(service_node_types) + ) + ) + + payload = dict( + name=service_graph, + displayName=display_name, + description=description, + nodeFilter=filter_after_first_node, + serviceGraphRef=dict( + serviceGraphName=service_graph, + templateName=template, + schemaId=schema_id, + ), + serviceNodes=nodes_payload, + ) + + mso.sanitize(payload, collate=True) + + if not mso.existing: + ops.append(dict(op="add", path=service_graphs_path, value=payload)) + else: + ops.append(dict(op="replace", path=service_graph_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py new file mode 100644 index 000000000..efafd2387 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py @@ -0,0 +1,228 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_template_vrf +short_description: Manage VRFs in schema templates +description: +- Manage VRFs in schema templates on Cisco ACI Multi-Site. +author: +- Anvitha Jain (@anvitha-jain) +- Dag Wieers (@dagwieers) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + vrf: + description: + - The name of the VRF to manage. + type: str + aliases: [ name ] + display_name: + description: + - The name as displayed on the MSO web interface. + type: str + layer3_multicast: + description: + - Whether to enable L3 multicast. + type: bool + vzany: + description: + - Whether to enable vzAny. + type: bool + ip_data_plane_learning: + description: + - Whether IP data plane learning is enabled or disabled. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ disabled, enabled ] + preferred_group: + description: + - Whether to enable preferred Endpoint Group. + - The APIC defaults to C(false) when unset during creation. + type: bool + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new VRF + cisco.mso.mso_schema_template_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF 1 + state: present + delegate_to: localhost + +- name: Remove an VRF + cisco.mso.mso_schema_template_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF1 + state: absent + delegate_to: localhost + +- name: Query a specific VRFs + cisco.mso.mso_schema_template_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all VRFs + cisco.mso.mso_schema_template_vrf: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects + display_name=dict(type="str"), + layer3_multicast=dict(type="bool"), + vzany=dict(type="bool"), + preferred_group=dict(type="bool"), + ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["vrf"]], + ["state", "present", ["vrf"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + display_name = module.params.get("display_name") + layer3_multicast = module.params.get("layer3_multicast") + vzany = module.params.get("vzany") + ip_data_plane_learning = module.params.get("ip_data_plane_learning") + preferred_group = module.params.get("preferred_group") + state = module.params.get("state") + + mso = MSOModule(module) + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get ANP + vrfs = [v.get("name") for v in schema_obj.get("templates")[template_idx]["vrfs"]] + + if vrf is not None and vrf in vrfs: + vrf_idx = vrfs.index(vrf) + mso.existing = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx] + + if state == "query": + if vrf is None: + mso.existing = schema_obj.get("templates")[template_idx]["vrfs"] + elif not mso.existing: + mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf)) + mso.exit_json() + + vrfs_path = "/templates/{0}/vrfs".format(template) + vrf_path = "/templates/{0}/vrfs/{1}".format(template, vrf) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=vrf_path)) + + elif state == "present": + if display_name is None and not mso.existing: + display_name = vrf + + payload = dict( + name=vrf, + displayName=display_name, + l3MCast=layer3_multicast, + vzAnyEnabled=vzany, + preferredGroup=preferred_group, + ipDataPlaneLearning=ip_data_plane_learning, + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + # clean contractRef to fix api issue + for contract in mso.sent.get("vzAnyConsumerContracts"): + contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef")) + for contract in mso.sent.get("vzAnyProviderContracts"): + contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef")) + ops.append(dict(op="replace", path=vrf_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=vrfs_path + "/-", value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py new file mode 100644 index 000000000..eaef8235c --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: mso_schema_template_vrf_contract +short_description: Manage vrf contracts in schema templates +description: +- Manage vrf contracts in schema templates on Cisco ACI Multi-Site. +author: +- Cindy Zhao (@cizhao) +version_added: '0.0.8' +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template to change. + type: str + required: true + vrf: + description: + - The name of the VRF. + type: str + required: true + contract: + description: + - A contract associated to this VRF. + type: dict + suboptions: + name: + description: + - The name of the Contract to associate with. + required: true + type: str + schema: + description: + - The schema that defines the referenced contract. + - If this parameter is unspecified, it defaults to the current schema. + type: str + template: + description: + - The template that defines the referenced contract. + type: str + type: + description: + - The type of contract. + type: str + required: true + choices: [ consumer, provider ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +seealso: +- module: cisco.mso.mso_schema_template_vrf +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a contract to a VRF + cisco.mso.mso_schema_template_vrf_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF 1 + contract: + name: Contract 1 + type: consumer + state: present + delegate_to: localhost + +- name: Remove a Contract + cisco.mso.mso_schema_template_vrf_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF 1 + contract: + name: Contract 1 + type: consumer + state: absent + delegate_to: localhost + +- name: Query a specific Contract + cisco.mso.mso_schema_template_vrf_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF 1 + contract: + name: Contract 1 + type: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all Contracts + cisco.mso.mso_schema_template_vrf_contract: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + vrf: VRF 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + vrf=dict(type="str", required=True), + contract=dict(type="dict", options=mso_contractref_spec()), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["contract"]], + ["state", "present", ["contract"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + vrf = module.params.get("vrf") + contract = module.params.get("contract") + if contract is not None and contract.get("template") is not None: + contract["template"] = contract.get("template").replace(" ", "") + state = module.params.get("state") + + mso = MSOModule(module) + if contract: + if contract.get("schema") is None: + contract["schema"] = schema + contract["schema_id"] = mso.lookup_schema(contract.get("schema")) + if contract.get("template") is None: + contract["template"] = template + + # Get schema + schema_id, schema_path, schema_obj = mso.query_schema(schema) + + # Get template + templates = [t.get("name") for t in schema_obj.get("templates")] + if template not in templates: + mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates))) + template_idx = templates.index(template) + + # Get VRF + vrfs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["vrfs"]] + if vrf not in vrfs: + mso.fail_json(msg="Provided vrf '{vrf}' does not exist. Existing vrfs: {vrfs}".format(vrf=vrf, vrfs=", ".join(vrfs))) + vrf_idx = vrfs.index(vrf) + vrf_obj = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx] + + if not vrf_obj.get("vzAnyEnabled"): + mso.fail_json(msg="vzAny attribute on vrf '{0}' is disabled.".format(vrf)) + + # Get Contract + if contract: + provider_contracts = [c.get("contractRef") for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyProviderContracts"]] + consumer_contracts = [c.get("contractRef") for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyConsumerContracts"]] + contract_ref = mso.contract_ref(**contract) + if contract_ref in provider_contracts and contract.get("type") == "provider": + contract_idx = provider_contracts.index(contract_ref) + contract_path = "/templates/{0}/vrfs/{1}/vzAnyProviderContracts/{2}".format(template, vrf, contract_idx) + mso.existing = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyProviderContracts"][contract_idx] + if contract_ref in consumer_contracts and contract.get("type") == "consumer": + contract_idx = consumer_contracts.index(contract_ref) + contract_path = "/templates/{0}/vrfs/{1}/vzAnyConsumerContracts/{2}".format(template, vrf, contract_idx) + mso.existing = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyConsumerContracts"][contract_idx] + if mso.existing.get("contractRef"): + mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef")) + mso.existing["relationshipType"] = contract.get("type") + + if state == "query": + if not contract: + provider_contracts = [ + dict(contractRef=mso.dict_from_ref(c.get("contractRef")), relationshipType="provider") + for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyProviderContracts"] + ] + consumer_contracts = [ + dict(contractRef=mso.dict_from_ref(c.get("contractRef")), relationshipType="consumer") + for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyConsumerContracts"] + ] + mso.existing = provider_contracts + consumer_contracts + elif not mso.existing: + mso.fail_json(msg="Contract '{0}' not found".format(contract.get("name"))) + + mso.exit_json() + + if contract.get("type") == "provider": + contracts_path = "/templates/{0}/vrfs/{1}/vzAnyProviderContracts/-".format(template, vrf) + if contract.get("type") == "consumer": + contracts_path = "/templates/{0}/vrfs/{1}/vzAnyConsumerContracts/-".format(template, vrf) + ops = [] + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=contract_path)) + + elif state == "present": + payload = dict( + contractRef=dict( + contractName=contract.get("name"), + templateName=contract.get("template"), + schemaId=contract.get("schema_id"), + ), + ) + + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=contract_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=contracts_path, value=mso.sent)) + + mso.existing = mso.proposed + mso.existing["relationshipType"] = contract.get("type") + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(schema_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py new file mode 100644 index 000000000..a4a4f6cd0 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_schema_validate +short_description: Validate the schema before deploying it to site +description: +- This module is used to verify if a schema can be deployed to site without any error. +- This module can only be used on versions of MSO that are 3.3 or greater. +- Starting with MSO 3.3, the schema modules in this collection will skip some validation checks to allow part of the schema to be updated more easily. +- This module will check those validation after all changes have been made. +author: +- Anvitha Jain (@anvitha-jain) +version_added: "1.3.0" +options: + schema: + description: + - The name of the schema. + type: str + required: true + state: + description: + - Use C(query) to validate deploying the schema. + type: str + default: query + choices: [ query ] +seealso: +- module: cisco.mso.mso_schema_template_external_epg +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + state=dict(type="str", default="query", choices=["query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + schema = module.params.get("schema") + + mso = MSOModule(module) + + mso.existing = mso.validate_schema(schema_id=mso.lookup_schema(schema)) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py b/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py new file mode 100644 index 000000000..b8319256b --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_service_node_type +short_description: Manage Service Node Types +description: +- Manage Service Node Types on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +options: + name: + description: + - The name of the node type. + type: str + display_name: + description: + - The name of the node type as displayed on the MSO web interface. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new Service Node Type + cisco.mso.mso_schema_service_node: + host: mso_host + username: admin + password: SomeSecretPassword + name: ips + display_name: ips + state: present + delegate_to: localhost + +- name: Remove a Service Node Type + cisco.mso.mso_schema_service_node: + host: mso_host + username: admin + password: SomeSecretPassword + name: ips + state: absent + delegate_to: localhost + +- name: Query a specific Service Node Type + cisco.mso.mso_schema_service_node: + host: mso_host + username: admin + password: SomeSecretPassword + name: ips + state: query + delegate_to: localhost + +- name: Query all Service Node Types + cisco.mso.mso_schema_service_node: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + name=dict(type="str"), + display_name=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name"]], + ["state", "present", ["name"]], + ], + ) + + name = module.params.get("name") + display_name = module.params.get("display_name") + state = module.params.get("state") + + mso = MSOModule(module) + + mso.existing = {} + service_node_id = None + + # Get service node id + query_node_data = mso.query_service_node_types() + service_nodes = [f.get("name") for f in query_node_data] + if name in service_nodes: + for node_data in query_node_data: + if node_data.get("name") == name: + service_node_id = node_data.get("id") + mso.existing = node_data + + if state == "query": + if name is None: + mso.existing = query_node_data + if name is not None and service_node_id is None: + mso.fail_json(msg="Service Node Type '{service_node_type}' not found".format(service_node_type=name)) + mso.exit_json() + + service_nodes_path = "schemas/service-node-types" + service_node_path = "schemas/service-node-types/{0}".format(service_node_id) + + mso.previous = mso.existing + if state == "absent": + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(service_node_path, method="DELETE") + + elif state == "present": + if display_name is None: + display_name = name + + payload = dict( + name=name, + displayName=display_name, + ) + mso.sanitize(payload, collate=True) + if not module.check_mode: + if not mso.existing: + mso.request(service_nodes_path, method="POST", data=payload) + elif mso.existing.get("displayName") != display_name: + mso.fail_json( + msg="Service Node Type '{0}' already exists with display name '{1}' which is different from provided display name '{2}'.".format( + name, mso.existing.get("displayName"), display_name + ) + ) + mso.existing = mso.proposed + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_site.py new file mode 100644 index 000000000..a3778d28a --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_site.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_site +short_description: Manage sites +description: +- Manage sites on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + apic_password: + description: + - The password for the APICs. + - The apic_password attribute is not supported when using with ND platform. + - See the ND collection for complete site management. + type: str + apic_site_id: + description: + - The site ID of the APICs. + type: str + apic_username: + description: + - The username for the APICs. + - The apic_username attribute is not supported when using with ND platform. + - See the ND collection for complete site management. + type: str + default: admin + apic_login_domain: + description: + - The AAA login domain for the username for the APICs. + - The apic_login_domain attribute is not supported when using with ND platform. + - See the ND collection for complete site management. + type: str + site: + description: + - The name of the site. + type: str + aliases: [ name ] + labels: + description: + - The labels for this site. + - Labels that do not already exist will be automatically created. + - The labels attribute is not supported when using with ND platform. + - See the ND collection for complete site management. + type: list + elements: str + location: + description: + - Location of the site. + - The location attribute is not supported when using with ND platform. + - See the ND collection for complete site management. + type: dict + suboptions: + latitude: + description: + - The latitude of the location of the site. + type: float + longitude: + description: + - The longitude of the location of the site. + type: float + urls: + description: + - A list of URLs to reference the APICs. + - The urls attribute is not supported when using with ND platform. + - See the ND collection for complete site management. + type: list + elements: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new site + cisco.mso.mso_site: + host: mso_host + username: admin + password: SomeSecretPassword + site: north_europe + description: North European Datacenter + apic_username: mso_admin + apic_password: AnotherSecretPassword + apic_site_id: 12 + urls: + - 10.2.3.4 + - 10.2.4.5 + - 10.3.5.6 + labels: + - NEDC + - Europe + - Diegem + location: + latitude: 50.887318 + longitude: 4.447084 + state: present + delegate_to: localhost + +- name: Remove a site + cisco.mso.mso_site: + host: mso_host + username: admin + password: SomeSecretPassword + site: north_europe + state: absent + delegate_to: localhost + +- name: Query a site + cisco.mso.mso_site: + host: mso_host + username: admin + password: SomeSecretPassword + site: north_europe + state: query + delegate_to: localhost + register: query_result + +- name: Query all sites + cisco.mso.mso_site: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + location_arg_spec = dict( + latitude=dict(type="float"), + longitude=dict(type="float"), + ) + + argument_spec = mso_argument_spec() + argument_spec.update( + apic_password=dict(type="str", no_log=True), + apic_site_id=dict(type="str"), + apic_username=dict(type="str", default="admin"), + apic_login_domain=dict(type="str"), + labels=dict(type="list", elements="str"), + location=dict(type="dict", options=location_arg_spec), + site=dict(type="str", aliases=["name"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + urls=dict(type="list", elements="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["site"]], + ["state", "present", ["apic_site_id", "site"]], + ], + ) + + apic_username = module.params.get("apic_username") + apic_password = module.params.get("apic_password") + apic_site_id = module.params.get("apic_site_id") + site = module.params.get("site") + location = module.params.get("location") + if location is not None: + latitude = module.params.get("location")["latitude"] + longitude = module.params.get("location")["longitude"] + state = module.params.get("state") + urls = module.params.get("urls") + apic_login_domain = module.params.get("apic_login_domain") + + mso = MSOModule(module) + + site_id = None + path = "sites" + api_version = "v1" + if mso.platform == "nd": + api_version = "v2" + + # Convert labels + labels = mso.lookup_labels(module.params.get("labels"), "site") + + # Query for mso.existing object(s) + if site: + if mso.platform == "nd": + site_info = mso.get_obj(path, api_version=api_version, common=dict(name=site)) + path = "sites/manage" + if site_info: + # If we found an existing object, continue with it + site_id = site_info.get("id") + if site_id is not None and site_id != "": + # Checking if site is managed by MSO + mso.existing = site_info + path = "sites/manage/{id}".format(id=site_id) + else: + mso.existing = mso.get_obj(path, name=site) + if mso.existing: + # If we found an existing object, continue with it + site_id = mso.existing.get("id") + path = "sites/{id}".format(id=site_id) + + else: + mso.existing = mso.query_objs(path, api_version=api_version) + + if state == "query": + pass + + elif state == "absent": + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.request(path, method="DELETE", qs=dict(force="true"), api_version=api_version) + mso.existing = {} + + elif state == "present": + mso.previous = mso.existing + + if mso.platform == "nd": + if mso.existing: + payload = mso.existing + else: + if site_info: + payload = site_info + payload["common"]["siteId"] = apic_site_id + else: + mso.fail_json(msg="Site '{0}' is not a valid Site configured at ND-level. Add Site to ND first.".format(site)) + + else: + payload = dict( + apicSiteId=apic_site_id, + id=site_id, + name=site, + urls=urls, + labels=labels, + username=apic_username, + password=apic_password, + ) + + if location is not None: + payload["location"] = dict( + lat=latitude, + long=longitude, + ) + + if apic_login_domain is not None and apic_login_domain not in ["", "local", "Local"]: + payload["username"] = "apic#{0}\\{1}".format(apic_login_domain, apic_username) + + mso.sanitize(payload, collate=True) + + if mso.existing: + if mso.check_changed(): + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent, api_version=api_version) + else: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent, api_version=api_version) + + if "password" in mso.existing: + mso.existing["password"] = "******" + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py b/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py new file mode 100644 index 000000000..17aa457e3 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_tenant +short_description: Manage tenants +description: +- Manage tenants on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ name ] + display_name: + description: + - The name of the tenant to be displayed in the web UI. + type: str + description: + description: + - The description for this tenant. + type: str + users: + description: + - A list of associated users for this tenant. + - Using this property will replace any existing associated users. + - Admin user is always added to the associated user list irrespective of this parameter being used. + type: list + elements: str + sites: + description: + - A list of associated sites for this tenant. + - Using this property will replace any existing associated sites. + type: list + elements: str + orchestrator_only: + description: + - Orchestrator Only C(no) is used to delete the tenant from the MSO and Sites/APIC. + - C(yes) is used to remove the tenant only from the MSO. + type: str + choices: [ 'yes', 'no' ] + default: 'yes' + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Add a new tenant + cisco.mso.mso_tenant: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: north_europe + display_name: North European Datacenter + description: This tenant manages the NEDC environment. + state: present + delegate_to: localhost + +- name: Remove a tenant from MSO and Site/APIC + cisco.mso.mso_tenant: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: north_europe + orchestrator_only: no + state: absent + delegate_to: localhost + +- name: Remove a tenant from MSO only + cisco.mso.mso_tenant: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: north_europe + orchestrator_only: yes + state: absent + delegate_to: localhost + +- name: Query a tenant + cisco.mso.mso_tenant: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: north_europe + state: query + delegate_to: localhost + register: query_result + +- name: Query all tenants + cisco.mso.mso_tenant: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec +from ansible_collections.cisco.mso.plugins.module_utils.constants import YES_OR_NO_TO_BOOL_STRING_MAP + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + description=dict(type="str"), + display_name=dict(type="str"), + tenant=dict(type="str", aliases=["name"]), + users=dict(type="list", elements="str"), + sites=dict(type="list", elements="str"), + orchestrator_only=dict(type="str", default="yes", choices=["yes", "no"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant"]], + ["state", "present", ["tenant"]], + ], + ) + + description = module.params.get("description") + display_name = module.params.get("display_name") + tenant = module.params.get("tenant") + orchestrator_only = module.params.get("orchestrator_only") + state = module.params.get("state") + + mso = MSOModule(module) + + # Convert sites and users + sites = mso.lookup_sites(module.params.get("sites")) + users = mso.lookup_users(module.params.get("users")) + + tenant_id = None + path = "tenants" + + # Query for existing object(s) + if tenant: + mso.existing = mso.get_obj(path, name=tenant) + if mso.existing: + tenant_id = mso.existing.get("id") + # If we found an existing object, continue with it + path = "tenants/{id}".format(id=tenant_id) + else: + mso.existing = mso.query_objs(path) + + if state == "query": + pass + + elif state == "absent": + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + path = "{0}?msc-only={1}".format(path, YES_OR_NO_TO_BOOL_STRING_MAP.get(orchestrator_only)) + mso.existing = mso.request(path, method="DELETE") + + elif state == "present": + mso.previous = mso.existing + + payload = dict( + description=description, + id=tenant_id, + name=tenant, + displayName=display_name, + siteAssociations=sites, + userAssociations=users, + ) + + mso.sanitize(payload, collate=True) + + # Ensure displayName is not undefined + if mso.sent.get("displayName") is None: + mso.sent["displayName"] = tenant + + if mso.existing: + if mso.check_changed(): + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent) + else: + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py new file mode 100644 index 000000000..735f85b13 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_tenant_site +short_description: Manage tenants with cloud sites. +description: +- Manage tenants with cloud sites on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +options: + tenant: + description: + - The name of the tenant. + type: str + required: true + site: + description: + - The name of the site. + - This can either be cloud site or non-cloud site. + type: str + aliases: [ name ] + cloud_account: + description: + - Required for cloud site. + - Account id of AWS in the form '000000000000'. + - Account id of Azure in the form 'uni/tn-(tenant_name)/act-[(subscription_id)]-azure_vendor-azure'. + - Example values inside account id of Azure '(tenant_name)=tenant_test and (subscription_id)=10'. + type: str + security_domains: + description: + - List of security domains for sites. + type: list + elements: str + default: [] + aws_account_org: + description: + - AWS account for organization. + default: false + type: bool + aws_trusted: + description: + - AWS account's access in trusted mode. Credentials are required, when set to false. + type: bool + aws_access_key: + description: + - AWS account's access key id. This is required when aws_trusted is set to false. + type: str + azure_access_type: + description: + - Managed mode for Azure. + - Unmanaged mode for Azure. + - Shared mode if the attribute is not specified. + choices: [ managed, unmanaged, shared ] + default: shared + type: str + azure_active_directory_id: + description: + - Azure account's active directory id. + - This attribute is required when azure_access_type is in unmanaged mode. + type: str + azure_active_directory_name: + description: + - Azure account's active directory name. Example being 'CiscoINSBUAd' as active directory name. + - This attribute is required when azure_access_type is in unmanaged mode. + type: str + azure_subscription_id: + description: + - Azure account's subscription id. + - This attribute is required when azure_access_type is either in managed mode or unmanaged mode. + type: str + azure_application_id: + description: + - Azure account's application id. + - This attribute is required when azure_access_type is either in managed mode or unmanaged mode. + type: str + azure_credential_name: + description: + - Azure account's credential name. + - This attribute is required when azure_access_type is in unmanaged mode. + type: str + secret_key: + description: + - secret key of AWS for untrusted account. Required when aws_trusted is set to false. + - secret key of Azure account for unmanaged identity. Required in unmanaged mode of Azure account. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Associate a non-cloud site with a tenant + cisco.mso.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: site_name + state: present + delegate_to: localhost + +- name: Associate AWS site with a tenant, with aws_trusted set to true + cisco.mso.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: site_name + cloud_account: '000000000000' + aws_trusted: true + state: present + delegate_to: localhost + +- name: Associate AWS site with a tenant, with aws_trusted set to false + cisco.mso.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: AWS + cloud_account: '000000000000' + aws_trusted: false + aws_access_key: '1' + secret_key: '0' + aws_account_org: false + state: present + delegate_to: localhost + +- name: Associate Azure site in managed mode + mso.cisco.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: site_name + cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure + azure_access_type: managed + azure_subscription_id: '9' + azure_application_id: '100' + state: present + delegate_to: localhost + +- name: Associate Azure site in unmanaged mode + mso.cisco.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: site_name + cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure + azure_access_type: unmanaged + azure_subscription_id: '9' + azure_application_id: '100' + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: '32' + azure_active_directory_name: CiscoINSBUAd + state: present + delegate_to: localhost + +- name: Dissociate a site + cisco.mso.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: site_name + state: absent + delegate_to: localhost + +- name: Query a site + cisco.mso.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + site: site_name + state: query + delegate_to: localhost + +- name: Query all sites of a tenant + cisco.mso.mso_tenant_site: + host: mso_host + username: admin + password: SomeSecretPassword + tenant: tenant_name + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + tenant=dict(type="str", aliases=["name"], required=True), + site=dict(type="str", aliases=["name"]), + cloud_account=dict(type="str"), + security_domains=dict(type="list", elements="str", default=[]), + aws_trusted=dict(type="bool"), + azure_access_type=dict(type="str", default="shared", choices=["managed", "unmanaged", "shared"]), + azure_active_directory_id=dict(type="str"), + aws_access_key=dict(type="str", no_log=True), + aws_account_org=dict(type="bool", default="false"), + azure_active_directory_name=dict(type="str"), + azure_subscription_id=dict(type="str"), + azure_application_id=dict(type="str"), + azure_credential_name=dict(type="str"), + secret_key=dict(type="str", no_log=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "site"]], + ["state", "present", ["tenant", "site"]], + ], + ) + + state = module.params.get("state") + security_domains = module.params.get("security_domains") + cloud_account = module.params.get("cloud_account") + azure_access_type = module.params.get("azure_access_type") + azure_credential_name = module.params.get("azure_credential_name") + azure_application_id = module.params.get("azure_application_id") + azure_active_directory_id = module.params.get("azure_active_directory_id") + azure_active_directory_name = module.params.get("azure_active_directory_name") + azure_subscription_id = module.params.get("azure_subscription_id") + secret_key = module.params.get("secret_key") + aws_account_org = module.params.get("aws_account_org") + aws_access_key = module.params.get("aws_access_key") + aws_trusted = module.params.get("aws_trusted") + + mso = MSOModule(module) + + # Get tenant_id and site_id + tenant_id = mso.lookup_tenant(module.params.get("tenant")) + site_id = mso.lookup_site(module.params.get("site")) + tenants = [(t.get("id")) for t in mso.query_objs("tenants")] + tenant_idx = tenants.index((tenant_id)) + + # set tenent and port paths + tenant_path = "tenants/{0}".format(tenant_id) + ops = [] + ports_path = "/siteAssociations/-" + port_path = "/siteAssociations/{0}".format(site_id) + + payload = dict( + siteId=site_id, + securityDomains=security_domains, + cloudAccount=cloud_account, + ) + + if cloud_account: + if "azure" in cloud_account: + azure_account = dict( + accessType=azure_access_type, + securityDomains=security_domains, + vendor="azure", + ) + + payload["azureAccount"] = [azure_account] + + cloudSubscription = dict( + cloudSubscriptionId=azure_subscription_id, + cloudApplicationId=azure_application_id, + ) + + payload["azureAccount"][0]["cloudSubscription"] = cloudSubscription + + if azure_access_type == "shared": + payload["azureAccount"] = [] + + if azure_access_type == "managed": + if not azure_subscription_id: + mso.fail_json(msg="azure_susbscription_id is required when in managed mode.") + if not azure_application_id: + mso.fail_json(msg="azure_application_id is required when in managed mode.") + payload["azureAccount"][0]["cloudApplication"] = [] + payload["azureAccount"][0]["cloudActiveDirectory"] = [] + + if azure_access_type == "unmanaged": + if not azure_subscription_id: + mso.fail_json(msg="azure_subscription_id is required when in unmanaged mode.") + if not azure_application_id: + mso.fail_json(msg="azure_application_id is required when in unmanaged mode.") + if not secret_key: + mso.fail_json(msg="secret_key is required when in unmanaged mode.") + if not azure_active_directory_id: + mso.fail_json(msg="azure_active_directory_id is required when in unmanaged mode.") + if not azure_active_directory_name: + mso.fail_json(msg="azure_active_directory_name is required when in unmanaged mode.") + if not azure_credential_name: + mso.fail_json(msg="azure_credential_name is required when in unmanaged mode.") + azure_account.update( + accessType="credentials", + ) + cloudApplication = dict( + cloudApplicationId=azure_application_id, + cloudCredentialName=azure_credential_name, + secretKey=secret_key, + cloudActiveDirectoryId=azure_active_directory_id, + ) + cloudActiveDirectory = dict(cloudActiveDirectoryId=azure_active_directory_id, cloudActiveDirectoryName=azure_active_directory_name) + payload["azureAccount"][0]["cloudApplication"] = [cloudApplication] + payload["azureAccount"][0]["cloudActiveDirectory"] = [cloudActiveDirectory] + + else: + aws_account = dict( + accountId=cloud_account, + isTrusted=aws_trusted, + accessKeyId=aws_access_key, + secretKey=secret_key, + isAccountInOrg=aws_account_org, + ) + + if not aws_trusted: + if not aws_access_key: + mso.fail_json(msg="aws_access_key is a required field in untrusted mode.") + if not secret_key: + mso.fail_json(msg="secret_key is a required field in untrusted mode.") + payload["awsAccount"] = [aws_account] + + sites = [(s.get("siteId")) for s in mso.query_objs("tenants")[tenant_idx]["siteAssociations"]] + + if site_id in sites: + site_idx = sites.index((site_id)) + mso.existing = mso.query_objs("tenants")[tenant_idx]["siteAssociations"][site_idx] + + if state == "query": + if len(sites) == 0: + mso.fail_json(msg="No site associated with tenant Id {0}".format(tenant_id)) + elif site_id not in sites and site_id is not None: + mso.fail_json(msg="Site Id {0} not associated with tenant Id {1}".format(site_id, tenant_id)) + elif site_id is None: + mso.existing = mso.query_objs("tenants")[tenant_idx]["siteAssociations"] + mso.exit_json() + + mso.previous = mso.existing + + if state == "absent": + if mso.existing: + mso.sent = mso.existing = {} + ops.append(dict(op="remove", path=port_path)) + if state == "present": + mso.sanitize(payload, collate=True) + + if mso.existing: + ops.append(dict(op="replace", path=port_path, value=mso.sent)) + else: + ops.append(dict(op="add", path=ports_path, value=mso.sent)) + + mso.existing = mso.proposed + + if not module.check_mode and mso.proposed != mso.previous: + mso.request(tenant_path, method="PATCH", data=ops) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_user.py b/ansible_collections/cisco/mso/plugins/modules/mso_user.py new file mode 100644 index 000000000..37127a7f1 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_user.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_user +short_description: Manage users +description: +- Manage users on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +options: + user: + description: + - The name of the user. + type: str + aliases: [ name ] + user_password: + description: + - The password of the user. + type: str + first_name: + description: + - The first name of the user. + - This parameter is required when creating new users. + type: str + last_name: + description: + - The last name of the user. + - This parameter is required when creating new users. + type: str + email: + description: + - The email address of the user. + - This parameter is required when creating new users. + type: str + phone: + description: + - The phone number of the user. + - This parameter is required when creating new users. + type: str + account_status: + description: + - The status of the user account. + type: str + choices: [ active, inactive ] + domain: + description: + - The domain this user belongs to. + - When creating new users, this defaults to C(Local). + type: str + roles: + description: + - The roles for this user and their access types (read or write). + - Access type defaults to C(write). + type: list + elements: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +notes: +- A default installation of ACI Multi-Site ships with admin password 'we1come!' which requires a password change on first login. + See the examples of how to change the 'admin' password using Ansible. +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Update initial admin password + cisco.mso.mso_user: + host: mso_host + username: admin + password: initialPassword + validate_certs: false + user: admin + user_password: newPassword + state: present + delegate_to: localhost + +- name: Add a new user + cisco.mso.mso_user: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + user: dag + user_password: userPassword + first_name: Dag + last_name: Wieers + email: dag@wieers.com + phone: +32 478 436 299 + roles: + - name: siteManager + access_type: write + - name: schemaManager + access_type: read + state: present + delegate_to: localhost + +- name: Add a new user + cisco.mso.mso_user: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + user: dag + first_name: Dag + last_name: Wieers + email: dag@wieers.com + phone: +32 478 436 299 + roles: + - powerUser + delegate_to: localhost + +- name: Remove a user + cisco.mso.mso_user: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + user: dag + state: absent + delegate_to: localhost + +- name: Query a user + cisco.mso.mso_user: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + user: dag + state: query + delegate_to: localhost + register: query_result + +- name: Query all users + cisco.mso.mso_user: + host: mso_host + username: admin + password: SomeSecretPassword + validate_certs: false + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" # """ + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, issubset + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + user=dict(type="str", aliases=["name"]), + user_password=dict(type="str", no_log=True), + first_name=dict(type="str"), + last_name=dict(type="str"), + email=dict(type="str"), + phone=dict(type="str"), + # TODO: What possible options do we have ? + account_status=dict(type="str", choices=["active", "inactive"]), + domain=dict(type="str"), + roles=dict(type="list", elements="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["user"]], + ["state", "present", ["user"]], + ], + ) + + user_name = module.params.get("user") + user_password = module.params.get("user_password") + first_name = module.params.get("first_name") + last_name = module.params.get("last_name") + email = module.params.get("email") + phone = module.params.get("phone") + account_status = module.params.get("account_status") + state = module.params.get("state") + + mso = MSOModule(module) + + roles = mso.lookup_roles(module.params.get("roles")) + domain = mso.lookup_domain(module.params.get("domain")) + + user_id = None + path = "users" + + # Query for existing object(s) + if user_name: + if mso.module._socket_path and mso.connection.get_platform() == "cisco.nd": + mso.existing = mso.get_obj(path, loginID=user_name, api_version="v2") + if mso.existing: + mso.existing["id"] = mso.existing.get("userID") + mso.existing["username"] = mso.existing.get("loginID") + else: + mso.existing = mso.get_obj(path, username=user_name) + if mso.existing: + user_id = mso.existing.get("id") + # If we found an existing object, continue with it + path = "users/{id}".format(id=user_id) + else: + mso.existing = mso.query_objs(path) + + if state == "query": + pass + + elif state == "absent": + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method="DELETE") + + elif state == "present": + mso.previous = mso.existing + + payload = dict( + id=user_id, + username=user_name, + firstName=first_name, + lastName=last_name, + emailAddress=email, + phoneNumber=phone, + accountStatus=account_status, + domainId=domain, + roles=roles, + # active=True, + # remote=True, + ) + + if user_password is not None: + payload.update(password=user_password) + + mso.sanitize(payload, collate=True) + + if mso.sent.get("accountStatus") is None: + mso.sent["accountStatus"] = "active" + + if mso.existing: + if not issubset(mso.sent, mso.existing): + # NOTE: Since MSO always returns '******' as password, we need to assume a change + if "password" in mso.proposed: + mso.module.warn("A password change is assumed, as the MSO REST API does not return passwords we do not know.") + mso.result["changed"] = True + + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="PUT", data=mso.sent) + + else: + if user_password is None: + mso.fail_json("The user {0} does not exist. The 'user_password' attribute is required to create a new user.".format(user_name)) + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method="POST", data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_version.py b/ansible_collections/cisco/mso/plugins/modules/mso_version.py new file mode 100644 index 000000000..19668afb4 --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/mso_version.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: mso_version +short_description: Get version of MSO +description: +- Retrieve the code version of Cisco Multi-Site Orchestrator. +author: +- Lionel Hercot (@lhercot) +options: + state: + description: + - Use C(query) for retrieving the version object. + type: str + choices: [ query ] + default: query +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Get MSO version + cisco.mso.mso_version: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update(state=dict(type="str", default="query", choices=["query"])) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + mso = MSOModule(module) + + path = "platform/version" + + # Query for mso.existing object + mso.existing = mso.query_obj(path) + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py b/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py new file mode 100644 index 000000000..b8bdb63ae --- /dev/null +++ b/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE 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 = r""" +--- +module: ndo_schema_template_deploy +short_description: Deploy schema templates to sites for NDO v3.7 and higher +description: +- Deploy schema templates to sites. +- Prior to deploy or redeploy a schema validation is executed. +- When schema validation fails, M(cisco.mso.ndo_schema_template_deploy) fails and deploy or redeploy will not be executed. +- Only supports NDO v3.7 and higher +author: +- Akini Ross (@akinross) +options: + schema: + description: + - The name of the schema. + type: str + required: true + template: + description: + - The name of the template. + type: str + required: true + sites: + description: + - The name of the site(s). + type: list + elements: str + state: + description: + - Use C(deploy) to deploy schema template. + - Use C(redeploy) to redeploy schema template. + - Use C(undeploy) to undeploy schema template from a site. + - Use C(query) to get deployment status. + type: str + choices: [ deploy, redeploy, undeploy, query ] + default: deploy +seealso: +- module: cisco.mso.mso_schema_site +- module: cisco.mso.mso_schema_template +extends_documentation_fragment: cisco.mso.modules +""" + +EXAMPLES = r""" +- name: Deploy a schema template + cisco.mso.ndo_schema_template_deploy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: deploy + delegate_to: localhost + +- name: Redeploy a schema template + cisco.mso.ndo_schema_template_deploy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: redeploy + delegate_to: localhost + +- name: Undeploy a schema template + cisco.mso.ndo_schema_template_deploy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + sites: [ Site1, Site2 ] + state: undeploy + delegate_to: localhost + +- name: Query a schema template deploy status + cisco.mso.ndo_schema_template_deploy: + host: mso_host + username: admin + password: SomeSecretPassword + schema: Schema 1 + template: Template 1 + state: query + delegate_to: localhost +""" + +RETURN = r""" +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + schema=dict(type="str", required=True), + template=dict(type="str", required=True), + sites=dict(type="list", elements="str"), + state=dict(type="str", default="deploy", choices=["deploy", "redeploy", "undeploy", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "undeploy", ["sites"]], + ], + ) + + schema = module.params.get("schema") + template = module.params.get("template").replace(" ", "") + sites = module.params.get("sites") + state = module.params.get("state") + + mso = MSOModule(module) + schema_id = mso.lookup_schema(schema) + + if state == "query": + path = "status/schema/{0}/template/{1}".format(schema_id, template) + method = "GET" + payload = None + else: + path = "task" + method = "POST" + payload = dict(schemaId=schema_id, templateName=template) + if state == "deploy": + mso.validate_schema(schema_id) + payload.update(isRedeploy=False) + elif state == "redeploy": + mso.validate_schema(schema_id) + payload.update(isRedeploy=True) + elif state == "undeploy": + payload.update(undeploy=[site.get("siteId") for site in mso.lookup_sites(sites)]) + + if not module.check_mode: + mso.existing = mso.request(path, method=method, data=payload) + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/mso/requirements.txt b/ansible_collections/cisco/mso/requirements.txt new file mode 100644 index 000000000..976bdfe94 --- /dev/null +++ b/ansible_collections/cisco/mso/requirements.txt @@ -0,0 +1 @@ +requests_toolbelt diff --git a/ansible_collections/cisco/mso/tests/.DS_Store b/ansible_collections/cisco/mso/tests/.DS_Store Binary files differnew file mode 100644 index 000000000..8249f4cab --- /dev/null +++ b/ansible_collections/cisco/mso/tests/.DS_Store diff --git a/ansible_collections/cisco/mso/tests/.gitignore b/ansible_collections/cisco/mso/tests/.gitignore new file mode 100644 index 000000000..ea1472ec1 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/ansible_collections/cisco/mso/tests/integration/inventory.networking b/ansible_collections/cisco/mso/tests/integration/inventory.networking new file mode 100644 index 000000000..e39d51abb --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/inventory.networking @@ -0,0 +1,36 @@ +[mso] +lh-dmz1-pod1-mso-v224 ansible_host=173.36.219.29 ansible_connection=local mso_hostname=173.36.219.29 +lh-dmz1-pod1-mso-v311 ansible_host=173.36.219.11 ansible_connection=local mso_hostname=173.36.219.11 + +[mso:children] +ndo + +[ndo] +lh-dmz1-pod1-ndo-v371 ansible_host=173.36.219.30 ansible_connection=ansible.netcommon.httpapi ansible_network_os=cisco.nd.nd mso_hostname=173.36.219.30 +lh-dmz1-pod1-ndo-v411 ansible_host=173.36.219.32 ansible_connection=ansible.netcommon.httpapi ansible_network_os=cisco.nd.nd mso_hostname=173.36.219.32 + +[all:vars] +mso_username=ansible_github_ci +mso_password="sJ94G92#8dq2hx*K4qh" +ansible_user=ansible_github_ci +ansible_ssh_pass="sJ94G92#8dq2hx*K4qh" +ansible_network_os=cisco.mso.mso +ansible_httpapi_validate_certs=False +ansible_httpapi_use_ssl=True +apic_hostname=173.36.219.190 +apic_username=ansible_github_ci +apic_password="sJ94G92#8dq2hx*K4qh" +apic_site_id=101 +aws_apic_hostname=52.52.20.121 +aws_apic_username=ansible_github_ci +aws_apic_password="sJ94G92#8dq2hx*K4qh" +aws_site_id=20 +azure_apic_hostname=20.245.236.136 +azure_apic_username=ansible_github_ci +azure_apic_password="sJ94G92#8dq2hx*K4qh" +azure_site_id=10 +ansible_python_interpreter=/usr/bin/python3.9 +mso_remote_location=173.36.219.190 +mso_radius_server=173.36.219.128 +ansible_command_timeout=300 +ansible_connect_timeoout=300
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt b/ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt new file mode 100644 index 000000000..1ecd96b24 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt @@ -0,0 +1 @@ +requests-toolbelt
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/target-prefixes.network b/ansible_collections/cisco/mso/tests/integration/target-prefixes.network new file mode 100644 index 000000000..74f40ea75 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/target-prefixes.network @@ -0,0 +1,2 @@ +mso +ndo
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml new file mode 100644 index 000000000..9fe4d1a9a --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml @@ -0,0 +1,546 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# Copyright: (c) 2023, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Clear tenant + mso_tenant: &tenant1_absent + <<: *mso_info + tenant: Tenant1 + display_name: Test_Tenant + state: absent + +- name: Ensure remote location is absent + cisco.mso.mso_remote_location: &remote_location_absent + <<: *mso_info + remote_location: ansible_test + remote_protocol: scp + remote_host: '{{ mso_remote_location }}' + remote_path: '{{ mso_remote_location_path | default("/tmp") }}' + authentication_type: password + remote_username: '{{ mso_remote_location_user | default(mso_username) }}' + remote_password: '{{ mso_remote_location_password | default(mso_password) }}' + state: absent + ignore_errors: true + +- name: Ensure remote location is present + cisco.mso.mso_remote_location: + <<: *remote_location_absent + state: present + +- name: Query all backups + mso_backup: &query_all_backups + <<: *mso_info + state: query + register: query_ansibleBackup_for_delete + +- name: Remove all backups + mso_backup: + <<: *mso_info + backup_id: '{{ item.id }}' + state: absent + loop: '{{ query_ansibleBackup_for_delete.current | list | sort(attribute="name", reverse=True) }}' + +# Create local backups - Only for version < 3.2 +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + - name: Create local ansibleBackup1 in check mode + mso_backup: + <<: *mso_info + backup: ansibleBackup1 + description: via Ansible + location_type: local + state: present + register: cm_add_ansibleBackup1 + check_mode: true + + - name: Verify local cm_add_ansibleBackup1 + assert: + that: + - cm_add_ansibleBackup1 is changed + + - name: Create local ansibleBackup1 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup1 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup1 + + - name: Query ansibleBackup1 to check if it exists + mso_backup: + <<: *mso_info + backup: ansibleBackup1 + state: query + register: check_ansibleBackup1 + until: + - check_ansibleBackup1.current is defined + - check_ansibleBackup1.current[0] is defined + - check_ansibleBackup1.current[0].status.statusType == "success" + retries: 50 + delay: 10 + + - name: Verify nm_add_ansibleBackup1 + assert: + that: + - nm_add_ansibleBackup1 is changed + - check_ansibleBackup1.current[0].backupEntry.metadata.name is match("ansibleBackup1_[0-9a-zA-Z]*") + + - name: Create local ansibleBackup3 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup3 + + - name: Query ansibleBackup3 to check if it exists + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: query + register: query_ansibleBackup3 + until: + - query_ansibleBackup3.current is defined + - query_ansibleBackup3.current[0] is defined + - query_ansibleBackup3.current[0].status.statusType == "success" + - query_ansibleBackup3.current[0].backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*") + retries: 50 + delay: 10 + + - name: Assertions check for create local ansibleBackup3 in normal mode + assert: + that: + - query_ansibleBackup3 is not changed + - query_ansibleBackup3.current.0.status.statusType == "success" + - query_ansibleBackup3.current.0.backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*") + - query_ansibleBackup3.current.0.location.locationType == "local" + + # Creating duplicate backups with the name of ansibleBackup3 - to validate "Multiple backups with same name found" error message + - name: Create local ansibleBackup3 in normal mode again + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup3_again + + - name: Query duplicate ansibleBackup3 to check if it exists + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: query + register: query_ansibleBackup3_again + until: + - query_ansibleBackup3_again.current is defined + - query_ansibleBackup3_again.current[1] is defined + - query_ansibleBackup3_again.current[1].status.statusType == "success" + - query_ansibleBackup3_again.current[1].backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*") + retries: 50 + delay: 10 + + - name: Assertions check for create local ansibleBackup3 in normal mode again + assert: + that: + - query_ansibleBackup3_again is not changed + - query_ansibleBackup3_again.current.1.status.statusType == "success" + - query_ansibleBackup3_again.current.1.backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*") + - query_ansibleBackup3_again.current.1.location.locationType == "local" + +# Move Local backup to Remote - Only for version < 3.2 +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + - name: Move ansibleBackup1 from local to remote location in check mode + mso_backup: &move_ab1_cm + <<: *mso_info + remote_location: ansible_test + remote_path: "" + backup: ansibleBackup1 + description: Local to Remote via Ansible + state: move + check_mode: true + register: move_ab1_cm + + - name: Move ansibleBackup1 from local to remote location in normal mode + mso_backup: + <<: *move_ab1_cm + register: move_ab1_nm + + - name: Move a non existent backup from local location to remote + mso_backup: + <<: *mso_info + backup: non_existent_backup + remote_location: ansible_test + remote_path: "" + state: move + register: move_non_existent_backup + ignore_errors: true + + - name: Move a ansibleBackup3 from local location to remote - check mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + remote_location: ansible_test + remote_path: "" + state: move + register: move_backup_cm_ab3 + ignore_errors: true + + - name: Move a ansibleBackup3 from local location to remote - normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + remote_location: ansible_test + remote_path: "" + state: move + register: move_backup_nm_ab3 + ignore_errors: true + + - name: Assertions check for move a backup from local location to remote location + assert: + that: + - move_ab1_cm is changed + - move_ab1_nm is changed + - move_non_existent_backup.msg is match ("Backup 'non_existent_backup' does not exist") + - move_backup_cm_ab3.msg is match ("Multiple backups with same name found. Existing backups with similar names{{':'}} ansibleBackup3_[0-9]*, ansibleBackup3_[0-9]*") + - move_backup_nm_ab3.msg is match ("Multiple backups with same name found. Existing backups with similar names{{':'}} ansibleBackup3_[0-9]*, ansibleBackup3_[0-9]*") + +# Create Remote backup - For all version +- name: Create ansibleBackupRemote1 in check mode + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + description: Remote via Ansible + location_type: remote + remote_location: ansible_test + remote_path: "tmp" + state: present + check_mode: true + register: cm_add_ansibleBackupRemote1 + +- name: Create ansibleBackupRemote1 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + description: Remote via Ansible + location_type: remote + remote_location: ansible_test + remote_path: "tmp" + state: present + register: nm_add_ansibleBackupRemote1 + +- name: Query ansibleBackupRemote1 to check if it exists + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + state: query + register: query_ansibleBackupRemote1 + until: + - query_ansibleBackupRemote1.current is defined + - query_ansibleBackupRemote1.current[0] is defined + - query_ansibleBackupRemote1.current[0].status.statusType == "success" + retries: 50 + delay: 10 + +- name: Assertions check for the backups present on the remote location + assert: + that: + - cm_add_ansibleBackupRemote1 is changed + - nm_add_ansibleBackupRemote1 is changed + - query_ansibleBackupRemote1.current.0.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") + +# Remove additional backups - to handle "Multiple backups with same name" during the download +- name: Query all backups to handle "Multiple backups with same name" during the download + mso_backup: + <<: *mso_info + state: query + register: query_ansibleBackup_for_delete + +- name: Delete all backups except for one to handle "Multiple backups with same name" during the download + mso_backup: + <<: *mso_info + backup_id: '{{ item.id }}' + state: absent + loop: '{{ query_ansibleBackup_for_delete.current[0:-1] | list | sort(attribute="name", reverse=True) }}' + when: query_ansibleBackup_for_delete.current | length >= 2 + +# Download Backup +- name: Create a directory if it does not exist + ansible.builtin.file: + path: './{{mso_hostname}}' + state: directory + mode: '0755' + +- name: Download non existent backup + mso_backup: + <<: *mso_info + backup: non_existent_backup + destination: './{{mso_hostname}}' + state: download + ignore_errors: true + register: download_non_existent_backup + +- name: Download ansibleBackupRemote1 in check mode + mso_backup: &download_ab1_cm + <<: *mso_info + backup: ansibleBackupRemote1 + destination: './{{mso_hostname}}' + state: download + check_mode: true + register: download_ab1_cm + +- name: Download ansibleBackupRemote1 in normal mode + mso_backup: + <<: *download_ab1_cm + register: download_ab1_nm + +- name: Assertions check for download a backup form MSO/NDO + assert: + that: + - download_ab1_cm is changed + - download_ab1_nm is changed + - download_non_existent_backup.msg is match ("Backup 'non_existent_backup' does not exist") + +# Find Backup +- name: Find backup + find: + paths: './{{mso_hostname}}' + patterns: "*.tar.gz" + register: backup_match + +# Upload a backup from local machine to local location only for MSO - version < 3.2 +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + - name: Upload a backup from local machine to local location in check mode + mso_backup: &upload_backup_to_local_cm + <<: *mso_info + backup: "{{ backup_match.files[0].path }}" + state: upload + register: upload_backup_to_local_cm + check_mode: true + + - name: Upload a backup from local machine to local location in normal mode + mso_backup: + <<: *upload_backup_to_local_cm + register: upload_backup_to_local_nm + + - name: Upload a non existent backup from local machine to local location + mso_backup: + <<: *mso_info + backup: non_existent_backup + state: upload + register: upload_non_existent_backup + ignore_errors: true + + - name: Assertions check for the upload a backup form local machine to local location + assert: + that: + - upload_backup_to_local_cm is changed + - upload_backup_to_local_nm is changed + - "{{ 'Upload failed due to' in upload_non_existent_backup.msg }}" + +# Upload backup to remote location for all platforms +# To Support NDO Backup upload +- name: Check present working directory with Shell command + shell: + "pwd" + register: present_working_directory + +- name: Upload an existing backup from local machine to remote location - check mode + mso_backup: &upload_backup_cm + <<: *mso_info + backup: "{{present_working_directory.stdout}}/{{ backup_match.files[0].path }}" + location_type: remote + remote_location: ansible_test + remote_path: "tmp" + state: upload + check_mode: true + register: upload_backup_cm + +- name: Upload an existing backup from local machine to remote location - normal mode + mso_backup: + <<: *upload_backup_cm + register: upload_backup_nm + ignore_errors: true + +# Block to handle "Backup already existing issue" +- name: Upload backup from local machine to remote again + when: + - upload_backup_nm is not changed + - upload_backup_nm.current == [] + block: + - name: Query all backups - clear existing backups before uploading - to handle "Backup already existing issue" + mso_backup: + <<: *mso_info + state: query + register: query_ansibleBackup_for_delete + + - name: Delete all backups from MSO/NDO before uploading the backup form local machine + mso_backup: + <<: *mso_info + backup_id: '{{ item.id }}' + state: absent + loop: '{{ query_ansibleBackup_for_delete.current | list | sort(attribute="name", reverse=True) }}' + + - name: Upload an existing backup from local machine to remote location - normal mode - after removing existing backups + mso_backup: + <<: *upload_backup_cm + register: upload_backup_nm + +- name: Upload a non existent backup from local location + mso_backup: + <<: *mso_info + backup: non_existent_backup + location_type: remote + remote_location: ansible_test + remote_path: "tmp" + state: upload + register: upload_non_existent_backup + ignore_errors: true + +- name: Assertions check for the upload a backup form local machine to remote location + assert: + that: + - upload_non_existent_backup is not changed + - "{{ 'Upload failed due to' in upload_non_existent_backup.msg }}" + - upload_backup_cm is changed + - upload_backup_cm.current == {} + - upload_backup_nm is changed + - upload_backup_nm.current != {} + +- name: Set Uploaded Backup Name for MSO + when: + - version.current.version is version('2.2.4e', '!=') + - version.current.version is version('3.2', '<') + set_fact: + uploaded_backup_name: '{{ upload_backup_nm.current.backupEntry.metadata.name.split("_") | first }}' + +- name: Set Uploaded Backup Name for NDO + when: + - version.current.version is version('2.2.4e', '!=') + - version.current.version is version('3.2', '>=') + set_fact: + uploaded_backup_name: '{{ upload_backup_nm.current.body.backupEntry.metadata.name.split("_") | first }}' + +- name: Restore backup check + when: version.current.version is version('2.2.4e', '!=') + block: + - name: Restore non existent backup + mso_backup: + <<: *mso_info + backup: non_existent_backup + state: restore + timeout: 1000 + ignore_errors: true + register: restore_non_existent_backup + + - name: Add a new tenant + mso_tenant: + <<: *tenant1_absent + state: present + register: tenant1_present + + - name: Restore {{ uploaded_backup_name }} in check mode + mso_backup: &restore_backup_cm + <<: *mso_info + backup: '{{ uploaded_backup_name }}' + state: restore + timeout: 1000 + check_mode: true + register: restore_backup_cm + + - name: Restore {{ uploaded_backup_name }} in normal mode + mso_backup: + <<: *restore_backup_cm + register: restore_backup_nm + + - name: Query Tenant1 after restoring the backup + mso_tenant: + <<: *tenant1_absent + state: query + register: query_tenant1 + + - name: Restore {{ uploaded_backup_name }} in normal mode - idempotency check + mso_backup: + <<: *restore_backup_cm + register: idm_restore_backup_nm + + - name: Assertions check for the backup restore + assert: + that: + - restore_non_existent_backup.msg is match ("Backup 'non_existent_backup' does not exist") + - restore_backup_cm is changed + - restore_backup_nm is changed + - idm_restore_backup_nm is changed + - tenant1_present.current != {} + - query_tenant1 is not changed + - query_tenant1.current == {} + +# Cleaning part +# Remove all other backups +- name: Query all backups before deleting + mso_backup: + <<: *query_all_backups + state: query + register: query_all_backups_bf_delete + +- name: Ensure backups does not exists + mso_backup: + <<: *mso_info + backup_id: '{{ item.id }}' + state: absent + loop: '{{ query_all_backups_bf_delete.current | list | sort(attribute="name", reverse=True) }}' + +- name: Query all backups after deleting - to ensure all are absent + mso_backup: + <<: *query_all_backups + register: query_all_backups_af_delete + +- name: Assertions check for the query and remove all backups + assert: + that: + - query_all_backups_bf_delete is not changed + - query_all_backups_af_delete is not changed + - query_all_backups_bf_delete.current | selectattr("name", "match", "^ansibleBackup.*") | list | length != 0 + - query_all_backups_af_delete.current | selectattr("name", "match", "^ansibleBackup.*") | list | length == 0 + +# Clear Remote Location +- name: Ensure remote location is absent + cisco.mso.mso_remote_location: + <<: *remote_location_absent + +# Remove Tenant1 +- name: Ensure Tenant1 is absent + mso_tenant: + <<: *tenant1_absent + state: absent diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml new file mode 100644 index 000000000..1a50284f8 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml @@ -0,0 +1,266 @@ +# Test code for the MSO modules +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT + +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Delete existing backup schedule + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: absent + delegate_to: localhost + +- name: Ensure remote location is present + cisco.mso.mso_remote_location: + <<: *mso_info + remote_location: ansible_test + remote_protocol: scp + remote_host: '{{ mso_remote_location }}' + remote_path: '{{ mso_remote_location_path | default("/tmp") }}' + authentication_type: password + remote_username: '{{ mso_remote_location_user | default(mso_username) }}' + remote_password: '{{ mso_remote_location_password | default(mso_password) }}' + state: present + +# TESTS + +- name: Get empty backup schedule + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: query + delegate_to: localhost + register: no_schedule + +- name: Verify empty backup schedule + assert: + that: + - no_schedule is not changed + - no_schedule.current == {} + +- name: Set backup schedule (check mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + check_mode: true + delegate_to: localhost + register: cm_schedule_create + +- name: Set backup schedule (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + delegate_to: localhost + register: nm_schedule_create + +# add date tests +- name: Verify success set backup schedule + assert: + that: + - cm_schedule_create is changed + - cm_schedule_create.current.intervalLength == 7 + - cm_schedule_create.current.intervalTimeUnit == "HOURS" + - cm_schedule_create.current.locationType == "remote" + - nm_schedule_create is changed + - nm_schedule_create.current.timeInterval.length == 7 + - nm_schedule_create.current.timeInterval.unit == "HOURS" + - nm_schedule_create.current.callbackWSRequest.methodBody.locationType == "remote" + - "'00:00:00' in nm_schedule_create.current.firstScheduledAt" + +- name: Adjust backup schedule (check mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + frequency_unit: days + frequency_length: 1 + remote_location: ansible_test + state: present + check_mode: true + delegate_to: localhost + register: cm_schedule_adjust + +- name: Adjust backup schedule (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + frequency_unit: days + frequency_length: 1 + remote_location: ansible_test + state: present + delegate_to: localhost + register: nm_schedule_adjust + +- name: Verify success set backup schedule + assert: + that: + - cm_schedule_adjust is changed + - cm_schedule_adjust.current.intervalLength == 1 + - cm_schedule_adjust.current.intervalTimeUnit == "DAYS" + - cm_schedule_adjust.current.locationType == "remote" + - nm_schedule_adjust is changed + - nm_schedule_adjust.current.timeInterval.length == 1 + - nm_schedule_adjust.current.timeInterval.unit == "DAYS" + - nm_schedule_adjust.current.callbackWSRequest.methodBody.locationType == "remote" + +- name: Get backup schedule + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: query + delegate_to: localhost + register: schedule + +- name: Verify success get backup schedule + assert: + that: + - schedule is not changed + - nm_schedule_adjust.current.timeInterval.length == 1 + - nm_schedule_adjust.current.timeInterval.unit == "DAYS" + - nm_schedule_adjust.current.callbackWSRequest.methodBody.locationType == "remote" + +- name: Delete backup schedule (check mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: absent + check_mode: true + delegate_to: localhost + +- name: Delete backup schedule (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: absent + delegate_to: localhost + +- name: Get empty backup schedule + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: query + delegate_to: localhost + register: no_schedule + +- name: Verify empty backup schedule + assert: + that: + - no_schedule is not changed + - no_schedule.current == {} + +- name: Set backup schedule incorrect time to parse (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + start_time: no_time + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + delegate_to: localhost + register: nm_schedule_incorrect_time + ignore_errors: true + +- name: Set backup schedule incorrect date to parse (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + start_date: no_date + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + delegate_to: localhost + register: nm_schedule_incorrect_date + ignore_errors: true + +- name: Set backup schedule incorrect date object create from start_date (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + start_date: "2030-15-45" + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + delegate_to: localhost + register: nm_schedule_incorrect_date_from_start_date + ignore_errors: true + +- name: Set backup schedule incorrect date object create from start_time (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + start_time: "2030:15:45" + frequency_unit: hours + frequency_length: 7 + remote_location: ansible_test + state: present + delegate_to: localhost + register: nm_schedule_incorrect_time_from_start_time + ignore_errors: true + +- name: Verify error messages + assert: + that: + - nm_schedule_incorrect_time is failed + - nm_schedule_incorrect_time.msg.startswith("Failed to parse time format") + - nm_schedule_incorrect_date is failed + - nm_schedule_incorrect_date.msg.startswith("Failed to parse date format") + - nm_schedule_incorrect_date_from_start_date is failed + - nm_schedule_incorrect_date_from_start_date.msg.startswith("Failed to create datetime object") + - nm_schedule_incorrect_time_from_start_time is failed + - nm_schedule_incorrect_time_from_start_time.msg.startswith("Failed to create datetime object") + +- name: Set backup schedule full (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + start_date: "2030-11-12" + start_time: "00:00:01" + frequency_unit: days + frequency_length: 1 + remote_location: ansible_test + remote_path: remote_add + state: present + delegate_to: localhost + register: nm_schedule_full + +- name: Verify success set backup schedule + assert: + that: + - nm_schedule_full is changed + - nm_schedule_full.current.timeInterval.length == 1 + - nm_schedule_full.current.timeInterval.unit == "DAYS" + - nm_schedule_full.current.callbackWSRequest.methodBody.locationType == "remote" + - nm_schedule_full.current.firstScheduledAt == "2030-11-12T00:00:01.000Z" + - nm_schedule_full.current.nextScheduleAt == "2030-11-13T00:00:01.000Z" + +- name: Delete backup schedule (normal mode) + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: absent + delegate_to: localhost + +- name: Get empty backup schedule + cisco.mso.mso_backup_schedule: + <<: *mso_info + state: query + delegate_to: localhost + register: no_schedule + +- name: Verify empty backup schedule + assert: + that: + - no_schedule is not changed + - no_schedule.current == {}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml new file mode 100644 index 000000000..b24749eb6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml @@ -0,0 +1,298 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy test case) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +#CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for MSO version < 4.0 + when: version.current.version is version('4.0', '<') + block: + - name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + register: ansible_tenant + + - name: Stop consuming DHCP Policy CLIENT_BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + + - name: Stop consuming DHCP Policy ansible_test_2 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + state: absent + ignore_errors: true + + - name: Stop consuming DHCP Policy ansible_test_multiple_dhcp + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_multiple_dhcp + state: absent + ignore_errors: true + + - name: Stop consuming DHCP Policy ansible_test_5 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test_2") }}' + template: Template 5 + bd: ansible_test_5 + state: absent + ignore_errors: true + + - name: Remove DHCP Option Policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_option_1 + - ansible_dhcp_option_2 + - ansible_test_dhcp_policy_option1 + - ansible_test_dhcp_policy_option2 + - ansible_test_dhcp_policy_option3 + + # ADD DHCP Policy + - name: Add a new DHCP Option Policy 1 (check mode) + mso_dhcp_option_policy: &create_dhcp + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + description: "My Test DHCP Policy 1" + tenant: ansible_test + state: present + check_mode: true + register: dhcp_pol1_cm + + - name: Add a new DHCP Option Policy 1 (normal mode) + mso_dhcp_option_policy: + <<: *create_dhcp + register: dhcp_pol1_nm + + - name: Verify dhcp_pol1_cm and dhcp_pol1_nm + assert: + that: + - dhcp_pol1_cm is changed + - dhcp_pol1_nm is changed + - dhcp_pol1_cm.current.name == dhcp_pol1_nm.current.name == 'ansible_dhcp_option_1' + - dhcp_pol1_cm.current.desc == dhcp_pol1_nm.current.desc == 'My Test DHCP Policy 1' + - dhcp_pol1_cm.current.policySubtype == dhcp_pol1_nm.current.policySubtype == 'option' + - dhcp_pol1_cm.current.policyType == dhcp_pol1_nm.current.policyType == 'dhcp' + - dhcp_pol1_cm.current.tenantId == dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id + + - name: Add a new DHCP Option Policy 1 again (check mode) + mso_dhcp_option_policy: + <<: *create_dhcp + check_mode: true + register: dhcp_pol1_again_cm + + - name: Add a new DHCP Option Policy 1 again (normal mode) + mso_dhcp_option_policy: + <<: *create_dhcp + register: dhcp_pol1_again_nm + + - name: Verify dhcp_pol1_again_cm and dhcp_pol1_again_nm + assert: + that: + - dhcp_pol1_again_cm is not changed + - dhcp_pol1_again_nm is not changed + - dhcp_pol1_again_cm.current.name == dhcp_pol1_again_nm.current.name == 'ansible_dhcp_option_1' + - dhcp_pol1_again_cm.current.desc == dhcp_pol1_again_nm.current.desc == 'My Test DHCP Policy 1' + - dhcp_pol1_again_cm.current.policySubtype == dhcp_pol1_again_nm.current.policySubtype == 'option' + - dhcp_pol1_again_cm.current.policyType == dhcp_pol1_again_nm.current.policyType == 'dhcp' + - dhcp_pol1_again_cm.current.tenantId == dhcp_pol1_again_nm.current.tenantId == ansible_tenant.current.id + + - name: Add a new DHCP Option Policy 2 (normal mode) + mso_dhcp_option_policy: + <<: *create_dhcp + dhcp_option_policy: ansible_dhcp_option_2 + + - name: Change DHCP Option Policy 1 description (check mode) + mso_dhcp_option_policy: + <<: *create_dhcp + description: "My Changed Test DHCP Policy 1" + check_mode: true + register: change_dhcp_pol1_cm + + - name: Change DHCP Option Policy 1 description (normal mode) + mso_dhcp_option_policy: + <<: *create_dhcp + description: "My Changed Test DHCP Policy 1" + register: change_dhcp_pol1_nm + + - name: Verify change_dhcp_pol1_cm and change_dhcp_pol1_nm + assert: + that: + - change_dhcp_pol1_cm is changed + - change_dhcp_pol1_nm is changed + - change_dhcp_pol1_cm.current.name == change_dhcp_pol1_nm.current.name == 'ansible_dhcp_option_1' + - change_dhcp_pol1_cm.current.desc == change_dhcp_pol1_nm.current.desc == 'My Changed Test DHCP Policy 1' + - change_dhcp_pol1_cm.current.policySubtype == change_dhcp_pol1_nm.current.policySubtype == 'option' + - change_dhcp_pol1_cm.current.policyType == change_dhcp_pol1_nm.current.policyType == 'dhcp' + - change_dhcp_pol1_cm.current.tenantId == change_dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id + + # QUERY A DHCP OPTION POLICY + - name: Query DHCP Option Policy 1 (check mode) + mso_dhcp_option_policy: &query_dhcp + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + state: query + check_mode: true + register: dhcp_pol1_query_cm + + - name: Query DHCP Option Policy 1 (normal mode) + mso_dhcp_option_policy: + <<: *query_dhcp + register: dhcp_pol1_query_nm + + - name: Verify dhcp_pol1_query + assert: + that: + - dhcp_pol1_query_cm is not changed + - dhcp_pol1_query_nm is not changed + - dhcp_pol1_query_cm.current.name == dhcp_pol1_query_nm.current.name == 'ansible_dhcp_option_1' + - dhcp_pol1_query_cm.current.desc == dhcp_pol1_query_nm.current.desc == 'My Changed Test DHCP Policy 1' + - dhcp_pol1_query_cm.current.policySubtype == dhcp_pol1_query_nm.current.policySubtype == 'option' + - dhcp_pol1_query_cm.current.policyType == dhcp_pol1_query_nm.current.policyType == 'dhcp' + + # QUERY A NON-EXISTING DHCP OPTION POLICY + - name: Query non-existing DHCP Option Policy (normal mode) + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: non_existing + state: query + register: quey_non_dhcp_pol + + - name: Verify quey_non_dhcp_pol + assert: + that: + - quey_non_dhcp_pol is not changed + + # QUERY ALL DHCP OPTION POLICIES + - name: Query all DHCP Option Policies (normal mode) + mso_dhcp_option_policy: + <<: *mso_info + state: query + register: dhcp_policies_query + + - name: Verify dhcp_policies_query + assert: + that: + - dhcp_policies_query is not changed + - dhcp_policies_query.current | length == 2 + + # REMOVE DHCP POLICY + - name: Remove DHCP Option Policy 1 (check mode) + mso_dhcp_option_policy: &remove_dhcp + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + state: absent + check_mode: true + register: dhcp_pol1_removed_cm + + - name: Remove DHCP Option Policy 1 (normal mode) + mso_dhcp_option_policy: + <<: *remove_dhcp + register: dhcp_pol1_removed_nm + + - name: Verify dhcp_policies_removed + assert: + that: + - dhcp_pol1_removed_cm is changed + - dhcp_pol1_removed_nm is changed + - dhcp_pol1_removed_cm.current == dhcp_pol1_removed_nm.current == {} + + # REMOVE DHCP POLICY AGAIN + - name: Remove DHCP Option Policy 1 again (check mode) + mso_dhcp_option_policy: + <<: *remove_dhcp + check_mode: true + register: dhcp_pol1_removed_again_cm + + - name: Remove DHCP Option Policy 1 again (normal mode) + mso_dhcp_option_policy: + <<: *remove_dhcp + register: dhcp_pol1_removed_again_nm + + - name: Verify dhcp_pol1_removed_again + assert: + that: + - dhcp_pol1_removed_again_cm is not changed + - dhcp_pol1_removed_again_nm is not changed + - dhcp_pol1_removed_again_cm.current == dhcp_pol1_removed_again_nm.current == {} + - dhcp_pol1_removed_again_cm.previous == dhcp_pol1_removed_again_nm.previous == {} + + + # USE A NON-EXISTING TENANT + - name: Non Existing Tenant for DHCP Option Policy 3 (normal mode) + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_3 + description: "My Test DHCP Policy 3" + tenant: non_existing + state: present + ignore_errors: true + register: nm_non_existing_tenant + + - name: Verify nm_non_existing_tenant + assert: + that: + - nm_non_existing_tenant is not changed + - nm_non_existing_tenant.msg == "Tenant 'non_existing' is not valid tenant name." + + # CLEAN UP + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + + - name: Remove DHCP Option Policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_option_1 + - ansible_dhcp_option_2
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml new file mode 100644 index 000000000..f6a79204f --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml @@ -0,0 +1,444 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy test case) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +#CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for MSO version < 4.0 + when: version.current.version is version('4.0', '<') + block: + - name: Remove options from DHCP Option Policy + mso_dhcp_option_policy_option: + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + name: "{{ item }}" + state: absent + loop: + - ansibletest + - ansibletest2 + ignore_errors: true + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: present + ignore_errors: true + + - name: Remove DHCP Relay Policy 1 + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2 + + - name: Remove DHCP Option Policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + state: absent + ignore_errors: true + loop: + - ansible_dhcp_option_1 + - ansible_dhcp_option_2 + + - name: Undeploy sites in schema 1 template 1 + mso_schema_template_deploy: + <<: *mso_info + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + + - name: Undeploy sites in schema 1 template 2 + mso_schema_template_deploy: + <<: *mso_info + template: Template 2 + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + + - name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + + - name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + register: tenant_ansible + + - name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + + - name: Add a new VRF + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + + - name: Add BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: present + + # ADD DHCP RELAY AND OPTION POLICY + - name: Add a new DHCP Option Policy 1 (Normal mode) + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + description: "My Test DHCP Policy 1" + tenant: ansible_test + state: present + + - name: Add a new DHCP Relay Policy 1 (Normal mode) + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + description: "My Test DHCP Policy 1" + tenant: ansible_test + state: present + + # ADD OPTION TO DHCP OPTION POLICY + - name: Add Option to DHCP Option Policy (check mode) + mso_dhcp_option_policy_option: &create_option + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + name: ansibletest + id: 1 + data: DHCP Data + state: present + check_mode: true + register: dhcp_pol1_opt1_cm + + - name: Add Option to DHCP Option Policy (normal mode) + mso_dhcp_option_policy_option: + <<: *create_option + register: dhcp_pol1_opt1_nm + + - name: Verify dhcp_pol1_opt1 + assert: + that: + - dhcp_pol1_opt1_cm is changed + - dhcp_pol1_opt1_nm is changed + - dhcp_pol1_opt1_cm.current.name == dhcp_pol1_opt1_nm.current.name == 'ansibletest' + - dhcp_pol1_opt1_cm.current.id == dhcp_pol1_opt1_nm.current.id == '1' + - dhcp_pol1_opt1_cm.current.data == dhcp_pol1_opt1_nm.current.data == 'DHCP Data' + + - name: Add Option to DHCP Option Policy again (check mode) + mso_dhcp_option_policy_option: + <<: *create_option + check_mode: true + register: dhcp_pol1_opt1_again_cm + + - name: Add Option to DHCP Option Policy again (normal mode) + mso_dhcp_option_policy_option: + <<: *create_option + register: dhcp_pol1_opt1_again_nm + + - name: Verify dhcp_pol1_opt1_again + assert: + that: + - dhcp_pol1_opt1_again_cm is not changed + - dhcp_pol1_opt1_again_nm is not changed + - dhcp_pol1_opt1_again_cm.current.name == dhcp_pol1_opt1_again_nm.current.name == 'ansibletest' + - dhcp_pol1_opt1_again_cm.current.id == dhcp_pol1_opt1_again_nm.current.id == '1' + - dhcp_pol1_opt1_again_cm.current.data == dhcp_pol1_opt1_again_nm.current.data == 'DHCP Data' + + - name: Change Option IP to DHCP Option Policy (check mode) + mso_dhcp_option_policy_option: + <<: *create_option + data: Changed DHCP Data + check_mode: true + register: dhcp_pol1_opt1_change_cm + + - name: Change Option IP to DHCP Option Policy (normal mode) + mso_dhcp_option_policy_option: + <<: *create_option + data: Changed DHCP Data + register: dhcp_pol1_opt1_change_nm + + - name: Verify dhcp_pol1_opt1_change + assert: + that: + - dhcp_pol1_opt1_change_cm is changed + - dhcp_pol1_opt1_change_nm is changed + - dhcp_pol1_opt1_change_cm.current.name == dhcp_pol1_opt1_change_nm.current.name == 'ansibletest' + - dhcp_pol1_opt1_change_cm.current.id == dhcp_pol1_opt1_change_nm.current.id == '1' + - dhcp_pol1_opt1_change_cm.current.data == dhcp_pol1_opt1_change_nm.current.data == 'Changed DHCP Data' + + - name: Add 2nd Option to DHCP Option Policy (check mode) + mso_dhcp_option_policy_option: + <<: *create_option + name: ansibletest2 + check_mode: true + register: dhcp_pol1_opt2_cm + + - name: Add 2nd Option to DHCP Option Policy (normal mode) + mso_dhcp_option_policy_option: + <<: *create_option + name: ansibletest2 + register: dhcp_pol1_opt2_nm + + - name: Verify dhcp_pol1_opt2 + assert: + that: + - dhcp_pol1_opt2_cm is changed + - dhcp_pol1_opt2_nm is changed + - dhcp_pol1_opt2_cm.current.name == dhcp_pol1_opt2_nm.current.name == 'ansibletest2' + - dhcp_pol1_opt2_cm.current.id == dhcp_pol1_opt2_nm.current.id == '1' + - dhcp_pol1_opt2_cm.current.data == dhcp_pol1_opt2_nm.current.data == 'DHCP Data' + + # QUERY OPTION FROM DHCP OPTION POLICY + - name: Query Option from DHCP Option Policy (check mode) + mso_dhcp_option_policy_option: &query_option + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + name: ansibletest + state: query + register: dhcp_pol1_opt1_query_cm + + - name: Query Option from DHCP Option Policy (normal mode) + mso_dhcp_option_policy_option: + <<: *query_option + register: dhcp_pol1_opt1_query_nm + + - name: Query nonexisting Option from DHCP Option Policy + mso_dhcp_option_policy_option: + <<: *query_option + name: nonexisting + state: query + register: dhcp_pol1_opt1_query_non_existing + + - name: Query all Options from a DHCP Option Policy + mso_dhcp_option_policy_option: + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + state: query + register: dhcp_pol1_query_all + + - name: Verify all query variables + assert: + that: + - dhcp_pol1_opt1_query_cm is not changed + - dhcp_pol1_opt1_query_nm is not changed + - dhcp_pol1_opt1_query_non_existing is not changed + - dhcp_pol1_query_all is not changed + - dhcp_pol1_opt1_query_cm.current.name == dhcp_pol1_opt1_query_nm.current.name == 'ansibletest' + - dhcp_pol1_opt1_query_cm.current.id == dhcp_pol1_opt1_query_nm.current.id == '1' + - dhcp_pol1_opt1_query_cm.current.data == dhcp_pol1_opt1_query_nm.current.data == 'Changed DHCP Data' + - dhcp_pol1_opt1_query_non_existing.current == {} + - dhcp_pol1_query_all.current | length == 2 + + # REMOVE OPTION FROM DHCP OPTION POLICY + - name: Remove Option from DHCP Option Policy (check mode) + mso_dhcp_option_policy_option: &delete_option + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + name: ansibletest + state: absent + check_mode: true + register: dhcp_pol1_opt1_del_cm + + - name: Remove Option from DHCP Option Policy (normal mode) + mso_dhcp_option_policy_option: + <<: *delete_option + register: dhcp_pol1_opt1_del_nm + + - name: Verify dhcp_pol1_opt1_del + assert: + that: + - dhcp_pol1_opt1_del_cm is changed + - dhcp_pol1_opt1_del_nm is changed + - dhcp_pol1_opt1_del_cm.current == dhcp_pol1_opt1_del_nm.current == {} + + - name: Remove Option from DHCP Option Policy again (check mode) + mso_dhcp_option_policy_option: + <<: *delete_option + check_mode: true + register: dhcp_pol1_opt1_del_again_cm + + - name: Remove Option from DHCP Option Policy again (normal mode) + mso_dhcp_option_policy_option: + <<: *delete_option + register: dhcp_pol1_opt1_del_again_nm + + - name: Verify dhcp_pol1_opt1_again_del + assert: + that: + - dhcp_pol1_opt1_del_again_cm is not changed + - dhcp_pol1_opt1_del_again_nm is not changed + - dhcp_pol1_opt1_del_again_cm.current == dhcp_pol1_opt1_del_again_nm.current == {} + + - name: Remove Non-Existing Option + mso_dhcp_option_policy_option: + <<: *delete_option + name: nonexisting + register: dhcp_pol1_opt1_del_nm_non_existing + + - name: Verify dhcp_pol1_opt1_del_nm_non_existing + assert: + that: + - dhcp_pol1_opt1_del_nm_non_existing is not changed + - dhcp_pol1_opt1_del_nm_non_existing.current == {} + + # CONSUME DHCP POLICIES + - name: Get DHCP Relay Policy version + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + state: query + register: dhcp_relay_policy_version + + - name: Get DHCP Option Policy version + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + state: query + register: dhcp_option_policy_version + + - name: Consume DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + dhcp_policy: + name: "{{ dhcp_relay_policy_version.current.name }}" + version: "{{ dhcp_relay_policy_version.current.version | int }}" + dhcp_option_policy: + name: "{{ dhcp_option_policy_version.current.name }}" + version: "{{ dhcp_option_policy_version.current.version | int }}" + state: present + register: bd_dhcp_policy + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: present + register: bd_dhcp_policy + + # QUERY OPTION FROM non_existing DHCP OPTION POLICY + - name: Query Option from DHCP Option Policy (check mode) + mso_dhcp_option_policy_option: + <<: *mso_info + dhcp_option_policy: nonexisting + state: query + ignore_errors: true + register: dhcp_non_existing + + - name: Verify dhcp_non_existing + assert: + that: + - dhcp_non_existing is not changed + - dhcp_non_existing.msg == "DHCP Option Policy 'nonexisting' is not a valid DHCP Option Policy name." + + # CLEAN UP + - name: Remove options from DHCP Option Policy + mso_dhcp_option_policy_option: + <<: *mso_info + dhcp_option_policy: ansible_dhcp_option_1 + name: "{{ item }}" + state: absent + loop: + - ansibletest + - ansibletest2 + ignore_errors: true + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + + - name: Remove DHCP Relay Policy 1 + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2 + + - name: Remove DHCP Option Policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + state: absent + ignore_errors: true + loop: + - ansible_dhcp_option_1 + - ansible_dhcp_option_2
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml new file mode 100644 index 000000000..4a7db2dca --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml @@ -0,0 +1,270 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +#CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for MSO version < 4.0 + when: version.current.version is version('4.0', '<') + block: + - name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + register: ansible_tenant + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + + - name: Remove DHCP Relay Policy 1 + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2 + - ansible_test_dhcp_policy1 + - ansible_test_dhcp_policy2 + - ansible_test_dhcp_policy3 + + # ADD DHCP Policy + - name: Add a new DHCP Relay Policy 1 (check mode) + mso_dhcp_relay_policy: &create_dhcp + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + description: "My Test DHCP Policy 1" + tenant: ansible_test + state: present + check_mode: true + register: dhcp_pol1_cm + + - name: Add a new DHCP Relay Policy 1 (normal mode) + mso_dhcp_relay_policy: + <<: *create_dhcp + register: dhcp_pol1_nm + + - name: Verify dhcp_pol1_cm and dhcp_pol1_nm + assert: + that: + - dhcp_pol1_cm is changed + - dhcp_pol1_nm is changed + - dhcp_pol1_cm.current.name == dhcp_pol1_nm.current.name == 'ansible_dhcp_relay_1' + - dhcp_pol1_cm.current.desc == dhcp_pol1_nm.current.desc == 'My Test DHCP Policy 1' + - dhcp_pol1_cm.current.policySubtype == dhcp_pol1_nm.current.policySubtype == 'relay' + - dhcp_pol1_cm.current.policyType == dhcp_pol1_nm.current.policyType == 'dhcp' + - dhcp_pol1_cm.current.tenantId == dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id + + - name: Add a new DHCP Relay Policy 1 again (check mode) + mso_dhcp_relay_policy: + <<: *create_dhcp + check_mode: true + register: dhcp_pol1_again_cm + + - name: Add a new DHCP Relay Policy 1 again (normal mode) + mso_dhcp_relay_policy: + <<: *create_dhcp + register: dhcp_pol1_again_nm + + - name: Verify dhcp_pol1_again_cm and dhcp_pol1_again_nm + assert: + that: + - dhcp_pol1_again_cm is not changed + - dhcp_pol1_again_nm is not changed + - dhcp_pol1_again_cm.current.name == dhcp_pol1_again_nm.current.name == 'ansible_dhcp_relay_1' + - dhcp_pol1_again_cm.current.desc == dhcp_pol1_again_nm.current.desc == 'My Test DHCP Policy 1' + - dhcp_pol1_again_cm.current.policySubtype == dhcp_pol1_again_nm.current.policySubtype == 'relay' + - dhcp_pol1_again_cm.current.policyType == dhcp_pol1_again_nm.current.policyType == 'dhcp' + - dhcp_pol1_again_cm.current.tenantId == dhcp_pol1_again_nm.current.tenantId == ansible_tenant.current.id + + - name: Add a new DHCP Relay Policy 2 (normal mode) + mso_dhcp_relay_policy: + <<: *create_dhcp + dhcp_relay_policy: ansible_dhcp_relay_2 + + - name: Change DHCP Relay Policy 1 description (check mode) + mso_dhcp_relay_policy: + <<: *create_dhcp + description: "My Changed Test DHCP Policy 1" + check_mode: true + register: change_dhcp_pol1_cm + + - name: Change DHCP Relay Policy 1 description (normal mode) + mso_dhcp_relay_policy: + <<: *create_dhcp + description: "My Changed Test DHCP Policy 1" + register: change_dhcp_pol1_nm + + - name: Verify change_dhcp_pol1_cm and change_dhcp_pol1_nm + assert: + that: + - change_dhcp_pol1_cm is changed + - change_dhcp_pol1_nm is changed + - change_dhcp_pol1_cm.current.name == change_dhcp_pol1_nm.current.name == 'ansible_dhcp_relay_1' + - change_dhcp_pol1_cm.current.desc == change_dhcp_pol1_nm.current.desc == 'My Changed Test DHCP Policy 1' + - change_dhcp_pol1_cm.current.policySubtype == change_dhcp_pol1_nm.current.policySubtype == 'relay' + - change_dhcp_pol1_cm.current.policyType == change_dhcp_pol1_nm.current.policyType == 'dhcp' + - change_dhcp_pol1_cm.current.tenantId == change_dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id + + # QUERY A DHCP RELAY POLICY + - name: Query DHCP Relay Policy 1 (check mode) + mso_dhcp_relay_policy: &query_dhcp + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + state: query + check_mode: true + register: dhcp_pol1_query_cm + + - name: Query DHCP Relay Policy 1 (normal mode) + mso_dhcp_relay_policy: + <<: *query_dhcp + register: dhcp_pol1_query_nm + + - name: Verify dhcp_pol1_query + assert: + that: + - dhcp_pol1_query_cm is not changed + - dhcp_pol1_query_nm is not changed + - dhcp_pol1_query_cm.current.name == dhcp_pol1_query_nm.current.name == 'ansible_dhcp_relay_1' + - dhcp_pol1_query_cm.current.desc == dhcp_pol1_query_nm.current.desc == 'My Changed Test DHCP Policy 1' + - dhcp_pol1_query_cm.current.policySubtype == dhcp_pol1_query_nm.current.policySubtype == 'relay' + - dhcp_pol1_query_cm.current.policyType == dhcp_pol1_query_nm.current.policyType == 'dhcp' + + # QUERY A NON-EXISTING DHCP RELAY POLICY + - name: Query non-existing DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: non_existing + state: query + register: quey_non_dhcp_pol + + - name: Verify quey_non_dhcp_pol + assert: + that: + - quey_non_dhcp_pol is not changed + + # QUERY ALL DHCP RELAY POLICIES + - name: Query all DHCP Relay Policies (normal mode) + mso_dhcp_relay_policy: + <<: *mso_info + state: query + register: dhcp_policies_query + + - name: Verify dhcp_policies_query + assert: + that: + - dhcp_policies_query is not changed + - dhcp_policies_query.current | length == 2 + + # REMOVE DHCP POLICY + - name: Remove DHCP Relay Policy 1 (check mode) + mso_dhcp_relay_policy: &remove_dhcp + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + state: absent + check_mode: true + register: dhcp_pol1_removed_cm + + - name: Remove DHCP Relay Policy 1 (normal mode) + mso_dhcp_relay_policy: + <<: *remove_dhcp + register: dhcp_pol1_removed_nm + + - name: Verify dhcp_policies_removed + assert: + that: + - dhcp_pol1_removed_cm is changed + - dhcp_pol1_removed_nm is changed + - dhcp_pol1_removed_cm.current == dhcp_pol1_removed_nm.current == {} + + # REMOVE DHCP POLICY AGAIN + - name: Remove DHCP Relay Policy 1 again (check mode) + mso_dhcp_relay_policy: + <<: *remove_dhcp + check_mode: true + register: dhcp_pol1_removed_again_cm + + - name: Remove DHCP Relay Policy 1 again (normal mode) + mso_dhcp_relay_policy: + <<: *remove_dhcp + register: dhcp_pol1_removed_again_nm + + - name: Verify dhcp_pol1_removed_again + assert: + that: + - dhcp_pol1_removed_again_cm is not changed + - dhcp_pol1_removed_again_nm is not changed + - dhcp_pol1_removed_again_cm.current == dhcp_pol1_removed_again_nm.current == {} + - dhcp_pol1_removed_again_cm.previous == dhcp_pol1_removed_again_nm.previous == {} + + + # USE A NON-EXISTING TENANT + - name: Non Existing Tenant for DHCP Relay Policy 3 (normal mode) + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_3 + description: "My Test DHCP Policy 3" + tenant: non_existing + state: present + ignore_errors: true + register: nm_non_existing_tenant + + - name: Verify nm_non_existing_tenant + assert: + that: + - nm_non_existing_tenant is not changed + - nm_non_existing_tenant.msg == "Tenant 'non_existing' is not valid tenant name." + + # CLEAN UP DHCP POLICIES + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + + - name: Remove DHCP Relay Policy 1 + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml new file mode 100644 index 000000000..828a34424 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml @@ -0,0 +1,662 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <cizhao@jgomezve.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +#CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for MSO version < 4.0 + when: version.current.version is version('4.0', '<') + block: + - name: Remove EXT_EPGs Providers from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_endpoint_group: "{{ item }}" + state: absent + ignore_errors: true + loop: + - EXT_EPG_1 + - EXT_EPG_2 + + - name: Remove EXT_EPGs Providers from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + endpoint_group: "{{ item }}" + application_profile: "ANP_1" + state: absent + ignore_errors: true + loop: + - EPG_1 + - EPG_2 + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: present + ignore_errors: true + + - name: Remove DHCP Relay Policies + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + ignore_errors: true + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2 + + - name: Undeploy sites in schema 1 template 1 + mso_schema_template_deploy: + <<: *mso_info + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + + - name: Undeploy sites in schema 1 template 2 + mso_schema_template_deploy: + <<: *mso_info + template: Template 2 + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + + - name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + + - name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + register: tenant_ansible + + - name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + + # CREATE EPG PROVIDER + - name: Add a new VRF + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + + - name: Add a new BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: BD_1 + vrf: + name: VRF1 + state: present + + - name: Add 2nd BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: present + + - name: Add a new ANP + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP_1 + state: present + + - name: Add a new EPG + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP_1 + epg: EPG_1 + bd: + name: BD_1 + vrf: + name: VRF1 + state: present + + - name: Add 2nd EPG + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP_1 + epg: EPG_2 + bd: + name: BD_1 + vrf: + name: VRF1 + state: present + + - name: Add a new L3out + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: L3OUT_1 + vrf: + name: VRF1 + state: present + + - name: Add a new external EPG + cisco.mso.mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: EXT_EPG_1 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: + name: L3OUT_1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + + - name: Add 2nd external EPG + cisco.mso.mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: EXT_EPG_2 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: + name: L3OUT_1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + + # ADD DHCP RELAY POLICY + - name: Add a new DHCP Relay Policy 1 (Normal mode) + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + description: "My Test DHCP Policy 1" + tenant: ansible_test + state: present + + # ADD PROVIDER TO DHCP RELAY POLICY + - name: Add Provider to DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: &create_provider + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + ip: "1.1.1.1" + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + application_profile: ANP_1 + endpoint_group: EPG_1 + state: present + check_mode: true + register: dhcp_pol1_prov1_cm + + - name: Add Provider to DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + register: dhcp_pol1_prov1_nm + + - name: Verify dhcp_pol1_prov1 + assert: + that: + - dhcp_pol1_prov1_cm is changed + - dhcp_pol1_prov1_nm is changed + - dhcp_pol1_prov1_cm.current.addr == dhcp_pol1_prov1_nm.current.addr == '1.1.1.1' + - "'EPG_1' in dhcp_pol1_prov1_cm.current.epgRef" + - "'EPG_1' in dhcp_pol1_prov1_nm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_cm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_nm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_cm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_nm.current.epgRef" + - dhcp_pol1_prov1_cm.current.tenantId == tenant_ansible.current.id + - dhcp_pol1_prov1_nm.current.tenantId == tenant_ansible.current.id + + - name: Add Provider to DHCP Relay Policy again (check mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + check_mode: true + register: dhcp_pol1_prov1_again_cm + + - name: Add Provider to DHCP Relay Policy again (normal mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + register: dhcp_pol1_prov1_again_nm + + - name: Verify dhcp_pol1_prov1_again + assert: + that: + - dhcp_pol1_prov1_again_cm is not changed + - dhcp_pol1_prov1_again_nm is not changed + - dhcp_pol1_prov1_again_cm.current.addr == dhcp_pol1_prov1_again_nm.current.addr == '1.1.1.1' + - "'EPG_1' in dhcp_pol1_prov1_again_cm.current.epgRef" + - "'EPG_1' in dhcp_pol1_prov1_again_nm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_again_cm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_again_nm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_again_cm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_again_nm.current.epgRef" + - dhcp_pol1_prov1_again_cm.current.tenantId == tenant_ansible.current.id + - dhcp_pol1_prov1_again_nm.current.tenantId == tenant_ansible.current.id + + - name: Change Provider IP to DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + ip: "2.2.2.2" + check_mode: true + register: dhcp_pol1_prov1_change_cm + + - name: Change Provider IP to DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + ip: "2.2.2.2" + register: dhcp_pol1_prov1_change_nm + + - name: Verify dhcp_pol1_prov1_change + assert: + that: + - dhcp_pol1_prov1_change_cm is changed + - dhcp_pol1_prov1_change_nm is changed + - dhcp_pol1_prov1_change_cm.current.addr == dhcp_pol1_prov1_change_nm.current.addr == '2.2.2.2' + - "'EPG_1' in dhcp_pol1_prov1_change_cm.current.epgRef" + - "'EPG_1' in dhcp_pol1_prov1_change_nm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_change_cm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_change_nm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_change_cm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_change_nm.current.epgRef" + - dhcp_pol1_prov1_change_cm.current.tenantId == tenant_ansible.current.id + - dhcp_pol1_prov1_change_nm.current.tenantId == tenant_ansible.current.id + + - name: Add 2nd Provider (EPG_2) to DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + ip: "2.2.2.2" + endpoint_group: EPG_2 + check_mode: true + register: dhcp_pol1_prov2_cm + + - name: Add 2nd Provider (EPG_2) to DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider + ip: "2.2.2.2" + endpoint_group: EPG_2 + register: dhcp_pol1_prov2_nm + + - name: Add 3rd Provider (EXT_EPG_1) to DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: &create_provider_external_epg + <<: *create_provider + ip: "2.2.2.2" + external_endpoint_group: EXT_EPG_1 + application_profile: null + endpoint_group: null + check_mode: true + register: dhcp_pol1_prov3_cm + + - name: Add 3rd Provider (EXT_EPG_1) to DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider_external_epg + external_endpoint_group: EXT_EPG_1 + register: dhcp_pol1_prov3_nm + + - name: Add 4th Provider (EXT_EPG_2) to DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider_external_epg + external_endpoint_group: EXT_EPG_2 + check_mode: true + register: dhcp_pol1_prov4_cm + + - name: Add 4th Provider (EXT_EPG_2) to DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *create_provider_external_epg + external_endpoint_group: EXT_EPG_2 + register: dhcp_pol1_prov4_nm + + - name: Verify dhcp_pol1_prov2, dhcp_pol1_prov3 and dhcp_pol1_prov4 + assert: + that: + - dhcp_pol1_prov2_cm is changed + - dhcp_pol1_prov2_nm is changed + - dhcp_pol1_prov3_cm is changed + - dhcp_pol1_prov3_nm is changed + - dhcp_pol1_prov4_cm is changed + - dhcp_pol1_prov4_nm is changed + - dhcp_pol1_prov2_cm.current.addr == dhcp_pol1_prov2_nm.current.addr == '2.2.2.2' + - dhcp_pol1_prov3_cm.current.addr == dhcp_pol1_prov3_nm.current.addr == '2.2.2.2' + - dhcp_pol1_prov4_cm.current.addr == dhcp_pol1_prov4_nm.current.addr == '2.2.2.2' + - "'EPG_2' in dhcp_pol1_prov2_cm.current.epgRef" + - "'EPG_2' in dhcp_pol1_prov2_nm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov2_cm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov2_nm.current.epgRef" + - "'Template1' in dhcp_pol1_prov2_cm.current.epgRef" + - "'Template1' in dhcp_pol1_prov2_nm.current.epgRef" + - "'EXT_EPG_1' in dhcp_pol1_prov3_cm.current.externalEpgRef" + - "'EXT_EPG_1' in dhcp_pol1_prov3_nm.current.externalEpgRef" + - "'EXT_EPG_2' in dhcp_pol1_prov4_cm.current.externalEpgRef" + - "'EXT_EPG_2' in dhcp_pol1_prov4_nm.current.externalEpgRef" + - dhcp_pol1_prov3_cm.current.tenantId == tenant_ansible.current.id + - dhcp_pol1_prov3_nm.current.tenantId == tenant_ansible.current.id + - dhcp_pol1_prov4_cm.current.tenantId == tenant_ansible.current.id + - dhcp_pol1_prov4_nm.current.tenantId == tenant_ansible.current.id + + # ADD DHCP RELAY PROVIDER WITH WRONG Attributes + - name: Add Provider to DHCP Relay Policy - wrong tenant (Normal mode) + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + ip: "2.2.2.2" + tenant: ansible_test_wrong + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + application_profile: ANP_1 + endpoint_group: EPG_1 + state: present + ignore_errors: true + register: dhcp_pol1_prov2_nm_ten_wrong + + - name: Add Provider to DHCP Relay Policy - wrong Schema (Normal mode) + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + ip: "2.2.2.2" + tenant: ansible_test + schema: schema_wrong + template: Template 1 + application_profile: ANP_1 + endpoint_group: EPG_1 + state: present + ignore_errors: true + register: dhcp_pol1_prov2_nm_sch_wrong + + - name: Verify dhcp_pol1_prov2_nm_ten_wrong, dhcp_pol1_prov2_nm_sch_wrong & dhcp_pol1_prov2_nm_tmp_wrong + assert: + that: + - dhcp_pol1_prov2_nm_ten_wrong is not changed + - dhcp_pol1_prov2_nm_ten_wrong.msg == "Tenant 'ansible_test_wrong' is not valid tenant name." + - dhcp_pol1_prov2_nm_sch_wrong is not changed + - dhcp_pol1_prov2_nm_sch_wrong.msg == "Provided schema 'schema_wrong' does not exist." + # MSO API allows to create provider in non-existing/wrong templates/epgs/ext_epgs + + # QUERY PROVIDER FROM DHCP RELAY POLICY + - name: Query Provider from DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: &query_provider + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + application_profile: ANP_1 + endpoint_group: EPG_1 + state: query + register: dhcp_pol1_prov1_query_cm + + - name: Query Provider from DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *query_provider + register: dhcp_pol1_prov1_query_nm + + - name: Query non_existing Provider from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *query_provider + endpoint_group: non_existing + state: query + register: dhcp_pol1_prov1_query_non_existing + + - name: Query all Providers from a DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + state: query + register: dhcp_pol1_query_all + + - name: Verify all query variables + assert: + that: + - dhcp_pol1_prov1_query_cm is not changed + - dhcp_pol1_prov1_query_nm is not changed + - dhcp_pol1_prov1_query_non_existing is not changed + - dhcp_pol1_query_all is not changed + - dhcp_pol1_prov1_query_cm.current.addr == dhcp_pol1_prov1_query_nm.current.addr == '2.2.2.2' + - "'EPG_1' in dhcp_pol1_prov1_query_cm.current.epgRef" + - "'EPG_1' in dhcp_pol1_prov1_query_nm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_query_cm.current.epgRef" + - "'ANP_1' in dhcp_pol1_prov1_query_nm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_query_cm.current.epgRef" + - "'Template1' in dhcp_pol1_prov1_query_nm.current.epgRef" + - dhcp_pol1_prov1_query_non_existing.current == {} + - dhcp_pol1_query_all.current | length == 4 + + # REMOVE PROVIDER FROM DHCP RELAY POLICY + - name: Remove Provider (EXT_EPG) from DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: &delete_provider + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_endpoint_group: EXT_EPG_1 + state: absent + check_mode: true + register: dhcp_pol1_prov1_del_cm + + - name: Remove Provider (EXT_EPG) from DHCP Relay Policy (normal mode) + mso_dhcp_relay_policy_provider: + <<: *delete_provider + register: dhcp_pol1_prov1_del_nm + + - name: Verify dhcp_pol1_prov1_del + assert: + that: + - dhcp_pol1_prov1_del_cm is changed + - dhcp_pol1_prov1_del_nm is changed + - dhcp_pol1_prov1_del_cm.current == dhcp_pol1_prov1_del_nm.current == {} + + - name: Remove Provider (EXT_EPG) from DHCP Relay Policy again (check mode) + mso_dhcp_relay_policy_provider: + <<: *delete_provider + check_mode: true + register: dhcp_pol1_prov1_del_again_cm + + - name: Remove Provider (EXT_EPG) from DHCP Relay Policy again (normal mode) + mso_dhcp_relay_policy_provider: + <<: *delete_provider + register: dhcp_pol1_prov1_del_again_nm + + - name: Verify dhcp_pol1_prov1_again_del + assert: + that: + - dhcp_pol1_prov1_del_again_cm is not changed + - dhcp_pol1_prov1_del_again_nm is not changed + - dhcp_pol1_prov1_del_again_cm.current == dhcp_pol1_prov1_del_again_nm.current == {} + + - name: Remove Non-Existing Provider (EXT_EPG) + mso_dhcp_relay_policy_provider: + <<: *delete_provider + external_endpoint_group: non_existing + register: dhcp_pol1_prov1_del_nm_non_existing + + - name: Remove Provider without epg or ext_epg + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: absent + ignore_errors: true + register: dhcp_pol1_prov1_del_none + + - name: Verify dhcp_pol1_prov1_del_nm_non_existing + assert: + that: + - dhcp_pol1_prov1_del_nm_non_existing is not changed + - dhcp_pol1_prov1_del_none is not changed + - dhcp_pol1_prov1_del_nm_non_existing.current == {} + - dhcp_pol1_prov1_del_none.msg == 'Missing either endpoint_group or external_endpoint_group required attribute.' + + # CONSUME DHCP POLICIES + - name: Get DHCP Relay Policy version + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + state: query + register: dhcp_relay_policy_version + + - name: Consume DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + dhcp_policy: + name: "{{ dhcp_relay_policy_version.current.name }}" + version: "{{ dhcp_relay_policy_version.current.version | int }}" + state: present + register: bd_dhcp_policy + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: present + register: bd_dhcp_policy + + # QUERY PROVIDER FROM non_existing DHCP RELAY POLICY + - name: Query Provider from DHCP Relay Policy (check mode) + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: non_existing + state: query + ignore_errors: true + register: dhcp_non_existing + + - name: Verify dhcp_non_existing + assert: + that: + - dhcp_non_existing is not changed + - dhcp_non_existing.msg == "DHCP Relay Policy 'non_existing' is not a valid DHCP Relay Policy name." + + # CLEAN UP + - name: Remove EXT_EPGs Providers from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_endpoint_group: "{{ item }}" + state: absent + ignore_errors: true + loop: + - EXT_EPG_1 + - EXT_EPG_2 + + - name: Remove EXT_EPGs Providers from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + endpoint_group: "{{ item }}" + application_profile: "ANP_1" + state: absent + ignore_errors: true + loop: + - EPG_1 + - EPG_2 + + - name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + + - name: Remove DHCP Relay Policies + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + ignore_errors: true + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml new file mode 100644 index 000000000..fffb0ce78 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml @@ -0,0 +1,411 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + - name: GET auth radius providers + mso_rest: + <<: *mso_info + path: /api/v1/auth/providers/radius + method: get + register: radius_providers + + - name: Add auth radius provider + mso_rest: + <<: *mso_info + path: /api/v1/auth/providers/radius + method: post + content: + { + "host": "{{ mso_radius_server }}", + "description": "", + "port": 1812, + "providerType": "radius", + "sharedSecret": "{{ mso_radius_secret | default('radius-secret') }}", + "timeoutInSeconds": 5, + "retries": 3, + "protocol": "pap" + } + register: radius_provider + when: mso_radius_server not in (radius_providers.jsondata.radiusProviders | map(attribute='host')) + + - name: GET login domains + mso_rest: + <<: *mso_info + path: /api/v1/auth/domains + method: get + register: login_domains + + - name: GET auth radius providers again after creation + mso_rest: + <<: *mso_info + path: /api/v1/auth/providers/radius + method: get + register: radius_providers + + - name: GET auth radius provider ID + set_fact: + radius_provider_id: "{{ (radius_providers.jsondata.radiusProviders | selectattr('host', 'eq', mso_radius_server) | first)['id'] }}" + + - name: Add test login domain + mso_rest: + <<: *mso_info + path: /api/v1/auth/domains + method: post + content: + { + "name": "{{ mso_login_domain | default('test') }}", + "description": "", + "realm": "radius", + "providerAssociations": [{ + "priority": 1, + "providerId": "{{ radius_provider_id }}" + }], + "status": "active", + "isDefault": false + } + when: (mso_login_domain | default('test')) not in (login_domains.jsondata.domains | map(attribute='name')) + +# REMOVE DHCP POLICY +- name: Remove DHCP Option Policy + mso_dhcp_option_policy: &remove_dhcp + <<: *mso_info + dhcp_option_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_option_1 + - ansible_dhcp_option_2 + +- name: Remove DHCP Relay Policy + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2 + +- name: Remove label ansible_test + mso_label: &label_absent + <<: *mso_info + label: ansible_test + state: absent + +- name: Remove label ansible_test2 + mso_label: + <<: *label_absent + label: ansible_test2 + register: cm_remove_label + +- name: Remove label ansible_test3 + mso_label: &domain_label_absent + <<: *mso_info + state: absent + label: ansible_test3 + login_domain: Local + register: nm_remove_label3 + +- name: Remove label ansible_test4 + mso_label: + <<: *domain_label_absent + label: ansible_test4 + login_domain: '{{ mso_login_domain | default("test") }}' + +# ADD LABEL +- name: Add label (check_mode) + mso_label: &label_present + <<: *mso_info + label: ansible_test + state: present + check_mode: true + register: cm_add_label + +- name: Verify cm_add_label + assert: + that: + - cm_add_label is changed + - cm_add_label.previous == {} + - cm_add_label.current.displayName == 'ansible_test' + - cm_add_label.current.id is not defined + - cm_add_label.current.type == 'site' + +- name: Add label (normal mode) + mso_label: *label_present + register: nm_add_label + +- name: Verify nm_add_label + assert: + that: + - nm_add_label is changed + - nm_add_label.previous == {} + - nm_add_label.current.displayName == 'ansible_test' + - nm_add_label.current.id is defined + - nm_add_label.current.type == 'site' + +- name: Add label again (check_mode) + mso_label: *label_present + check_mode: true + register: cm_add_label_again + +- name: Verify cm_add_label_again + assert: + that: + - cm_add_label_again is not changed + - cm_add_label_again.previous.displayName == 'ansible_test' + - cm_add_label_again.previous.type == 'site' + - cm_add_label_again.current.displayName == 'ansible_test' + - cm_add_label_again.current.id == nm_add_label.current.id + - cm_add_label_again.current.type == 'site' + +- name: Add label again (normal mode) + mso_label: *label_present + register: nm_add_label_again + +- name: Verify nm_add_label_again + assert: + that: + - nm_add_label_again is not changed + - nm_add_label_again.previous.displayName == 'ansible_test' + - nm_add_label_again.previous.type == 'site' + - nm_add_label_again.current.displayName == 'ansible_test' + - nm_add_label_again.current.id == nm_add_label.current.id + - nm_add_label_again.current.type == 'site' + + +# CHANGE LABEL +# - name: Change label (check_mode) +# mso_label: +# <<: *label_present +# label_id: '{{ nm_add_label.current.id }}' +# label: ansible_test2 +# check_mode: true +# register: cm_change_label + +# - name: Verify cm_change_label +# assert: +# that: +# - cm_change_label is changed +# - cm_change_label.current.displayName == 'ansible_test2' +# - cm_change_label.current.id == nm_add_label.current.id +# - cm_change_label.current.type == 'site' + +# - name: Change label (normal mode) +# mso_label: +# <<: *label_present +# label_id: '{{ nm_add_label.current.id }}' +# label: ansible_test2 +# output_level: debug +# register: nm_change_label + +# - name: Verify nm_change_label +# assert: +# that: +# - nm_change_label is changed +# - cm_change_label.current.displayName == 'ansible_test2' +# - nm_change_label.current.id == nm_add_label.current.id +# - nm_change_label.current.type == 'site' + +# - name: Change label again (check_mode) +# mso_label: +# <<: *label_present +# label_id: '{{ nm_add_label.current.id }}' +# label: ansible_test2 +# check_mode: true +# register: cm_change_label_again + +# - name: Verify cm_change_label_again +# assert: +# that: +# - cm_change_label_again is not changed +# - cm_change_label_again.current.displayName == 'ansible_test2' +# - cm_change_label_again.current.id == nm_add_label.current.id +# - cm_change_label_again.current.type == 'site' + +# - name: Change label again (normal mode) +# mso_label: +# <<: *label_present +# label_id: '{{ nm_add_label.current.id }}' +# label: ansible_test2 +# register: nm_change_label_again + +# - name: Verify nm_change_label_again +# assert: +# that: +# - nm_change_label_again is not changed +# - nm_change_label_again.current.displayName == 'ansible_test2' +# - nm_change_label_again.current.id == nm_add_label.current.id +# - nm_change_label_again.current.type == 'site' + + +# QUERY ALL LABELS +- name: Query all labels (check_mode) + mso_label: &label_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_all_labels + +- name: Query all labels (normal mode) + mso_label: *label_query + register: nm_query_all_labels + +- name: Verify query_all_labels + assert: + that: + - cm_query_all_labels is not changed + - nm_query_all_labels is not changed + # NOTE: Order of labels is not stable between calls + # FIXME: + #- cm_query_all_labels == nm_query_all_labels + + +# QUERY A LABEL +- name: Query our label (check mode) + mso_label: + <<: *label_query + label: ansible_test + check_mode: true + register: cm_query_label + +- name: Query our label (normal mode) + mso_label: + <<: *label_query + label: ansible_test + register: nm_query_label + +- name: Verify query_label + assert: + that: + - cm_query_label is not changed + - cm_query_label.current.displayName == 'ansible_test' + - cm_query_label.current.id == nm_add_label.current.id + - cm_query_label.current.type == 'site' + - nm_query_label is not changed + - nm_query_label.current.displayName == 'ansible_test' + - nm_query_label.current.id == nm_add_label.current.id + - nm_query_label.current.type == 'site' + - cm_query_label == nm_query_label + + +# REMOVE LABEL +- name: Remove label (check_mode) + mso_label: *label_absent + check_mode: true + register: cm_remove_label + +- name: Verify cm_remove_label + assert: + that: + - cm_remove_label is changed + - cm_remove_label.current == {} + +- name: Remove label (normal mode) + mso_label: *label_absent + register: nm_remove_label + +- name: Verify nm_remove_label + assert: + that: + - nm_remove_label is changed + - nm_remove_label.current == {} + +- name: Remove label again (check_mode) + mso_label: *label_absent + check_mode: true + register: cm_remove_label_again + +- name: Verify cm_remove_label_again + assert: + that: + - cm_remove_label_again is not changed + - cm_remove_label_again.current == {} + +- name: Remove label again (normal mode) + mso_label: *label_absent + register: nm_remove_label_again + +- name: Verify nm_remove_label_again + assert: + that: + - nm_remove_label_again is not changed + - nm_remove_label_again.current == {} + + +# QUERY NON-EXISTING LABEL +- name: Query non-existing label (check_mode) + mso_label: + <<: *label_query + label: ansible_test + check_mode: true + register: cm_query_non_label + +- name: Query non-existing label (normal mode) + mso_label: + <<: *label_query + label: ansible_test + register: nm_query_non_label + +# TODO: Implement more tests +- name: Verify query_non_label + assert: + that: + - cm_query_non_label is not changed + - nm_query_non_label is not changed + - cm_query_non_label == nm_query_non_label + +# add label with login domain +- name: Add label local domain(normal mode) + mso_label: &domain_label_present + <<: *mso_info + state: present + label: ansible_test3 + login_domain: Local + register: label_local_domain + +- name: Verify label_local_domain + assert: + that: + - label_local_domain is changed + - label_local_domain.current.displayName == 'ansible_test3' + - label_local_domain.current.type == 'site' + +- name: Add label test domain(normal mode) + mso_label: + <<: *domain_label_present + label: ansible_test4 + login_domain: '{{ mso_login_domain | default("test") }}' + register: label_test_domain + +- name: Verify label_test_domain + assert: + that: + - label_test_domain is changed + - label_test_domain.current.displayName == 'ansible_test4' + - label_test_domain.current.type == 'site' diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa new file mode 100644 index 000000000..1c3cede15 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG5QIBAAKCAYEAn/kFOjXlF4NV5aO/V0EQV1Z0Wqnss5fLpQ/fbBHhq98aalpJ +v9tSnfjlBDOp9n/MvxudqWRQPNMfvQkUyxcN1NbSeiy3QMX+iWjFt/C9q9oij5m4 +c8dk+oJ9qfha65EFVrlUGPc3+ydEIofnsLJbIIwWqNMlPKqjqhC/iMWW7BOkXliR +Go2GCYLYBdA00W59gI4xkbsc4Q0Br6vTLIX+BzJHXuTl4SFZFFvCPYSjW+j8twhQ +2NUjaVblOLZtWbEVyb5hlKJ73alS/fndIJKH1I31qGXqSwvnYQwpLx2ufPTMTVBM +q4l+0qfgIiv6SJVwiMVHxG6MP/fWUATJNE0DFjFGX61qJAqAuoa/PyOM6N01/dF9 +5jvljd5Wafut3JGGxbJ4f8T4fAeEvi3EX+axlgjVtbUjuqeyC03YpS/iMu8A6P34 +/dENbMHu1Xh6UjlTaqREb7KZl/Nsp0+vLFUrFr/8QHhHs19C54WtQsEwD73/pqo5 +Pn1/gTIRux2w23e5AgMBAAECggGAUBjxQxolIMbDxX1dmqSbN/+ztomKWMnST01J +QuUZJ2NH6KRYdNWt4ibzFE2B9kg7Dh0Xre7qNepH4/CeFqnuZPlC3aVyA96e+dIZ +3WWOsnNABsKjFmVp6/xWSzps27H7CFc3AmEWCIy6kseVfGVxNzStS86cwGl4FPjZ +zfORA5c6H3sc/DyMNkrrOs3rBEncUPfhXeRgK1bF112jGJHmhVfpYFwftb9qyMTA +1uiImsZncoWZZVgiqOW3U9QToGsHgRYhg73hQuPHhmQ869z/O5pkQ2rcP5XpC0aP +uUGLRQ/U51v5QQ05uAa9a4rejaQr6Sp2UteWr0QkKcZzLAUNstEqOHcRnXedI6py +Ls5kABZxPJhdMxHtrCUwX0XIKMIiKpGxE+vZMZQIPbD8jJE56nrFkQ3d/CA0E4fw +LALavs6x6o3f7LMUQpBCWoVQy9D9udVqx+5+TgbHhpnG9W3J957Y2g1NX/xVCIfU +KzPzmRH/7Bv1FFSWpkR7GrxtlMABAoHBAM0fNBQ0pGUFFn8mQVT1gdpASiqDZNJB +VVFQy9SaBuWIxJcJycJ/ILOhkAi2qYcqBBtS3WdTH1oplccgkFn0YijxnaE7T1rw +K29+J9pQXBQsNFWoaOhlzMNU1taXjIEhnDzTJWDPy7XNpKWsWucZH3CM5lWZEmZ4 +6Rr+MCu1BOAdiC7klGr9K91WAYe3mp9IShloWrQSJYLRtEgew+wR/kiDPfwQwR7C +siIEzWGO3DOsypIJQdJ6S0sKpVBL7jIcAQKBwQDHpvGCPmaDsi6ndXr/MqYDlxVg +Nu/Nr75xaQzvx3oqiPViet7d8WWN2ZFJd3vI70Xwmoj3gB2okpscgfJGxTZ3xCcN +ywKsshWLYkt1nAMMMC1OZiN1xL2LLos6R9ioe6R4pXChIIr1hmzycKMFA6HAdt0b +Bgcr2Odl5V9D5BIAkCbBD089WJfylz/mgqNuVh3oL2ECEJdPbpmjecXzQVqq78z5 +UjNj7qU8bGnVjkU3STr2TBuKdO1h5XFimaYxO7kCgcEAxKii9LBX4OaU4AjcYEkV +WxuCP+pDknXDB7gwBEA8VnrfCHQA9TGPN8mxXzlJpeY5k7zJutNt3rK5//UPkL8G +EX09BKTpeyWCb12DdgLPlSOgdXOGSTG4tJm1dH5N3kxMD+DcGEqBY2eq8JAjgyeK +Bg2AlBazFn3b9942buEZsIl/1H2gckcSdB2OUAFPBGF5cYykUbqILjlB4FdmvgGu +SvVRS0cA8K33vTffdSZTplOGz6aCbfqED4lAX5C86VwBAoHBAIbcePSejAbXnHYX +gE7T+pogOxsz4MZSuVTIPinV1+rVetPb5aGMBypLVb2HjUEMh3TgHjb4/o+5ADfA +e1RcsM8z26GQiSz4Wl89tXUrPk/EV0ZG7hsGG3bhqMBkebBNXKr2Ld9ZKSRyejNF +7IhdjKyCXhZ7+uoeaShGSRSGAbcJqHPukHsC1hjTHCHsCtNkLm2BW4jWhi7sqbFo +d1M6yTEALLgZU4dkU48+ODs+D/kpaT+n506ebx8aqn2NBlrpWQKBwQCtCCntlu2U +p0ZcBjXnliGJpxfEg1w6R/dj3w1Sju0M45UGqIoLFBmNpFSadBWE2JPBxuwUgqf/ +/eJoNUl6aIAr8NGF8EWvu16Hxg0Qx1vJkeBO9/EwoHZ8PmwXFzM2dW+P4yXmaDMI +Q+rvn7gI1UJk/1MxhfXpEWTxIyPTDhvHDb4iC6OPxt8+qNO/MRHCUyQ4grUOkeyQ +eSPW4pVejih5Kd+k5uFah8+PyoL7csVrt3RaOZiOCa7qq6L0CRmvXeI= +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase new file mode 100644 index 000000000..fce397fb2 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase @@ -0,0 +1,42 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,234BAAD9A19386249918BB2C07874AD1 + +CUqdVbGGnu9XRtHfdVzZ0SvLGaEgkDTvBk5kNnkkSHXjlJTdpjzccD7K8sWKfSR+ +LpuTf4yGfkAQMFZBvTbrMuFYrkARxNh65i6U8YTp2punE0LzgI59ykOquz9XJKMz +c4fo4xQLqnEf3+XypwDLCrSaWLcWJ/5GDjDL0B+GexomOhj+07D3QPALtmj/4qWq +QC3Q3xb8hMQJelyC39Z4I3Gb2VIMxzvZi6rOeLtXd7OdrP5ZWT43XIC8U1Uo+Pp7 +g58XXDS1JmYDbVUqgVE73dvEfM3vtDmS3XpP8+8Wvm2ld5Ky55G+eG3xE2iellLT +q2THpzDvgQcvxIL+/E+kazCMv44aWuqeo2+BIRa2yKPicc9SXMyDibmvF5MjSiVb +/FdWd2pYzUl1z2lkhMv23H53o6Yu+3y+aOfyWPSSJHObZ+CNblKrnIP6Vj/oZ6XI +uwEqe9bwUwakrLUybgthFxw77fLr5k8miQk58sdp7fPl4l+6MRO9by+gXKD6SEmk +LK5OiqztiMHJrRb79WarCgiqelIXL4jz6dM/vNVZLcgqNLVAmxzMHm/p0bK3W2cU ++LQYeW51d9laQ1QeXY57jIaOMQhkoHqnkUqifrIWIzxdcl+lCtn3+MFZAXVI0tik +q9LqDVbDoxn0SuF1EELHx6AUFNJU5Rn/56DMYFUlWWXs22uKq7g1ci8NShSX1S2J +s/HkLFgHWE4IvvWmyyolRG5vh3VgUwdVdU4h+g8nkUaQm1OstbxmcfPOuYfsQpmY +jlhk/d4WZ3l3Et/Kjb4c25789xMfBgB6KReLapoE6nr7vg9oKfsFXIEiakwWRwHK +f/O9fiZZr55IDRX7herp3dYJqY5kINanl1s8FoFSoUc9opgY4ENg5FXer4RFLdWI +ZPAY9DPxXYY+MjGpEXrlA0W/kuTqNkUH3fMrX+0kEO0OCDDRcsUTJ8J//oRx//4O +UggbCK6DuZJLzlosRoabshkhCPHQxsujk5jeIZCI7Yae+UZCFXqgluXdFcRslFuR +BP1etL4qh3BFxmKpwq3dSwT3iqEd+kPK2HZXeWmy2E8XJhuL6+gc4ULohAKAjWWm +HrehCQjrQVG1dIOMuPFAXb2d4uhBZFIEf0yzs9HDNKlW2E0npr0yf7XHuI40E+mP +V70Wpb49Z1vhmtXp84k6Xu+QG9GPYceZG4jDaQupKunQbgaH1zSglExPLy1wAKIc +IX2nO8xXanMl6yx87eQtq+/Rvlrv8PyjbbLQlZt6TMKNWJlmH20OG5nA6E+L4sye +LGzkd/vA0b3jL0tNIXkteDT1oIS/OPW/7smWwNOeKbutdGPCxfuW52agLoxJwac+ +a3Z2G1TE9fIIJE9GnBjTp2IO5AZ/cUNsqkQhrPMb2F7w3lu+wBFV53KoJlkHpryJ +PHRP+can696UiauwHiq8C1ufcqnaNdktH2Gl1nFN5urDEkOLQ3bT+3Zpr9DJvhS7 +LekjXImMIsvjKgbEb16Hz9ZuQ2BPU1KNHv3KSrxR7f4RIA9KVoSsKd0NsqR47EVz +TyN1Aci2eu5ngeCNeQ2w8DYzx7ZS0tsL8xBTJa0dm10XmI1N+lLOYMZHeiBmaXXN +Rw5RRBqk22ulConecg44M78eLvWTxvNE5CQoO/fPRaaKu8f6zU/rig5AEsv+BPOO +HGEtRzpITF8icS+p8rHCd4YwiU+fJhtl77GA0zMcN/pPFavtW3nmL4jKtnOxq7Js +m5QNBp4dKllo70jT7/f6IlZD8CQibA1h34M4tn3NyTR7UI/2A0IsbyXEgriw0Pl+ +wKyPJ0vkkL8/0UoJ8Z+0QqrKZbH1a77myjDrLoGzlGzFmesPvmNIhWpPKEqnIJrx +JvEgxEET4Ry1/5dqovxmE20TMap4EX6LHuORJTqjCFtUn8y0m6+019EmW7Jvo7vs +uo/xMUXmbkAtQhYnN/bQkfV6L0QxwFAFHGDvC/8AzJdw146cjlZ1ReCWca6uEs1H +QoKwjIt4B+9WfNjCOLYe3iReDkTL/FW6Tkn15mYGSOql9Zza+sVvvxKYRbnyVh0g +gaRtundjZVtUPMkIYMSB8XEqdJ2pHJm1g+KJR+QsL21qeoWJ7D2MTqOY9ErDgh/c +mvrLWyajGVr6SsbwhOVfltCuR2bBeRwC75duqif3dnEWXipUhvTB/SR6u1m1SJ0p +IJfcPJI1KiRII4L0ihFk2WzXPQA3XbWVVWZ2wN9AqhtV6/e19xP2L1rgpfVwAf/i +Q/8mv5ACzo0V8UJRWgO4CA1yZqhUqUQHRUm6WjRJrGheQKdU3ZK9ZOAWY+yW/2La +QvCyfRIP8jW8EDic5m7VXWEE9vVxyLhTtH0BOgZNTfzCYhdhHeLXKehS9qqhmoy2 +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub new file mode 100644 index 000000000..0c9e393c5 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQColnrKInjNCJXLcAiEOLbTTiP9plzevk+VgMa++0vL7flNhbLJYFJMWgnZLYglUb3crJvR5grxn98iVrWcopLskk1p8yz1BR6Hal6Pv5ZFxKe1w487oQk2ShrV/vX9xC/86SHET0zjFrNNg8bjsYg/do+FzBDBuy/V5sQ6S427bK5h4z8/ld3VAbBopIEPLeuVg3z69aFhnMwNCSpbPjvCc2dJk1avUwLQj2Ol0FaWiYtE1ug9RAFVIWPpLYexkJh7l9OZR+BhgmspUq4w4QjPlhCGs85GpbQAo8fdoT+ukN4oOiLfhL62CAuCfaOEfO9//i7Kk7xQDpf0I5/qA2eGjve1AaTPjGRSk4l90tn5bt+655GIz62zUKSJKaT8Cn2gMbtNxXt1qNQqXxn4bnzVQjdfWKZpSIfTdrC7DgmWObWNMJQlhbaN4brvi4Z5mU3A0FukKDRgPCFykKvbVFtVhUWsBv+rBX0QToaCEwIYYvicLPBA2igYcVmx2NJAu0M= akinross@AKINROSS-M-M4WR diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub new file mode 100644 index 000000000..104b840e1 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCf+QU6NeUXg1Xlo79XQRBXVnRaqeyzl8ulD99sEeGr3xpqWkm/21Kd+OUEM6n2f8y/G52pZFA80x+9CRTLFw3U1tJ6LLdAxf6JaMW38L2r2iKPmbhzx2T6gn2p+FrrkQVWuVQY9zf7J0Qih+ewslsgjBao0yU8qqOqEL+IxZbsE6ReWJEajYYJgtgF0DTRbn2AjjGRuxzhDQGvq9Mshf4HMkde5OXhIVkUW8I9hKNb6Py3CFDY1SNpVuU4tm1ZsRXJvmGUonvdqVL9+d0gkofUjfWoZepLC+dhDCkvHa589MxNUEyriX7Sp+AiK/pIlXCIxUfEbow/99ZQBMk0TQMWMUZfrWokCoC6hr8/I4zo3TX90X3mO+WN3lZp+63ckYbFsnh/xPh8B4S+LcRf5rGWCNW1tSO6p7ILTdilL+Iy7wDo/fj90Q1swe7VeHpSOVNqpERvspmX82ynT68sVSsWv/xAeEezX0Lnha1CwTAPvf+mqjk+fX+BMhG7HbDbd7k= akinross@AKINROSS-M-M4WR diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml new file mode 100644 index 000000000..aa543f959 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml @@ -0,0 +1,303 @@ +# Test code for the MSO modules +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT + +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query all backups + cisco.mso.mso_backup: + <<: *mso_info + state: query + delegate_to: localhost + register: backups + +- name: Ensure all backups with link to remote location ansible_test are removed + cisco.mso.mso_backup: + <<: *mso_info + backup_id: "{{ item.id }}" + state: absent + when: + - item.location is defined + - item.location.locationType is defined + - item.location.locationType == "remote" + loop: "{{ backups.current | sort(attribute='name', reverse=True) }}" + delegate_to: localhost + +- name: Ensure remote location ansible_test is removed + cisco.mso.mso_remote_location: + <<: *mso_info + remote_location: ansible_test + state: absent + delegate_to: localhost + +- name: Configure remote location scp (check mode) + cisco.mso.mso_remote_location: &remote_location + <<: *mso_info + remote_location: ansible_test + remote_protocol: scp + remote_host: '{{ mso_remote_location }}' + remote_path: '{{ mso_remote_location_path | default("/tmp") }}' + authentication_type: password + remote_username: '{{ mso_remote_location_user | default(mso_username) }}' + remote_password: '{{ mso_remote_location_password | default(mso_password) }}' + state: present + check_mode: true + delegate_to: localhost + register: cm_config_remote + +- name: Configure remote location scp + cisco.mso.mso_remote_location: + <<: *remote_location + delegate_to: localhost + register: nm_config_remote + +- name: Verify configuration + assert: + that: + - cm_config_remote is changed + - cm_config_remote.current.name == "ansible_test" + - cm_config_remote.current.credential.authType == "password" + - cm_config_remote.current.credential.port == 22 + - cm_config_remote.current.credential.protocolType == "scp" + - cm_config_remote.current.credential.hostname == '{{ mso_remote_location }}' + - cm_config_remote.current.credential.remotePath == '/tmp' + - cm_config_remote.current.credential.username == '{{ mso_remote_location_user | default(mso_username) }}' + - nm_config_remote is changed + - nm_config_remote.current.name == "ansible_test" + - nm_config_remote.current.credential.authType == "password" + - nm_config_remote.current.credential.port == 22 + - nm_config_remote.current.credential.protocolType == "scp" + - nm_config_remote.current.credential.hostname == '{{ mso_remote_location }}' + - nm_config_remote.current.credential.remotePath == '/tmp' + - nm_config_remote.current.credential.username == '{{ mso_remote_location_user | default(mso_username) }}' + - nm_config_remote.current.id is defined + +- name: Configure remote location again + cisco.mso.mso_remote_location: + <<: *remote_location + delegate_to: localhost + register: nm_config_remote_again + +- name: Verify configuration after again + assert: + that: + - nm_config_remote_again is not changed + +- name: Change remote location description (check mode) + cisco.mso.mso_remote_location: + <<: *remote_location + description: changed_description + check_mode: true + delegate_to: localhost + register: cm_change_config_remote_description + +- name: Change remote location description + cisco.mso.mso_remote_location: + <<: *remote_location + description: changed_description + delegate_to: localhost + register: nm_change_config_remote_description + +- name: Verify configuration change + assert: + that: + - cm_change_config_remote_description is changed + - cm_change_config_remote_description.current.description == "changed_description" + - nm_change_config_remote_description is changed + - nm_change_config_remote_description.current.description == "changed_description" + +- name: Query remote location + cisco.mso.mso_remote_location: + <<: *remote_location + state: query + delegate_to: localhost + register: nm_query_remote + +- name: Query all remote locations + cisco.mso.mso_remote_location: + <<: *mso_info + state: query + delegate_to: localhost + register: nm_query_all_remotes + +- name: Query non existing remote location + cisco.mso.mso_remote_location: + <<: *mso_info + remote_location: non_existing + state: query + ignore_errors: true + delegate_to: localhost + register: nm_query_non_existing + +- name: Verify queries + assert: + that: + - nm_query_remote is not changed + - nm_query_remote.current | type_debug == "dict" + - nm_query_all_remotes is not changed + - nm_query_all_remotes.current | type_debug == "list" + - nm_query_non_existing is not changed + - 'nm_query_non_existing.msg == "Remote location non_existing not found. Remote locations configured: ansible_test"' + +- name: Remove remote location (check mode) + cisco.mso.mso_remote_location: + <<: *remote_location + state: absent + check_mode: true + delegate_to: localhost + register: cm_delete_config_remote + +- name: Remove remote location + cisco.mso.mso_remote_location: + <<: *remote_location + state: absent + delegate_to: localhost + register: nm_delete_config_remote + +- name: Verify delete + assert: + that: + - cm_delete_config_remote is changed + - cm_delete_config_remote.current == {} + - nm_delete_config_remote is changed + - nm_delete_config_remote.current == {} + +- name: Create remote location different path directory if it does not exist + ansible.builtin.file: + path: '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}' + state: directory + mode: '0755' + +- name: Configure remote location different path (check mode) + cisco.mso.mso_remote_location: + <<: *remote_location + remote_path: '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}' + check_mode: true + delegate_to: localhost + register: cm_config_remote_different_path + +- name: Configure remote location different path + cisco.mso.mso_remote_location: + <<: *remote_location + remote_path: '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}' + delegate_to: localhost + register: nm_config_remote_different_path + +- name: Verify configuration different path + assert: + that: + - cm_config_remote_different_path is changed + - cm_config_remote_different_path.current.credential.remotePath == '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}' + - nm_config_remote_different_path is changed + - nm_config_remote_different_path.current.credential.remotePath == '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}' + +- name: Remove remote location + cisco.mso.mso_remote_location: + <<: *remote_location + state: absent + delegate_to: localhost + +- name: Configure remote location sftp (check mode) + cisco.mso.mso_remote_location: + <<: *remote_location + remote_protocol: sftp + check_mode: true + delegate_to: localhost + register: cm_config_remote_sftp + +- name: Configure remote location sftp + cisco.mso.mso_remote_location: + <<: *remote_location + remote_protocol: sftp + delegate_to: localhost + register: nm_config_remote_sftp + +- name: Verify configuration sftp + assert: + that: + - cm_config_remote_sftp is changed + - cm_config_remote_sftp.current.credential.protocolType == "sftp" + - nm_config_remote_sftp is changed + - nm_config_remote_sftp.current.credential.protocolType == "sftp" + +- name: Remove remote location + cisco.mso.mso_remote_location: + <<: *remote_location + state: absent + delegate_to: localhost + +- name: Configure remote location ssh (check mode) + cisco.mso.mso_remote_location: &remote_location_ssh + <<: *remote_location + authentication_type: ssh + remote_ssh_key: "{{ lookup('file', 'pki/rsa') }}" + check_mode: true + delegate_to: localhost + register: cm_config_remote_ssh + +- name: Configure remote location ssh + cisco.mso.mso_remote_location: + <<: *remote_location_ssh + delegate_to: localhost + register: nm_config_remote_ssh + +- name: Verify configuration ssh + assert: + that: + - cm_config_remote_ssh is changed + - cm_config_remote_ssh.current.credential.authType == "sshKey" + - nm_config_remote_ssh is changed + - nm_config_remote_ssh.current.credential.authType == "sshKey" + +- name: Remove remote location + cisco.mso.mso_remote_location: + <<: *remote_location_ssh + state: absent + delegate_to: localhost + +- name: Configure remote location ssh with passphrase (check mode) + cisco.mso.mso_remote_location: &remote_location_ssh_pass + <<: *remote_location + authentication_type: ssh + remote_ssh_key: "{{ lookup('file', 'pki/rsa-passphrase') }}" + remote_ssh_passphrase: '{{ mso_output_level | default("ansible") }}' + check_mode: true + delegate_to: localhost + register: cm_config_remote_ssh_pass + +- name: Configure remote location ssh with passphrase + cisco.mso.mso_remote_location: + <<: *remote_location_ssh_pass + delegate_to: localhost + register: nm_config_remote_ssh_pass + +- name: Verify configuration ssh + assert: + that: + - cm_config_remote_ssh_pass is changed + - cm_config_remote_ssh_pass.current.credential.authType == "sshKey" + - nm_config_remote_ssh_pass is changed + - nm_config_remote_ssh_pass.current.credential.authType == "sshKey" + +- name: Remove remote location + cisco.mso.mso_remote_location: + <<: *remote_location_ssh_pass + state: absent + delegate_to: localhost
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml new file mode 100644 index 000000000..63a241918 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml @@ -0,0 +1,153 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# SET VARs +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +# PROVOKE ERRORS +- name: Error when required parameter is missing + cisco.mso.mso_rest: + <<: *mso_info + output_level: debug + method: post + content: + displayName: mso_tenant + name: mso_tenant + description: MSO tenant + siteAssociations: [] + userAssociations: [] + _updateVersion: 0 + ignore_errors: true + register: error_on_missing_required_param + +- name: Verify error_on_missing_required_param + assert: + that: + - error_on_missing_required_param is failed + - 'error_on_missing_required_param.msg == "missing required arguments: path"' + +- name: Error on name resolution + cisco.mso.mso_rest: + host: foo.bar.cisco.com + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + path: /mso/api/v1/tenants + method: post + content: + fvFoobar: + displayName: mso_tenant + name: mso_tenant + description: This is description + siteAssociations: [] + userAssociations: [] + _updateVersion: 0 + ignore_errors: true + register: error_on_name_resolution + +- name: Verify error_on_name_resolution + assert: + that: + - error_on_name_resolution is failed + +- name: Verify error_on_name_resolution + assert: + that: + - error_on_name_resolution.msg is search("Name or service not known") + when: + - version.current.version is version('3.7', '>=') + +- name: Error on invalid path + mso_rest: + <<: *mso_info + path: /mso/api/v1/tenant + method: post + content: + displayName: mso_tenant + name: mso_tenant + description: MSO tenant + siteAssociations: [] + userAssociations: [] + _updateVersion: 0 + ignore_errors: true + register: error_on_invalid_path + +- name: Verify error_on_invalid_path + assert: + that: + - error_on_invalid_path is failed + - error_on_invalid_path.status == 404 + when: version.current.version is version('3.0.0a', '<') or version.current.version is version('3.2', '>=') + +- name: Verify error_on_invalid_path + assert: + that: + - error_on_invalid_path is failed + - error_on_invalid_path.status == 405 + when: + - version.current.version is version('3.0.0a', '>=') + - version.current.version is version('3.2', '<') + +- name: Error when attributes are missing + cisco.mso.mso_rest: + <<: *mso_info + path: /mso/api/v1/tenants + method: post + content: + children: + ignore_errors: true + register: error_on_missing_attributes + +- name: Verify error_on_missing_attributes + assert: + that: + - error_on_missing_attributes is failed + - error_on_missing_attributes.status == 400 + +- name: Error when input does not validate + cisco.mso.mso_rest: + <<: *mso_info + path: /mso/api/v1/tenants + method: post + content: + displayName: 0 + name: 0 + descr: This is an [invalid] description + siteAssociations: [] + userAssociations: [] + _updateVersion: 0 + ignore_errors: true + register: error_on_input_validation + +- name: Verify error_on_input_validation + assert: + that: + - error_on_input_validation is failed + - error_on_input_validation.status == 400 diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml new file mode 100644 index 000000000..252733c29 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml @@ -0,0 +1,289 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove EXT_EPGs Providers from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_endpoint_group: "{{ item }}" + state: absent + ignore_errors: true + loop: + - EXT_EPG_1 + - EXT_EPG_2 + +- name: Remove EXT_EPGs Providers from DHCP Relay Policy + mso_dhcp_relay_policy_provider: + <<: *mso_info + dhcp_relay_policy: ansible_dhcp_relay_1 + tenant: ansible_test + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + endpoint_group: "{{ item }}" + application_profile: "ANP_1" + state: absent + ignore_errors: true + loop: + - EPG_1 + - EPG_2 + +- name: Stop consuming DHCP Policy + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: CLIENT_BD + vrf: + name: VRF1 + state: absent + ignore_errors: true + +- name: Remove DHCP Relay Policies + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + state: absent + ignore_errors: true + loop: + - ansible_dhcp_relay_1 + - ansible_dhcp_relay_2 + +- name: Remove DHCP Option Policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + state: absent + ignore_errors: true + loop: + - ansible_dhcp_option_1 + - ansible_dhcp_option_2 + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + ignore_errors: true + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Remove tenant ansible_test + mso_tenant: + <<: *mso_info + tenant: ansible_test + state: absent + ignore_errors: true + +# QUERY SCHEMAS +- name: Query schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: get + register: query_all_schema + +- name: Verify query_all_schema in json_inline + assert: + that: + - query_all_schema is not changed + +# QUERY A USER +- name: Query our user + mso_user: + <<: *mso_info + state: query + user: '{{ mso_username }}' + check_mode: true + register: query_user_id + +- name: Verify query_user_id + assert: + that: + - query_user_id is not changed + - query_user_id.current.username == '{{ mso_username }}' + +# ADD tenant +- name: Add tenant + mso_rest: + <<: *mso_info + path: /api/v1/tenants + method: post + content: + { + "displayName": "ansible_test", + "name": "ansible_test", + "description": "", + "siteAssociations": [], + "userAssociations": [{ + "userId": "{{ query_user_id.current.id }}" + }], + "_updateVersion": 0, + } + register: add_tenant + +- name: Verify add_tenant in json_inline + assert: + that: + - add_tenant is changed + - add_tenant.jsondata.displayName == 'ansible_test' + +# ADD schema +- name: Add schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: post + content: + { + "displayName": "{{ mso_schema | default('ansible_test') }}", + "templates": [{ + "name": "Template_1", + "tenantId": "{{ add_tenant.jsondata.id }}", + "displayName": "Template_1", + "templateSubType": [], + "templateType": "stretched-template", + "anps": [], + "contracts": [], + "vrfs": [], + "bds": [], + "filters": [], + "externalEpgs": [], + "serviceGraphs": [], + "intersiteL3outs": [] + }], + "sites": [], + "_updateVersion": 0 + } + register: add_schema + +- name: Verify add_schema in json_inline + assert: + that: + - add_schema is changed + - add_schema.jsondata.displayName == 'ansible_test' + +# PUT schema +- name: Put schema + mso_rest: + <<: *mso_info + port: 443 + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: put + content: + { + "displayName": "ansible_test_2", + "templates": [{ + "name": "Template_1", + "tenantId": "{{ add_tenant.jsondata.id }}", + "displayName": "Template_1", + "templateSubType": [], + "templateType": "stretched-template", + "anps": [], + "contracts": [], + "vrfs": [], + "bds": [], + "filters": [], + "externalEpgs": [], + "serviceGraphs": [], + "intersiteL3outs": [] + }], + "sites": [], + "_updateVersion": 0 + } + register: put_schema + +- name: Verify put_schema in json_inline + assert: + that: + - put_schema is changed + - put_schema.jsondata.displayName == 'ansible_test_2' + +# PATCH schema +- name: Patch schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: patch + content: + [ + { + "op": "add", + "path": "/templates/Template_1/anps/-", + "value": { "name": "AP2", "displayName": "AP2", "epgs": [] }, + "_updateVersion": 0 + } + ] + register: patch_schema + +- name: Verify patch_schema in json_inline + assert: + that: + - patch_schema is changed + +- name: Verify patch_schema in json_inline + assert: + that: + - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2' + # MSO 3.3 PATCH does not return anything anymore. + when: version.current.version is version('3.3', '<') + +# DELETE the schema +- name: Delete the schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: delete + register: delete_schema + +- name: Verify delete_schema in json_inline + assert: + that: + - delete_schema is changed + - delete_schema.jsondata == None + +# DELETE TENANT +- name: Delete the tenant + mso_rest: + <<: *mso_info + path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}" + method: delete + register: delete_tenant + +- name: Verify delete_tenant in json_inline + assert: + that: + - delete_tenant is changed + - delete_tenant.jsondata == None
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml new file mode 100644 index 000000000..9a9df1ea7 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml @@ -0,0 +1,224 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove tenant ansible_test + mso_tenant: + <<: *mso_info + tenant: ansible_test + state: absent + +# QUERY SCHEMAS +- name: Query schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: get + register: query_all_schema + +- name: Verify query_all_schema + assert: + that: + - query_all_schema is not changed + +# QUERY A USER +- name: Query our user + mso_user: + <<: *mso_info + state: query + user: '{{ mso_username }}' + check_mode: true + register: query_user_id + +- name: Verify query_user_id + assert: + that: + - query_user_id is not changed + - query_user_id.current.username == '{{ mso_username }}' + +# ADD tenant +- name: Add tenant + mso_rest: + <<: *mso_info + path: /api/v1/tenants + method: post + content: + { + "displayName": "ansible_test", + "name": "ansible_test", + "description": "", + "siteAssociations": [], + "userAssociations": [{ + "userId": "{{ query_user_id.current.id }}" + }], + "_updateVersion": 0, + } + register: add_tenant + +- name: Verify add_tenant in json_string + assert: + that: + - add_tenant is changed + - add_tenant.jsondata.displayName == 'ansible_test' + +# ADD schema +- name: Add schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: post + content: | + { + "displayName": "{{ mso_schema | default('ansible_test') }}", + "templates": [{ + "name": "Template_1", + "tenantId": "{{ add_tenant.jsondata.id }}", + "displayName": "Template_1", + "templateSubType": [], + "templateType": "stretched-template", + "anps": [], + "contracts": [], + "vrfs": [], + "bds": [], + "filters": [], + "externalEpgs": [], + "serviceGraphs": [], + "intersiteL3outs": [] + }], + "sites": [], + "_updateVersion": 0 + } + register: add_schema + +- name: Verify add_schema in json_string + assert: + that: + - add_schema is changed + - add_schema.jsondata.displayName == 'ansible_test' + +# PUT schema +- name: Put schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: put + content: | + { + "displayName": "ansible_test_2", + "templates": [{ + "name": "Template_1", + "tenantId": "{{ add_tenant.jsondata.id }}", + "displayName": "Template_1", + "templateSubType": [], + "templateType": "stretched-template", + "anps": [], + "contracts": [], + "vrfs": [], + "bds": [], + "filters": [], + "externalEpgs": [], + "serviceGraphs": [], + "intersiteL3outs": [] + }], + "sites": [], + "_updateVersion": 0 + } + register: put_schema + +- name: Verify put_schema in json_string + assert: + that: + - put_schema is changed + - put_schema.jsondata.displayName == 'ansible_test_2' + +# PATCH schema +- name: Patch schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: patch + content: | + [ + { + "op": "add", + "path": "/templates/Template_1/anps/-", + "value": { "name": "AP2", "displayName": "AP2", "epgs": [] }, + "_updateVersion": 0 + } + ] + register: patch_schema + +- name: Verify patch_schema in json_string + assert: + that: + - patch_schema is changed + +- name: Verify patch_schema in json_string + assert: + that: + - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2' + # MSO 3.3 PATCH does not return anything anymore. + when: version.current.version is version('3.3', '<') + +# DELETE the schema +- name: Delete the schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: delete + register: delete_schema + +- name: Verify delete_schema in json_string + assert: + that: + - delete_schema is changed + - delete_schema.jsondata == None + +# DELETE TENANT +- name: Delete the tenant + mso_rest: + <<: *mso_info + path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}" + method: delete + register: delete_tenant + +- name: Verify delete_tenant in json_string + assert: + that: + - delete_tenant is changed + - delete_tenant.jsondata == None
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml new file mode 100644 index 000000000..622b3d21d --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml @@ -0,0 +1,67 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_1' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove tenant ansible_test + mso_tenant: + <<: *mso_info + tenant: ansible_test + state: absent + +# QUERY A USER +- name: Query our user + mso_user: + <<: *mso_info + state: query + user: '{{ mso_username }}' + check_mode: true + register: query_user_id + +- name: Verify query_user_id + assert: + that: + - query_user_id is not changed + - query_user_id.current.username == '{{ mso_username }}' + +- name: Add a tenant from a templated payload file from templates + mso_rest: + <<: *mso_info + path: /api/v1/tenants + method: post + content: "{{ lookup('template', 'tenant.json.j2') }}" + register: add_tenant + +- name: Verify add_tenant in json_string + assert: + that: + - add_tenant is changed + - add_tenant.jsondata.displayName == 'ansible_test'
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml new file mode 100644 index 000000000..22851bf72 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml @@ -0,0 +1,28 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +- include_tasks: json_inline.yml + tags: json_inline + +- include_tasks: json_string.yml + tags: json_string + +- include_tasks: json_template.yml + tags: json_template + +- include_tasks: yaml_inline.yml + tags: yaml_inline + +- include_tasks: yaml_string.yml + tags: yaml_string + +- include_tasks: error_handling.yml + tags: error_handling diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j2 b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j2 new file mode 100644 index 000000000..2d91ee76c --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j2 @@ -0,0 +1,10 @@ +{ + "displayName": "ansible_test", + "name": "ansible_test", + "description": "", + "siteAssociations": [], + "userAssociations": [{ + "userId": "{{ query_user_id.current.id }}" + }], + "_updateVersion": 0, +}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml new file mode 100644 index 000000000..1fe44f283 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml @@ -0,0 +1,214 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove tenant ansible_test + mso_tenant: + <<: *mso_info + tenant: ansible_test + state: absent + +# QUERY SCHEMAS +- name: Query schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: get + register: query_all_schema + +- name: Verify query_all_schema + assert: + that: + - query_all_schema is not changed + +# QUERY A USER +- name: Query our user + mso_user: + <<: *mso_info + state: query + user: '{{ mso_username }}' + check_mode: true + register: query_user_id + +- name: Verify query_user_id + assert: + that: + - query_user_id is not changed + - query_user_id.current.username == '{{ mso_username }}' + +# ADD tenant +- name: Add tenant + mso_rest: + <<: *mso_info + path: /mso/api/v1/tenants + method: post + content: + displayName: ansible_test + name: ansible_test + description: MSO tenant + siteAssociations: [] + userAssociations: + - userId: '{{ query_user_id.current.id }}' + _updateVersion: 0 + register: add_tenant + +- name: Verify add_tenant in yaml_inline + assert: + that: + - add_tenant is changed + - add_tenant.jsondata.displayName == 'ansible_test' + +# ADD schema +- name: Add schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: post + content: + displayName: '{{ mso_schema | default("ansible_test") }}' + templates: + - name: Template_1 + tenantId: '{{ add_tenant.jsondata.id }}' + displayName: Template_1 + templateSubType: [] + templateType: stretched-template + anps: [] + contracts: [] + vrfs: [] + bds: [] + filters: [] + externalEpgs: [] + serviceGraphs: [] + intersiteL3outs: [] + sites: [] + _updateVersion: 0 + register: add_schema + +- name: Verify add_schema in yaml_inline + assert: + that: + - add_schema is changed + - add_schema.jsondata.displayName == 'ansible_test' + +# PUT schema +- name: Put schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: put + content: + displayName: ansible_test_2 + templates: + - name: Template_1 + tenantId: '{{ add_tenant.jsondata.id }}' + displayName: Template_1 + templateSubType: [] + templateType: stretched-template + anps: [] + contracts: [] + vrfs: [] + bds: [] + filters: [] + externalEpgs: [] + serviceGraphs: [] + intersiteL3outs: [] + sites: [] + _updateVersion: 0 + register: put_schema + +- name: Verify put_schema in yaml_inline + assert: + that: + - put_schema is changed + - put_schema.jsondata.displayName == 'ansible_test_2' + +# PATCH schema +- name: Patch schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: patch + content: + - op: add + path: /templates/Template_1/anps/- + value: + name: AP2 + displayName: AP2 + epgs: [] + _updateVersion: 0 + register: patch_schema + +- name: Verify patch_schema in yaml_inline + assert: + that: + - patch_schema is changed + +- name: Verify patch_schema in yaml_inline + assert: + that: + - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2' + # MSO 3.3 PATCH does not return anything anymore. + when: version.current.version is version('3.3', '<') + +# DELETE the schema +- name: Delete the schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: delete + register: delete_schema + +- name: Verify delete_schema in yaml_inline + assert: + that: + - delete_schema is changed + - delete_schema.jsondata == None + +# DELETE TENANT +- name: Delete the tenant + mso_rest: + <<: *mso_info + path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}" + method: delete + register: delete_tenant + +- name: Verify delete_tenant in yaml_inline + assert: + that: + - delete_tenant is changed + - delete_tenant.jsondata == None
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml new file mode 100644 index 000000000..5d9cfb05e --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml @@ -0,0 +1,214 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove tenant ansible_test + mso_tenant: + <<: *mso_info + tenant: ansible_test + state: absent + +# QUERY SCHEMAS +- name: Query schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: get + register: query_all_schema + +- name: Verify query_all_schema + assert: + that: + - query_all_schema is not changed + +# QUERY A USER +- name: Query our user + mso_user: + <<: *mso_info + state: query + user: '{{ mso_username }}' + check_mode: true + register: query_user_id + +- name: Verify query_user_id + assert: + that: + - query_user_id is not changed + - query_user_id.current.username == '{{ mso_username }}' + +# ADD tenant +- name: Add tenant + mso_rest: + <<: *mso_info + path: /mso/api/v1/tenants + method: post + content: + displayName: ansible_test + name: ansible_test + description: MSO tenant + siteAssociations: [] + userAssociations: + - userId: '{{ query_user_id.current.id }}' + _updateVersion: 0 + register: add_tenant + +- name: Verify add_tenant in yaml_string + assert: + that: + - add_tenant is changed + - add_tenant.jsondata.displayName == 'ansible_test' + +# ADD schema +- name: Add schema + mso_rest: + <<: *mso_info + path: /mso/api/v1/schemas + method: post + content: + displayName: '{{ mso_schema | default("ansible_test") }}' + templates: + - name: Template_1 + tenantId: '{{ add_tenant.jsondata.id }}' + displayName: Template_1 + templateSubType: [] + templateType: stretched-template + anps: [] + contracts: [] + vrfs: [] + bds: [] + filters: [] + externalEpgs: [] + serviceGraphs: [] + intersiteL3outs: [] + sites: [] + _updateVersion: 0 + register: add_schema + +- name: Verify add_schema in yaml_string + assert: + that: + - add_schema is changed + - add_schema.jsondata.displayName == 'ansible_test' + +# PUT schema +- name: Put schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: put + content: + displayName: ansible_test_2 + templates: + - name: Template_1 + tenantId: '{{ add_tenant.jsondata.id }}' + displayName: Template_1 + templateSubType: [] + templateType: stretched-template + anps: [] + contracts: [] + vrfs: [] + bds: [] + filters: [] + externalEpgs: [] + serviceGraphs: [] + intersiteL3outs: [] + sites: [] + _updateVersion: 0 + register: put_schema + +- name: Verify put_schema in yaml_string + assert: + that: + - put_schema is changed + - put_schema.jsondata.displayName == 'ansible_test_2' + +# PATCH schema +- name: Patch schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: patch + content: + - op: add + path: /templates/Template_1/anps/- + value: + name: AP2 + displayName: AP2 + epgs: [] + _updateVersion: 0 + register: patch_schema + +- name: Verify patch_schema in yaml_string + assert: + that: + - patch_schema is changed + +- name: Verify patch_schema in yaml_string + assert: + that: + - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2' + # MSO 3.3 PATCH does not return anything anymore. + when: version.current.version is version('3.3', '<') + +# DELETE the schema +- name: Delete the schema + mso_rest: + <<: *mso_info + path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}" + method: delete + register: delete_schema + +- name: Verify delete_schema in yaml_string + assert: + that: + - delete_schema is changed + - delete_schema.jsondata == None + +# DELETE TENANT +- name: Delete the tenant + mso_rest: + <<: *mso_info + path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}" + method: delete + register: delete_tenant + +- name: Verify delete_tenant in yaml_string + assert: + that: + - delete_tenant is changed + - delete_tenant.jsondata == None
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml new file mode 100644 index 000000000..a27a0e166 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml @@ -0,0 +1,44 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Set version vars + set_fact: + mso_rw: true + when: + - version.current.version is version('2.2.4', '<') + +- name: Import tasks if RW of role in this MSO version + import_tasks: role-rw.yml + when: mso_rw is defined + +- name: Import tasks if RO of role in this MSO version + import_tasks: role-ro.yml + when: + - mso_rw is not defined + - version.current.version is version('3.2', '<')
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml new file mode 100644 index 000000000..730202a9d --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml @@ -0,0 +1,88 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + + +# QUERY ALL ROLES +- name: Query all roles (check_mode) + mso_role: &role_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_all_roles + +- name: Query all roles (normal mode) + mso_role: *role_query + register: nm_query_all_roles + +- name: Verify query_all_roles + assert: + that: + - cm_query_all_roles is not changed + - nm_query_all_roles is not changed + # NOTE: Order of roles is not stable between calls + #- cm_query_all_roles == nm_query_all_roles + + +# QUERY A ROLE +- name: Query our role + mso_role: + <<: *role_query + role: powerUser + check_mode: true + register: cm_query_role + +- name: Query our role + mso_role: + <<: *role_query + role: powerUser + register: nm_query_role + +- name: Verify query_role + assert: + that: + - cm_query_role is not changed + - cm_query_role.current.description == 'Elevates this user to \"admin\"' + - cm_query_role.current.displayName == 'Power User' + - nm_query_role is not changed + - nm_query_role.current.description == 'Elevates this user to \"admin\"' + - nm_query_role.current.displayName == 'Power User' + - cm_query_role == nm_query_role + + +# QUERY NON-EXISTING ROLE +- name: Query non-existing role (check_mode) + mso_role: + <<: *role_query + role: non-existing-role + check_mode: true + register: cm_query_non_role + +- name: Query non-existing role (normal mode) + mso_role: + <<: *role_query + role: non-existing-role + register: nm_query_non_role + +# TODO: Implement more tests +- name: Verify query_non_role + assert: + that: + - cm_query_non_role is not changed + - nm_query_non_role is not changed + - cm_query_non_role == nm_query_non_role diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml new file mode 100644 index 000000000..82db67640 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml @@ -0,0 +1,275 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove role ansible_test + mso_role: &role_absent + <<: *mso_info + role: ansible_test + state: absent + +- name: Remove role ansible_test2 + mso_role: + <<: *role_absent + role: ansible_test2 + register: cm_remove_role + + +# ADD ROLE +- name: Add role (check_mode) + mso_role: &role_present + <<: *mso_info + role: ansible_test + description: Ansible test role + read_permissions: view-sites + write_permissions: manage-sites + state: present + check_mode: true + register: cm_add_role + +- name: Verify cm_add_role + assert: + that: + - cm_add_role is changed + - cm_add_role.previous == {} + - cm_add_role.current.description == 'Ansible test role' + - cm_add_role.current.displayName == 'ansible_test' + - cm_add_role.current.id is not defined + +- name: Add role (normal mode) + mso_role: *role_present + register: nm_add_role + +- name: Verify nm_add_role + assert: + that: + - nm_add_role is changed + - nm_add_role.previous == {} + - nm_add_role.current.description == 'Ansible test role' + - nm_add_role.current.displayName == 'ansible_test' + - nm_add_role.current.id is defined + +- name: Add role again (check_mode) + mso_role: *role_present + check_mode: true + register: cm_add_role_again + +- name: Verify cm_add_role_again + assert: + that: + - cm_add_role_again is not changed + - cm_add_role_again.previous.description == 'Ansible test role' + - cm_add_role_again.previous.displayName == 'ansible_test' + - cm_add_role_again.current.description == 'Ansible test role' + - cm_add_role_again.current.displayName == 'ansible_test' + - cm_add_role_again.current.id == nm_add_role.current.id + +- name: Add role again (normal mode) + mso_role: *role_present + register: nm_add_role_again + +- name: Verify nm_add_role_again + assert: + that: + - nm_add_role_again is not changed + - nm_add_role_again.previous.description == 'Ansible test role' + - nm_add_role_again.previous.displayName == 'ansible_test' + - nm_add_role_again.current.description == 'Ansible test role' + - nm_add_role_again.current.displayName == 'ansible_test' + - nm_add_role_again.current.id == nm_add_role.current.id + + +# CHANGE ROLE +- name: Change role (check_mode) + mso_role: + <<: *role_present + role: ansible_test + description: Ansible test role 2 + check_mode: true + register: cm_change_role + +- name: Verify cm_change_role + assert: + that: + - cm_change_role is changed + - cm_change_role.current.description == 'Ansible test role 2' + - cm_change_role.current.displayName == 'ansible_test' + - cm_change_role.current.id == nm_add_role.current.id + +- name: Change role (normal mode) + mso_role: + <<: *role_present + role: ansible_test + description: Ansible test role 2 + output_level: debug + register: nm_change_role + +- name: Verify nm_change_role + assert: + that: + - nm_change_role is changed + - nm_change_role.current.description == 'Ansible test role 2' + #- nm_change_role.current.displayName == 'ansible_test2' + - nm_change_role.current.id == nm_add_role.current.id + +- name: Change role again (check_mode) + mso_role: + <<: *role_present + role: ansible_test + description: Ansible test role 2 + check_mode: true + register: cm_change_role_again + +- name: Verify cm_change_role_again + assert: + that: + - cm_change_role_again is not changed + - cm_change_role_again.current.description == 'Ansible test role 2' + - cm_change_role_again.current.displayName == 'ansible_test' + - cm_change_role_again.current.id == nm_add_role.current.id + +- name: Change role again (normal mode) + mso_role: + <<: *role_present + role: ansible_test + description: Ansible test role 2 + register: nm_change_role_again + +- name: Verify nm_change_role_again + assert: + that: + - nm_change_role_again is not changed + - nm_change_role_again.current.description == 'Ansible test role 2' + - nm_change_role_again.current.displayName == 'ansible_test' + - nm_change_role_again.current.id == nm_add_role.current.id + + +# QUERY ALL ROLES +- name: Query all roles (check_mode) + mso_role: &role_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_all_roles + +- name: Query all roles (normal mode) + mso_role: *role_query + register: nm_query_all_roles + +- name: Verify query_all_roles + assert: + that: + - cm_query_all_roles is not changed + - nm_query_all_roles is not changed + # NOTE: Order of roles is not stable between calls + #- cm_query_all_roles == nm_query_all_roles + + +# QUERY A ROLE +- name: Query our role + mso_role: + <<: *role_query + role: ansible_test + check_mode: true + register: cm_query_role + +- name: Query our role + mso_role: + <<: *role_query + role: ansible_test + register: nm_query_role + +- name: Verify query_role + assert: + that: + - cm_query_role is not changed + - cm_query_role.current.description == 'Ansible test role 2' + - cm_query_role.current.displayName == 'ansible_test' + - cm_query_role.current.id == nm_add_role.current.id + - nm_query_role is not changed + - nm_query_role.current.description == 'Ansible test role 2' + - nm_query_role.current.displayName == 'ansible_test' + - nm_query_role.current.id == nm_add_role.current.id + - cm_query_role == nm_query_role + + +# REMOVE ROLE +- name: Remove role (check_mode) + mso_role: *role_absent + check_mode: true + register: cm_remove_role + +- name: Verify cm_remove_role + assert: + that: + - cm_remove_role is changed + - cm_remove_role.current == {} + +- name: Remove role (normal mode) + mso_role: *role_absent + register: nm_remove_role + +- name: Verify nm_remove_role + assert: + that: + - nm_remove_role is changed + - nm_remove_role.current == {} + +- name: Remove role again (check_mode) + mso_role: *role_absent + check_mode: true + register: cm_remove_role_again + +- name: Verify cm_remove_role_again + assert: + that: + - cm_remove_role_again is not changed + - cm_remove_role_again.current == {} + +- name: Remove role again (normal mode) + mso_role: *role_absent + register: nm_remove_role_again + +- name: Verify nm_remove_role_again + assert: + that: + - nm_remove_role_again is not changed + - nm_remove_role_again.current == {} + + +# QUERY NON-EXISTING ROLE +- name: Query non-existing role (check_mode) + mso_role: + <<: *role_query + role: non-existing-role + check_mode: true + register: cm_query_non_role + +- name: Query non-existing role (normal mode) + mso_role: + <<: *role_query + role: non-existing-role + register: nm_query_non_role + +# TODO: Implement more tests +- name: Verify query_non_role + assert: + that: + - cm_query_non_role is not changed + - nm_query_non_role is not changed + - cm_query_non_role == nm_query_non_role diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml new file mode 100644 index 000000000..84613d63a --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml @@ -0,0 +1,117 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Create schema 1 with Template 1, and Template 2, Template 3 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + - { template: Template 3} + +- name: Create schema 2 with Template 4 + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 4 + state: present + +- name: Query for all schemas + cisco.mso.mso_schema: + <<: *mso_info + state: query + register: query_all + +- name: Query a schema + cisco.mso.mso_schema: + <<: *mso_info + schema: ansible_test + state: query + register: query_one + +- name: Verify query_all and query_one + assert: + that: + - query_all is not changed + - query_one is not changed + - query_all.current | length >= 2 + - query_one.current.displayName == "ansible_test" + +- name: Remove schema (check_mode) + cisco.mso.mso_schema: + <<: *mso_info + schema: ansible_test + state: absent + check_mode: true + register: cm_rm_schema + +- name: Remove schema (normal_mode) + cisco.mso.mso_schema: + <<: *mso_info + schema: ansible_test + state: absent + register: nm_rm_schema + +- name: Verify rm_schema + assert: + that: + - cm_rm_schema is changed + - cm_rm_schema.previous.displayName == "ansible_test" + - cm_rm_schema.current == {} + - nm_rm_schema is changed + - nm_rm_schema.current == {} + - nm_rm_schema.previous.displayName == "ansible_test" + +- name: Query non_existing schema + cisco.mso.mso_schema: + <<: *mso_info + schema: non_existing + state: query + register: query_non_existing + +- name: Verify query_non_existing + assert: + that: + - query_non_existing is not changed + - query_non_existing.current == {} diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml new file mode 100644 index 000000000..e26bf45d5 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml @@ -0,0 +1,158 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + ignore_errors: true + loop: + - Destination_Schema + - Source_Schema + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Create Source schema with Template 1, and Template 2, Template 3 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: Source_Schema + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + - { template: Template 3} + +- name: Ensure VRF exist + mso_schema_template_vrf: &vrf_present + <<: *mso_info + schema: Source_Schema + template: Template1 + vrf: VRF1 + state: present + +- name: Add bd in Source schema + mso_schema_template_bd: &bd_present + <<: *mso_info + schema: Source_Schema + template: Template1 + bd: BD_1 + vrf: + name: VRF1 + state: present + +- name: Clone schema + cisco.mso.mso_schema_clone: + <<: *mso_info + source_schema: Source_Schema + destination_schema: Destination_Schema + state: clone + register: add_schema + +- name: Verify add_schema + assert: + that: + - add_schema is changed + - add_schema.previous == {} + - add_schema.current.displayName == 'Destination_Schema' + +- name: Clone schema with same name + cisco.mso.mso_schema_clone: + <<: *mso_info + source_schema: Source_Schema + destination_schema: Source_Schema + state: clone + ignore_errors: true + register: add_same_schema + +- name: Verify add_same_schema + assert: + that: + - add_same_schema is not changed + - add_same_schema.current == {} + - add_same_schema.msg == "Source and Destination schema cannot have same names." + +- name: Clone schema when destination schema exists + cisco.mso.mso_schema_clone: + <<: *mso_info + source_schema: Source_Schema + destination_schema: Destination_Schema + state: clone + ignore_errors: true + register: add_existing_schema + +- name: Verify add_existing_schema + assert: + that: + - add_existing_schema is not changed + - add_existing_schema.msg == "Schema with the name 'Destination_Schema' already exists. Please use another name." + +- name: Clone schema when source schema does not exist + cisco.mso.mso_schema_clone: + <<: *mso_info + source_schema: Source_Schema_1 + destination_schema: Destination_Schema_2 + state: clone + ignore_errors: true + register: add_existing_schema + +- name: Verify add_existing_schema + assert: + that: + - add_existing_schema is not changed + - add_existing_schema.msg == "Provided schema 'Source_Schema_1' does not exist." + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + register: rm_schema + loop: + - Destination_Schema + - Source_Schema + +- name: Verify rm_schema + assert: + that: + - rm_schema is changed
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml new file mode 100644 index 000000000..6eede75ae --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml @@ -0,0 +1,273 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + ignore_errors: true + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 and Template 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + +- name: Add a new site to a schema with Template 1 in check mode + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + check_mode: true + register: add_site_cm + +- name: Verify add_site_cm + assert: + that: + - add_site_cm.current.siteId is match ("[0-9a-zA-Z]*") + - add_site_cm.current.templateName == "Template1" + +- name: Add a new site to a schema with Template 1 in normal mode + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + register: add_site_nm + +- name: Verify add_site_nm + assert: + that: + - add_site_nm.current.siteId is match ("[0-9a-zA-Z]*") + - add_site_nm.current.templateName == "Template1" + +- name: Add a new site to a schema in normal mode again + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + register: add_site_nm_again + +- name: Verify add_site_nm_again + assert: + that: + - add_site_nm_again is not changed + - add_site_nm_again.current.siteId is match ("[0-9a-zA-Z]*") + +- name: Add a new site to a schema with Template 2 in normal mode + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: present + output_level: debug + register: add_site_temp2_nm + +- name: Verify add_site_temp2_nm + assert: + that: + - add_site_temp2_nm.current.siteId is match ("[0-9a-zA-Z]*") + - add_site_temp2_nm.current.templateName == "Template2" + - add_site_temp2_nm.method == "PATCH" + - add_site_temp2_nm.patch_operation != [] + - add_site_temp2_nm.patch_operation.0.value.templateName == "Template2" + - add_site_temp2_nm.previous == {} + - add_site_temp2_nm.proposed != {} + - add_site_temp2_nm.proposed.templateName == "Template2" + - add_site_temp2_nm.sent != {} + - add_site_temp2_nm.sent.templateName == "Template2" + +- name: Query a schema site + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: query + register: query_site + +- name: Query all schema sites + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + register: query_all_sites + +- name: Verify query_site and query_all_sites + assert: + that: + - query_site is not changed + - query_all_sites is not changed + - query_all_sites.current | length == 2 + +- name: Remove a site from a schema with Template1 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: absent + register: rm_site_temp1 + +- name: Remove a site from a schema with Template2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: absent + register: rm_site_temp2 + +- name: Verify rm_site_temp1 and rm_site_temp2 + assert: + that: + - rm_site_temp1 is changed + - rm_site_temp1.current == {} + - rm_site_temp2 is changed + - rm_site_temp2.current == {} + +- name: Remove a site from a schema with Template2 again + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: absent + register: rm_site_again + +- name: Verify rm_site_again + assert: + that: + - rm_site_again is not changed + +# USE NON-EXISTING STATE +- name: non_existing_state state + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: non_existing_state + ignore_errors: true + register: non_existing_state + +- name: Verify non_existing_state + assert: + that: + - non_existing_state is not changed + - non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state" + +# USE A NON_EXISTING_SCHEMA +- name: non_existing_schema + mso_schema_site: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: query + ignore_errors: true + register: non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - non_existing_schema is not changed + - non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON_EXISTING_TEMPLATE +- name: non_existing_template + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + state: query + ignore_errors: true + register: non_existing_template + +- name: Verify non_existing_template + assert: + that: + - non_existing_template is not changed + - non_existing_template.msg == "Template 'non_existing_template' not found" + +- name: Template attribute absent in task + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: absent_template + +- name: Verify absent_template + assert: + that: + - absent_template is not changed + - absent_template.current == []
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml new file mode 100644 index 000000000..9faffb376 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml @@ -0,0 +1,481 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure physical site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure aws site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure azure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Associate non-cloud site with ansible_test again in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Associate aws site with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + register: aaws_nm + +- name: Associate azure site with access_type not present, with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure + state: present + register: aazure_shared_nm + +- name: Ensure schema 1 with Template 1, and Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + - { template: Template 3} + +- name: Ensure schema 2 with Template 4 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 4 + state: present + +- name: Add cloud site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{item.site}}' + template: '{{item.template}}' + state: present + loop: + - { site: 'azure_{{ mso_site | default("ansible_test") }}', template: 'Template 1' } + - { site: 'aws_{{ mso_site | default("ansible_test") }}', template: 'Template 2' } + +- name: Add physical site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + vrf: VRF1 + state: present + +- name: Add BD1 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + bd: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure ANPs exist at template level + mso_schema_template_anp: + <<: *mso_info + schema: '{{item.schema}}' + template: '{{ item.template }}' + anp: '{{ item.anp }}' + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', anp: 'ANP' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2', anp: 'ANP_2' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_1' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_2' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 4', anp: 'ANP_4' } + +- name: Ensure EPGs exist at template level + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + epg: ansible_test_3 + vrf: + name: VRF1 + schema: ansible_test + template: Template 3 + bd: + name: BD1 + schema: ansible_test + template: Template 3 + state: present + +- name: Add ANP to site azure (check_mode) + mso_schema_site_anp: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + check_mode: true + register: cm_add_anp + +- name: Add ANP to site azure (normal mode) + mso_schema_site_anp: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + register: nm_add_anp + +- name: Verify add_anp values + assert: + that: + - cm_add_anp.current.anpRef.anpName == 'ANP' + - nm_add_anp.current.anpRef.anpName == 'ANP' + +- name: Verify add_anp change + assert: + that: + - cm_add_anp is changed + - nm_add_anp is changed + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add ANP to site aws + mso_schema_site_anp: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + anp: ANP_2 + state: present + register: add_anp + +- name: Verify add_anp value + assert: + that: + - add_anp.current.anpRef.anpName == 'ANP_2' + +- name: Verify add_anp change + assert: + that: + - add_anp is changed + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add ANPs to site + mso_schema_site_anp: + <<: *mso_info + site: '{{ item.site }}' + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: '{{ item.anp }}' + state: present + loop: + - { site: '{{ mso_site | default("ansible_test") }}', schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3' } + - { site: '{{ mso_site | default("ansible_test") }}', schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_1' } + - { site: '{{ mso_site | default("ansible_test") }}', schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_2' } + +- name: Add a new site EPG for idempotency check + mso_schema_site_anp_epg: &idempotency_vmm + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: 'Template 3' + anp: 'ANP_3' + site: '{{ mso_site | default("ansible_test") }}' + epg: ansible_test_3 + state: present + +# Test due to inconsistency in attributes REQUEST/RESPONSE API +# MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing +- name: Add new site domain to site EPG for idempotency check + mso_schema_site_anp_epg_domain: + <<: *idempotency_vmm + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + +- name: Add ANPs to site again + mso_schema_site_anp: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: 'Template 3' + anp: 'ANP_3' + state: present + register: add_anp_again + +- name: Verify add_anp_again + assert: + that: + - add_anp_again is not changed + - add_anp_again.current.anpRef.anpName == 'ANP_3' + +# QUERY ANPs +- name: Query specific ANP (normal mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: query + register: query_anp + +- name: Verify query_anp + assert: + that: + - query_anp is not changed + +- name: Query all ANPs (normal mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all | length >= 3 + +# DELETE the ANP +- name: Delete ANP3 (normal mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: absent + register: delete_anp + +- name: Verify delete_anp + assert: + that: + - delete_anp is changed + - delete_anp.current == {} + +- name: Delete ANP1 again + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: absent + register: delete_anp_again + +- name: Verify delete_anp_again + assert: + that: + - delete_anp_again is not changed + - delete_anp_again.current == {} + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP (normal mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: non_existing_anp + state: query + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - nm_query_non_anp is not changed + - nm_query_non_anp.msg == "ANP 'non_existing_anp' not found" + +# USE A NON-EXISTING STATE +- name: Non-existing state (normal_mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - nm_non_existing_state is not changed + - nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema (normal_mode) + mso_schema_site_anp: + <<: *mso_info + schema: non-existing-schema + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - nm_non_existing_schema is not changed + - nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING SITE +- name: Non-existing site (normal_mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non-existing-site + template: Template 1 + anp: ANP + state: query + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - nm_non_existing_site is not changed + - nm_non_existing_site.msg == "Site 'non-existing-site' is not a valid site name." + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add ANP to Template without any site associated (normal mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 4 + anp: ANP_4 + state: present + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - nm_no_site_associated is not changed + - nm_no_site_associated.msg == "No site associated with template 'Template4'. Associate the site with the template using mso_schema_site." + +# USE A NON-EXISTING SITE-TEMPLATE +- name: Non-existing site-template (normal_mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + anp: ANP_2 + state: query + ignore_errors: true + register: nm_non_existing_site_template + +- name: Verify non_existing_site_template + assert: + that: + - nm_non_existing_site_template is not changed + - nm_non_existing_site_template.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1, [0-9a-zA-Z]*/Template2, [0-9a-zA-Z]*/Template3")
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml new file mode 100644 index 000000000..8373a629f --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml @@ -0,0 +1,705 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + ignore_errors: true + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Associate non-cloud site with ansible_test again in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Associate aws site with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + register: aaws_nm + +- name: Associate azure site with access_type not present, with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure + state: present + register: aazure_shared_nm + +- name: Ensure schema 1 with Template 1, and Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + - { template: Template 3} + +- name: Ensure schema 2 with Template 4 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 4 + state: present + +- name: Add cloud site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{item.site}}' + template: '{{item.template}}' + state: present + loop: + - { site: 'azure_{{ mso_site | default("ansible_test") }}', template: 'Template 1' } + - { site: 'aws_{{ mso_site | default("ansible_test") }}', template: 'Template 2' } + when: version.current.version is version('3', '<') or version.current.version is version('3.2', '>=') + +- name: Add physical site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{ item.template }}' + state: present + loop: + - { template: 'Template 3' } + - { template: 'Template 1' } + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: '{{ item.template }}' + vrf: VRF1 + state: present + loop: + - { template: 'Template 1' } + - { template: 'Template 3' } + +- name: Add BD1 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: '{{ item.template }}' + bd: BD1 + vrf: + name: VRF1 + state: present + loop: + - { template: 'Template 1' } + - { template: 'Template 3' } + +- name: Ensure ANPs exist at template level + mso_schema_template_anp: + <<: *mso_info + schema: '{{item.schema}}' + template: '{{ item.template }}' + anp: '{{ item.anp }}' + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', anp: 'ANP' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2', anp: 'ANP_2' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 4', anp: 'ANP_4' } + +- name: Ensure ANP exist at template level + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: present + +- name: Ensure EPGs exist at template level + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: '{{ item.anp }}' + epg: '{{ item.epg }}' + vrf: + name: VRF1 + schema: ansible_test + template: Template 1 + bd: + name: BD1 + schema: ansible_test + template: Template 1 + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', anp: 'ANP', epg: 'ansible_test_1' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2', anp: 'ANP_2', epg: 'ansible_test_2' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 4', anp: 'ANP_4', epg: 'ansible_test_4' } + +- name: Ensure EPGs exist at template level + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + epg: ansible_test_3 + vrf: + name: VRF1 + schema: ansible_test + template: Template 3 + bd: + name: BD1 + schema: ansible_test + template: Template 3 + state: present + +- name: Add ANP to site + mso_schema_site_anp: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: present + +# ADD ANP EPGs to SITE +- name: Add new EPG to site after adding ANP to site (check_mode) + mso_schema_site_anp_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + epg: ansible_test_3 + state: present + check_mode: true + register: cm_add_epg + +- name: Add new EPG to site after adding ANP to site (normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + epg: ansible_test_3 + state: present + register: nm_add_epg + +- name: Verify add_epg + assert: + that: + - cm_add_epg is changed + - nm_add_epg is changed + - cm_add_epg.current.epgRef.anpName == 'ANP_3' + - nm_add_epg.current.epgRef.anpName == 'ANP_3' + - cm_add_epg.current.epgRef.epgName == 'ansible_test_3' + - nm_add_epg.current.epgRef.epgName == 'ansible_test_3' + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add new EPG to site after adding ANP to site again + mso_schema_site_anp_epg: &idempotency_vmm + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + epg: ansible_test_3 + state: present + register: add_epg_again + +- name: Verify add_epg_again + assert: + that: + - add_epg_again is not changed + - add_epg_again.current.epgRef.anpName == 'ANP_3' + - add_epg_again.current.epgRef.epgName == 'ansible_test_3' + +# Test due to inconsistency in attributes REQUEST/RESPONSE API +# MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing +- name: Add new site domain to site EPG for idempotency check + mso_schema_site_anp_epg_domain: + <<: *idempotency_vmm + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + +- name: Add new EPG to site after adding ANP to site again + mso_schema_site_anp_epg: + <<: *idempotency_vmm + register: add_epg_again_with_vmm + +- name: Verify add_epg_again with vmm + assert: + that: + - add_epg_again is not changed + - add_epg_again.current.epgRef.anpName == 'ANP_3' + - add_epg_again.current.epgRef.epgName == 'ansible_test_3' + +- name: Add new EPG to site without adding ANPs to site + mso_schema_site_anp_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: present + register: add_epg_no_anp + +- name: Verify add_epg_no_anp + assert: + that: + - add_epg_no_anp is changed + - add_epg_no_anp.current.epgs.0.epgRef.anpName == 'ANP' + - add_epg_no_anp.current.epgs.0.epgRef.epgName == 'ansible_test_1' + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add new EPG to site without adding ANPs to site again (ANP already exists from previous run) + mso_schema_site_anp_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: present + register: add_epg_no_anp_again + +- name: Verify add_epg_no_anp_again + assert: + that: + - add_epg_no_anp_again is not changed + - add_epg_no_anp_again.current.epgRef.anpName == 'ANP' + - add_epg_no_anp_again.current.epgRef.epgName == 'ansible_test_1' + +# QUERY EPGs +- name: Query all EPGs with ANP (normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + +- name: Query specific EPG1 (normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + register: query_epg + +- name: Verify query_epg + assert: + that: + - query_epg is not changed + +# DELETE the EPG +- name: Delete EPG1 (normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: absent + register: delete_epg + +- name: Verify delete_epg + assert: + that: + - delete_epg is changed + - delete_epg.current == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Delete EPG1 again + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: absent + register: delete_epg_again + +- name: Verify delete_epg_again + assert: + that: + - delete_epg_again is not changed + - delete_epg_again.current == {} + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG in template (normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + epg: non_existing_epg + state: query + ignore_errors: true + register: nm_query_non_epg + +- name: Verify query_non_epg + assert: + that: + - nm_query_non_epg is not changed + - nm_query_non_epg.msg == "Provided EPG 'non_existing_epg' does not exist. Existing EPGs{{':'}} ansible_test_3" + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG in site level + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + ignore_errors: true + register: query_non_epg + +- name: Verify query_non_epg + assert: + that: + - query_non_epg is not changed + - query_non_epg.msg == "EPG 'ansible_test_1' not found" + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Delete anp + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + state: absent + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP in template(normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: non_existing_anp + epg: ansible_test_3 + state: query + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - nm_query_non_anp is not changed + - nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP_3" + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP at site level(normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 3 + anp: ANP_3 + site: '{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - nm_query_non_anp is not changed + - nm_query_non_anp.msg == "Provided anp 'ANP_3' does not exist at site level." + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +# USE A NON-EXISTING STATE +- name: Non-existing state (normal_mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - nm_non_existing_state is not changed + - nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# # USE A NON-EXISTING TEMPLATE +- name: Non-existing template (normal_mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: non-existing-template + anp: ANP + epg: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - nm_non_existing_template is not changed + - nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2, Template3" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema (normal_mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: non-existing-schema + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - nm_non_existing_schema is not changed + - nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING SITE +- name: Non-existing site (normal_mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non-existing-site + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - nm_non_existing_site is not changed + - nm_non_existing_site.msg == "Site 'non-existing-site' is not a valid site name." + +# USE A NON-EXISTING SITE-TEMPLATE +- name: Non-existing site-template (normal_mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + anp: ANP_2 + epg: ansible_test_2 + state: query + ignore_errors: true + register: nm_non_existing_site_template + +- name: Verify non_existing_site_template + assert: + that: + - nm_non_existing_site_template is not changed + - nm_non_existing_site_template.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1, [0-9a-zA-Z]*/Template2, [0-9a-zA-Z]*/Template3, [0-9a-zA-Z]*/Template1") + when: version.current.version is version('3', '<') or version.current.version is version('3.2', '>=') + +- name: Verify non_existing_site_template + assert: + that: + - nm_non_existing_site_template is not changed + - nm_non_existing_site_template.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template3, [0-9a-zA-Z]*/Template1") + when: version.current.version is version('3', '>=') and version.current.version is version('3.2', '<') + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add EPG to Template without any site associated (normal mode) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 4 + anp: ANP_4 + epg: ansible_test_1 + state: present + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - nm_no_site_associated is not changed + - nm_no_site_associated.msg == "No site associated with template 'Template4'. Associate the site with the template using mso_schema_site." + +# ADDING PRIVATE LINK LABEL +# Add private link label when MSO version >= 3.3 +- name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + - name: Remove physical site from schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: 'Template 1' + state: absent + + - name: Ensure region for VRF1 at site level exists + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + state: present + + - name: Ensure Private Link Label in Azure VRF subnet exist (MSO >3.3) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + subnet: 10.0.0.0/24 + private_link_label: 'New_Private_Link_Label' + zone: null + + - name: Ensure another Private Link Label in Azure VRF subnet exist (MSO >3.3) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + subnet: 10.0.1.0/26 + private_link_label: 'PLL' + + - name: Add new EPG service type parameters (for version greater than 3.3) + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + vrf: + name: VRF1 + schema: ansible_test + template: Template 1 + epg_type: 'service' + deployment_type: 'cloud_native' + service_type: 'Azure-Storage' + access_type: 'private' + state: present + + - name: Add private link label to the EPG (for version greater than 3.3) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + private_link_label: 'New_Private_Link_Label' + state: present + + - name: Change private link label in the EPG (for version greater than 3.3) + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + private_link_label: 'PLL' + state: present
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml new file mode 100644 index 000000000..0fc8943fd --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml @@ -0,0 +1,774 @@ +# Test code for the MSO modules +# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("debug") }}' + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add a new site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Add BD1 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 with AP1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + state: present + +- name: Ensure Template 1 and AP1 with EPG1 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP1 with EPG3 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG3 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 with AP2 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + state: present + +- name: Ensure Template 1 and AP2 with EPG2 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP2 with EPG4 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP2 with EPG6 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG6 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +# ADD STATIC PORTS +- name: Add static port 1 to site EPG1 of AP1 (check mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: native + type: port + deployment_immediacy: immediate + static_ports: + - path: eth1/2 + leaf: 102 + - path: eth1/3 + vlan: 124 + - leaf: 102 + path: eth1/3 + vlan: 124 + state: present + check_mode: true + register: cm_add_stat1e1 + +- name: Verify cm_add_stat1e1 + assert: + that: + - cm_add_stat1e1 is changed + - cm_add_stat1e1.previous == [] + - cm_add_stat1e1.current[0].deploymentImmediacy == 'immediate' + - cm_add_stat1e1.current[0].portEncapVlan == 126 + - cm_add_stat1e1.current[0].path == 'topology/pod-1/paths-102/pathep-[eth1/2]' + - cm_add_stat1e1.current[0].mode == 'native' + - cm_add_stat1e1.current[0].type == 'port' + - cm_add_stat1e1.current[1].deploymentImmediacy == 'immediate' + - cm_add_stat1e1.current[1].portEncapVlan == 124 + - cm_add_stat1e1.current[1].path == 'topology/pod-1/paths-101/pathep-[eth1/3]' + - cm_add_stat1e1.current[1].mode == 'native' + - cm_add_stat1e1.current[1].type == 'port' + - cm_add_stat1e1.current[2].portEncapVlan == 124 + - cm_add_stat1e1.current[2].path == 'topology/pod-1/paths-102/pathep-[eth1/3]' + - cm_add_stat1e1.current|length == 3 + +- name: Add static port 1 to site EPG1 of AP1 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: &add_static_port_1 + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: native + type: port + deployment_immediacy: immediate + static_ports: + - path: eth1/2 + leaf: 102 + - path: eth1/3 + vlan: 124 + - leaf: 102 + path: eth1/3 + vlan: 124 + state: present + register: nm_add_stat1e1 + +- name: Verify nm_add_stat1e1 + assert: + that: + - nm_add_stat1e1 is changed + - nm_add_stat1e1.previous == [] + - nm_add_stat1e1.current[0].deploymentImmediacy == 'immediate' + - nm_add_stat1e1.current[0].portEncapVlan == 126 + - nm_add_stat1e1.current[0].path == 'topology/pod-1/paths-102/pathep-[eth1/2]' + - nm_add_stat1e1.current[0].mode == 'native' + - nm_add_stat1e1.current[0].type == 'port' + - nm_add_stat1e1.current[1].deploymentImmediacy == 'immediate' + - nm_add_stat1e1.current[1].portEncapVlan == 124 + - nm_add_stat1e1.current[1].path == 'topology/pod-1/paths-101/pathep-[eth1/3]' + - nm_add_stat1e1.current[1].mode == 'native' + - nm_add_stat1e1.current[1].type == 'port' + - nm_add_stat1e1.current[2].portEncapVlan == 124 + - nm_add_stat1e1.current[2].path == 'topology/pod-1/paths-102/pathep-[eth1/3]' + - nm_add_stat1e1.current|length ==3 + +- name: Add static port 3 (vpc) to site EPG1 of AP1 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port_1 + static_ports: + - path: eth1/2 + leaf: 102 + - path: eth1/3 + vlan: 124 + - leaf: 102 + path: eth1/3 + vlan: 124 + - path: ansible_polgrp + type: vpc + leaf: 103-104 + vlan: 105 + state: present + register: nm_add_stat3e1 + +- name: Verify nm_add_stat3e1 + assert: + that: + - nm_add_stat3e1 is changed + - nm_add_stat3e1.previous != [] + - nm_add_stat3e1.current[3].deploymentImmediacy == 'immediate' + - nm_add_stat3e1.current[3].portEncapVlan == 105 + - nm_add_stat3e1.current[3].path == 'topology/pod-1/protpaths-103-104/pathep-[ansible_polgrp]' + - nm_add_stat3e1.current[3].mode == 'native' + - nm_add_stat3e1.current[3].type == 'vpc' + - nm_add_stat3e1.current|length > nm_add_stat1e1.current|length # verifying length of current task (nm_add_stat3e1) is greater than the previous task (nm_add_stat1e1) + +- name: Add static port 2 (dpc) to EPG6 of AP2 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG6 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + static_ports: + - path: eth1/2 + pod: pod-2 + leaf: 102 + vlan: 100 + deployment_immediacy: lazy + mode: regular + type: dpc + primary_micro_segment_vlan: 199 + state: present + register: nm_add_stat2e6 + +- name: Verify nm_add_stat2e6 + assert: + that: + - nm_add_stat2e6 is changed + - nm_add_stat2e6.previous == [] + - nm_add_stat2e6.current[0].deploymentImmediacy == 'lazy' + - nm_add_stat2e6.current[0].portEncapVlan == 100 + - nm_add_stat2e6.current[0].microSegVlan == 199 + - nm_add_stat2e6.current[0].path == 'topology/pod-2/paths-102/pathep-[eth1/2]' + - nm_add_stat2e6.current[0].mode == 'regular' + - nm_add_stat2e6.current[0].type == 'dpc' + +- name: Add static port 2 to site EPG2 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: &static_port_2 + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + static_ports: + - path: eth1/2 + pod: pod-2 + leaf: 102 + state: present + register: nm_add_stat2e2 + +- name: Verify nm_add_stat2e2 + assert: + that: + - nm_add_stat2e2 is changed + - nm_add_stat2e2.previous == [] + - nm_add_stat2e2.current[0].deploymentImmediacy == 'immediate' + - nm_add_stat2e2.current[0].portEncapVlan == 100 + - nm_add_stat2e2.current[0].path == 'topology/pod-2/paths-102/pathep-[eth1/2]' + - nm_add_stat2e2.current[0].mode == 'regular' + - nm_add_stat2e2.current[0].type == 'port' + +# ADD EXISTING STATIC PORT +- name: Add static port 1 to site EPG1 again (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *static_port_2 + state: present + register: nm_add_stat1e1_2 + +- name: Verify nm_add_stat1e1_2 + assert: + that: + - nm_add_stat1e1_2 is not changed + +# ADD STATIC FEX PORT +- name: Add static fex port to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *static_port_2 + static_ports: + - path: eth1/2 + pod: pod-2 + leaf: 102 + - pod: pod-4 + vlan: 126 + fex: 151 + mode: native + deployment_immediacy: lazy + state: present + register: nm_add_statfex + +- name: Verify nm_add_statfex + assert: + that: + - nm_add_statfex is changed + - nm_add_statfex.previous != [] + - nm_add_statfex.current[1].deploymentImmediacy == 'lazy' + - nm_add_statfex.current[1].portEncapVlan == 126 + - nm_add_statfex.current[1].path == 'topology/pod-4/paths-101/extpaths-151/pathep-[eth1/1]' + - nm_add_statfex.current[1].mode == 'native' + - nm_add_statfex.current[1].type == 'port' + +# QUERY STATIC PORTS +- name: Query STATIC PORTS of site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + state: query + register: nm_query_statse1 + +- name: Verify nm_query_statse1 + assert: + that: + - nm_query_statse1 is not changed + +#REMOVE STATIC PORT +- name: Remove all static ports from EPG2 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *static_port_2 + state: absent + register: nm_remove_stat2e2 + +- name: Verify nm_remove_stat2e2 + assert: + that: + - nm_remove_stat2e2 is changed + +- name: Remove all static ports from EPG2 again(normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *static_port_2 + state: absent + ignore_errors: true + register: nm_remove_again_stat2e2 + +- name: Verify nm_remove_again_stat2e2 + assert: + that: + - nm_remove_again_stat2e2 is not changed + +# VERIFY NON EXISTENT 'DEPLOYMENT IMMEDIACY', 'TYPE' AND 'MODE' +- name: Add static port 1 to site EPG4 with AP2 with no deployment immediacy, type and mode (normal mode) + mso_schema_site_anp_epg_bulk_staticport: &add_static_port + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + pod: pod-4 + leaf: 101 + path: eth1/1 + vlan: 126 + static_ports: + - path: eth1/2 + leaf: 101 + state: present + register: nm_add_stat_no_di_type_mode + +- name: Verify nm_add_stat_no_di_type_mode + assert: + that: + - nm_add_stat_no_di_type_mode.current[0].deploymentImmediacy == 'lazy' + - nm_add_stat_no_di_type_mode.current[0].mode == 'untagged' + - nm_add_stat_no_di_type_mode.current[0].type == 'port' + +# VERIFY NON EXISTENT parent values. +- name: Add static port 1 to site EPG4 with AP2 with no parent values (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + static_ports: + - path: eth1/2 + pod: pod-4 + leaf: 101 + vlan: 126 + state: present + register: nm_add_stat_no_parent + +- name: Verify nm_add_stat_no_parent + assert: + that: + - nm_add_stat_no_parent.current[0].deploymentImmediacy == 'lazy' + - nm_add_stat_no_parent.current[0].mode == 'untagged' + - nm_add_stat_no_parent.current[0].type == 'port' + +# VERIFY NON EXISTENT required values. +- name: Add static port 1 to site EPG4 with AP2 with missing required values (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + static_ports: + - mode: regular + state: present + ignore_errors: true + register: nm_add_stat_no_req + +- name: Verify nm_add_stat_no_req + assert: + that: + - nm_add_stat_no_req is not changed + - nm_add_stat_no_req.msg == "state is present but all of the following are missing{{':'}} pod, leaf, path, vlan." + +# VERIFY path in each leaf within a pod is unique (pod->leaf->path) +- name: Add static port 1 to site EPG4 with AP2 with missing required values (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + static_ports: + - path: eth1/2 + pod: pod-1 + leaf: 101 + vlan: 126 + - path: eth1/2 + pod: pod-1 + leaf: 101 + vlan: 127 + state: present + ignore_errors: true + register: nm_add_unique_path + +- name: Verify nm_add_unique_path + assert: + that: + - nm_add_unique_path is not changed + - nm_add_unique_path.msg == "Each leaf in a pod of a static port should have an unique path." + +# USE NON-EXISTING EPG and ANP AT TEMPLATE LEVEL +- name: Add static port 1 to non-existent site EPG5 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port + anp: AP5 + epg: EPG5 + state: present + ignore_errors: true + register: nm_add_stat1e5 + +- name: Verify nm_add_stat1e5 + assert: + that: + - nm_add_stat1e5 is not changed + +# USE NON-EXISTING EPG AT TEMPLATE LEVEL +- name: Add static port 1 to non-existent site EPG5 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port + anp: AP1 + epg: EPG6 + state: present + ignore_errors: true + register: nm_add_stat1e6 + +- name: Verify nm_add_stat1e6 + assert: + that: + - nm_add_stat1e6 is not changed + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for static port + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port + schema: non_existing_schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - nm_non_existing_schema is not changed + - nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for static port (normal_mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port + template: non_existing_template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - nm_non_existing_template is not changed + - nm_non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2" + +# USE A NON-EXISTING SITE +- name: Non-existing site for static port (normal_mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port + site: 'non_existing_site' + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - nm_non_existing_site is not changed + - nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name." + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site EPG static port association to Template 3 without any site associated (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *add_static_port + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - nm_no_site_associated is not changed + - nm_no_site_associated.msg == "No sites associated with schema 'ansible_test_2'. Associate the site with the schema using (M) mso_schema_site." + +# Add static port 3 after adding ANP and EPG to site +- name: Add a new site to a schema 2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF1 + state: present + +- name: Add BD1 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + bd: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 3 with AP1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: AP1 + state: present + +- name: Ensure Template 3 and AP1 with EPG1 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: AP1 + epg: EPG1 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Add new ANP to site + mso_schema_site_anp: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: AP1 + state: present + register: cm_add_epg + +- name: Add new EPG to site after adding ANP to site + mso_schema_site_anp_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: AP1 + epg: EPG1 + state: present + register: cm_add_epg + +- name: Add static port to site EPG1 in schema 2 (normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + static_ports: + - path: eth1/2 + pod: pod-2 + leaf: 102 + - vlan: 101 + state: present + register: nm_add_stat3 + +- name: Verify nm_add_stat3 + assert: + that: + - nm_add_stat3 is changed + - nm_add_stat3.previous == [] + - nm_add_stat3.current[0].deploymentImmediacy == 'immediate' + - nm_add_stat3.current[0].portEncapVlan == 100 + - nm_add_stat3.current[0].path == 'topology/pod-2/paths-102/pathep-[eth1/2]' + - nm_add_stat3.current[0].mode == 'regular' + - nm_add_stat3.current[0].type == 'port' + +- name: Add static port to site EPG1 in schema 2 again without static_ports(normal mode) + mso_schema_site_anp_epg_bulk_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + state: present + ignore_errors: true + register: nm_add_no_static_port + +- name: Verify nm_add_no_static_port + assert: + that: + - nm_add_no_static_port is not changed + - nm_add_no_static_port.msg == "state is present but all of the following are missing{{':'}} static_ports" diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml new file mode 100644 index 000000000..24b953a0f --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml @@ -0,0 +1,1068 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + ignore_errors: true + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add a new site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Add BD1 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 with AP1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + state: present + +- name: Ensure Template 1 and AP1 with EPG1 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP1 with EPG3 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG3 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 with AP2 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + state: present + +- name: Ensure Template 1 and AP2 with EPG2 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP2 with EPG4 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +# ADD DOMAINS +- name: Add domain 1 to site EPG1 with AP1 (check mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: VMware-VMM + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + check_mode: true + register: cm_add_dom1e1 + +- name: Verify cm_add_dom1e1 + assert: + that: + - cm_add_dom1e1 is changed + - cm_add_dom1e1.previous == {} + - cm_add_dom1e1.current.deploymentImmediacy == 'lazy' + - cm_add_dom1e1.current.domainType == 'vmmDomain' + - cm_add_dom1e1.current.dn == 'uni/vmmp-VMware/dom-VMware-VMM' + - cm_add_dom1e1.current.resolutionImmediacy == 'pre-provision' + +- name: Add domain 1 to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + register: nm_add_dom1e1 + +- name: Verify nm_add_dom1e1 + assert: + that: + - nm_add_dom1e1 is changed + - nm_add_dom1e1.previous == {} + - nm_add_dom1e1.current.deploymentImmediacy == 'lazy' + - nm_add_dom1e1.current.domainType == 'vmmDomain' + - nm_add_dom1e1.current.dn == 'uni/vmmp-VMware/dom-VMware-VMM' + - nm_add_dom1e1.current.resolutionImmediacy == 'pre-provision' + +- name: Add domain 2 to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + register: nm_add_dom2e1 + +- name: Verify nm_add_dom2e1 + assert: + that: + - nm_add_dom2e1 is changed + - nm_add_dom2e1.previous == {} + - nm_add_dom2e1.current.deploymentImmediacy == 'lazy' + - nm_add_dom2e1.current.domainType == 'physicalDomain' + - nm_add_dom2e1.current.dn == 'uni/phys-phys' + - nm_add_dom2e1.current.resolutionImmediacy == 'pre-provision' + +- name: Add domain 3 to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: lazy + state: present + register: nm_add_dom3e1 + +- name: Verify nm_add_dom3e1 + assert: + that: + - nm_add_dom3e1 is changed + - nm_add_dom3e1.previous != {} + - nm_add_dom3e1.current.deploymentImmediacy == 'lazy' + - nm_add_dom3e1.current.domainType == 'physicalDomain' + - nm_add_dom3e1.current.dn == 'uni/phys-phys' + - nm_add_dom3e1.current.resolutionImmediacy == 'lazy' + +- name: Add domain1 to site EPG3 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG3 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + state: present + register: nm_add_dom1e3 + +- name: Verify nm_add_dom1e2 + assert: + that: + - nm_add_dom1e3 is changed + - nm_add_dom1e3.previous == {} + - nm_add_dom1e3.current.deploymentImmediacy == 'lazy' + - nm_add_dom1e3.current.domainType == 'vmmDomain' + - nm_add_dom1e3.current.dn == 'uni/vmmp-VMware/dom-VMware-VMM' + - nm_add_dom1e3.current.resolutionImmediacy == 'lazy' + +- name: Add domain 2 to EPG2 (check mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + check_mode: true + register: cm_add_dom2e2 + +- name: Verify cm_add_dom2e2 + assert: + that: + - cm_add_dom2e2 is changed + - cm_add_dom2e2.previous == {} + - cm_add_dom2e2.current.deploymentImmediacy == 'lazy' + - cm_add_dom2e2.current.domainType == 'physicalDomain' + - cm_add_dom2e2.current.dn == 'uni/phys-phys' + - cm_add_dom2e2.current.resolutionImmediacy == 'pre-provision' + +# QUERY DOMAINS +- name: Query domains of site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + state: query + register: nm_query_domse1 + +- name: Verify nm_query_domse1 + assert: + that: + - nm_query_domse1 is not changed + +# QUERY A DOMAIN +- name: Query domain3 of site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: query + register: nm_query_dom3e1 + +- name: Verify nm_query_dom3e1 + assert: + that: + - nm_query_dom3e1 is not changed + +# QUERY REMOVED DOMAIN +- name: Add domain 2 to site EPG2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + register: nm_add_dom2e2 + +- name: Verify nm_add_dom2e2 + assert: + that: + - nm_add_dom2e2 is changed + - nm_add_dom2e2.previous == {} + - nm_add_dom2e2.current.deploymentImmediacy == 'lazy' + - nm_add_dom2e2.current.domainType == 'physicalDomain' + - nm_add_dom2e2.current.dn == 'uni/phys-phys' + - nm_add_dom2e2.current.resolutionImmediacy == 'pre-provision' + +- name: Remove domain2 from EPG2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: absent + register: nm_remove_dom2e2 + +- name: Verify nm_remove_dom2e2 + assert: + that: + - nm_remove_dom2e2 is changed + +- name: Query removed domain2 from EPG2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: query + ignore_errors: true + register: nm_non_existent_dom2e2 + +- name: Verify non_existing_domain + assert: + that: + - nm_non_existent_dom2e2 is not changed + - nm_non_existent_dom2e2.msg == "Domain association 'physicalDomain/phys' not found" + +- name: Remove domain2 from EPG2 again(normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + domain_association_type: physicalDomain + domain_profile: phys + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: absent + ignore_errors: true + register: nm_remove_again_dom2e2 + +- name: Verify nm_remove_again_dom2e2 + assert: + that: + - nm_remove_again_dom2e2 is not changed + +# ADD EXISTING DOMAIN +- name: Add domain 1 to site EPG1 again (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + register: nm_add_dom1e1_2 + +- name: Verify nm_add_dom1e1_2 + assert: + that: + - nm_add_dom1e1_2 is not changed + +# ADD DOMAIN WITH NO STATE +- name: Add domain 1 to site EPG1 again with no state (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + ignore_errors: true + register: nm_add_stateless_dom1e1_2 + +- name: Verify nm_add_stateless_dom1e1_2 + assert: + that: + - nm_add_stateless_dom1e1_2 is not changed + +# ADD OTHER DOMAIN OPTIONS +- name: Add domain l3ExtDomain to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: l3ExtDomain + domain_profile: 'ansible_l3domain' + deployment_immediacy: lazy + resolution_immediacy: lazy + state: present + register: nm_add_doml3 + +- name: Verify nm_add_doml3 + assert: + that: + - nm_add_doml3 is changed + - nm_add_doml3.previous == {} + - nm_add_doml3.current.deploymentImmediacy == 'lazy' + - nm_add_doml3.current.domainType == 'l3ExtDomain' + - nm_add_doml3.current.dn =='uni/l3dom-ansible_l3domain' + - nm_add_doml3.current.resolutionImmediacy == 'lazy' + +- name: Add domain l2ExtDomain to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: l2ExtDomain + domain_profile: 'ansible_l2domain' + deployment_immediacy: lazy + resolution_immediacy: lazy + state: present + ignore_errors: true + register: nm_add_doml2 + +- name: Verify nm_add_doml2 + assert: + that: + - nm_add_doml2 is changed + - nm_add_doml2.previous == {} + - nm_add_doml2.current.deploymentImmediacy == 'lazy' + - nm_add_doml2.current.domainType == 'l2ExtDomain' + - nm_add_doml2.current.dn =='uni/l2dom-ansible_l2domain' + - nm_add_doml2.current.resolutionImmediacy == 'lazy' + +- name: Add domain fibreChannel to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: fibreChannelDomain + domain_profile: 'ansible_fibreChanneldomain' + deployment_immediacy: lazy + resolution_immediacy: lazy + state: present + register: nm_add_domfc + +- name: Verify nm_add_domfc + assert: + that: + - nm_add_domfc is changed + - nm_add_domfc.previous == {} + - nm_add_domfc.current.domainType == 'fibreChannelDomain' + - nm_add_domfc.current.dn =='uni/fc-ansible_fibreChanneldomain' + - nm_add_domfc.current.resolutionImmediacy == 'lazy' + - nm_add_domfc.current.deploymentImmediacy == 'lazy' + +# USE OTHER ATTRIBUTES OF VMM DOMAIN +- name: Add domain vmm to site EPG2 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: &domain_ap1_epg2 + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: immediate + resolution_immediacy: lazy + micro_seg_vlan_type: 'vlan' + micro_seg_vlan: 100 + port_encap_vlan_type: 'vlan' + port_encap_vlan: 100 + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_name: 'ansible_check_name' + enhanced_lagpolicy_dn: 'ansible_check' + state: present + register: nm_add_domvmprop + +- name: Query domain vmms attached to site EPG2 with AP2 + mso_schema_site_anp_epg_domain: + <<: *domain_ap1_epg2 + state: query + register: nm_query_domvmprop + +- name: Verify domain vmm to site EPG2 with AP2 + assert: + that: + - nm_add_domvmprop is changed + - nm_add_domvmprop.previous == {} + - nm_add_domvmprop.current.allowMicroSegmentation == true + - nm_add_domvmprop.current.deploymentImmediacy == "immediate" + - nm_add_domvmprop.current.deployImmediacy == "immediate" + - nm_add_domvmprop.current.dn == "uni/vmmp-VMware/dom-VMware-VMM" + - nm_add_domvmprop.current.epgLagPol.enhancedLagPol.dn == "ansible_check" + - nm_add_domvmprop.current.epgLagPol.enhancedLagPol.name == "ansible_check_name" + - nm_add_domvmprop.current.microSegVlan.vlan == 100 + - nm_add_domvmprop.current.microSegVlan.vlanType == "vlan" + - nm_add_domvmprop.current.portEncapVlan.vlan == 100 + - nm_add_domvmprop.current.portEncapVlan.vlanType == "vlan" + - nm_add_domvmprop.current.resolutionImmediacy == "lazy" + - nm_add_domvmprop.current.switchType == "default" + - nm_add_domvmprop.current.switchingMode == "native" + - nm_add_domvmprop.current.vlanEncapMode == "static" + - nm_add_domvmprop.current.vmmDomainProperties.allowMicroSegmentation == true + - nm_add_domvmprop.current.vmmDomainProperties.epgLagPol.enhancedLagPol.dn == "ansible_check" + - nm_add_domvmprop.current.vmmDomainProperties.epgLagPol.enhancedLagPol.name == "ansible_check_name" + - nm_add_domvmprop.current.vmmDomainProperties.microSegVlan.vlan == 100 + - nm_add_domvmprop.current.vmmDomainProperties.microSegVlan.vlanType == "vlan" + - nm_add_domvmprop.current.vmmDomainProperties.portEncapVlan.vlan == 100 + - nm_add_domvmprop.current.vmmDomainProperties.portEncapVlan.vlanType == "vlan" + - nm_add_domvmprop.current.vmmDomainProperties.switchType == "default" + - nm_add_domvmprop.current.vmmDomainProperties.switchingMode == "native" + - nm_add_domvmprop.current.vmmDomainProperties.vlanEncapMode == "static" + - nm_query_domvmprop.current.allowMicroSegmentation == true + - nm_query_domvmprop.current.deployImmediacy == "immediate" + - nm_query_domvmprop.current.dn == "uni/vmmp-VMware/dom-VMware-VMM" + - nm_query_domvmprop.current.epgLagPol.enhancedLagPol.dn == "ansible_check" + - nm_query_domvmprop.current.epgLagPol.enhancedLagPol.name == "ansible_check_name" + - nm_query_domvmprop.current.microSegVlan.vlan == 100 + - nm_query_domvmprop.current.microSegVlan.vlanType == "vlan" + - nm_query_domvmprop.current.portEncapVlan.vlan == 100 + - nm_query_domvmprop.current.portEncapVlan.vlanType == "vlan" + - nm_query_domvmprop.current.resolutionImmediacy == "lazy" + - nm_query_domvmprop.current.switchType == "default" + - nm_query_domvmprop.current.switchingMode == "native" + - nm_query_domvmprop.current.vlanEncapMode == "static" + +- name: Add another domain vmm to site EPG2 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *domain_ap1_epg2 + domain_profile: 'TEST' + state: present + register: nm_add_another_domvmprop + +- name: Query all domains vmms attached to site EPG2 with AP2 + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + state: query + register: nm_query_another_domvmprop + +- name: Verify domain vmm to site EPG2 with AP2 + assert: + that: + - nm_add_another_domvmprop is changed + - nm_query_another_domvmprop is not changed + - nm_query_another_domvmprop.current | length == 2 + +- name: Add domain vmm to site EPG4 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + micro_seg_vlan: 100 + port_encap_vlan_type: 'vlan' + port_encap_vlan: 100 + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_name: 'ansible_check' + enhanced_lagpolicy_dn: 'ansible_check' + state: present + ignore_errors: true + register: nm_add_domvmprop1 + +- name: Verify nm_add_domvmprop1 + assert: + that: + - nm_add_domvmprop1 is not changed + - nm_add_domvmprop1.previous == {} + - nm_add_domvmprop1.msg == "micro_seg_vlan_type is required when micro_seg_vlan is provided." + +- name: Add domain vmm to site EPG4 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + micro_seg_vlan_type: 'vlan' + port_encap_vlan_type: 'vlan' + port_encap_vlan: 100 + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_name: 'ansible_check' + enhanced_lagpolicy_dn: 'ansible_check' + state: present + ignore_errors: true + register: nm_add_domvmprop2 + +- name: Verify nm_add_domvmprop2 + assert: + that: + - nm_add_domvmprop2 is not changed + - nm_add_domvmprop2.msg == "micro_seg_vlan is required when micro_seg_vlan_type is provided." + +- name: Add domain vmm to site EPG4 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + micro_seg_vlan_type: 'vlan' + micro_seg_vlan: 100 + port_encap_vlan: 100 + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_name: 'ansible_check' + enhanced_lagpolicy_dn: '' + state: present + ignore_errors: true + register: nm_add_domvmprop3 + +- name: Verify nm_add_domvmprop3 + assert: + that: + - nm_add_domvmprop3 is not changed + - nm_add_domvmprop3.msg == "port_encap_vlan_type is required when port_encap_vlan is provided." + +- name: Add domain vmm to site EPG4 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + micro_seg_vlan_type: 'vlan' + micro_seg_vlan: 100 + port_encap_vlan_type: 'vlan' + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_name: 'ansible_check' + enhanced_lagpolicy_dn: 'ansible_check' + state: present + ignore_errors: true + register: nm_add_domvmprop4 + +- name: Verify nm_add_domvmprop4 + assert: + that: + - nm_add_domvmprop4 is not changed + - nm_add_domvmprop4.msg == "port_encap_vlan is required when port_encap_vlan_type is provided." + +- name: Add domain vmm to site EPG4 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + micro_seg_vlan_type: 'vlan' + micro_seg_vlan: 100 + port_encap_vlan_type: 'vlan' + port_encap_vlan: 100 + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_dn: 'ansible_check' + state: present + ignore_errors: true + register: nm_add_domvmprop5 + +- name: Verify nm_add_domvmprop5 + assert: + that: + - nm_add_domvmprop5 is not changed + - nm_add_domvmprop5.msg == "enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided." + +- name: Add domain vmm to site EPG4 with AP2 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: lazy + micro_seg_vlan_type: 'vlan' + micro_seg_vlan: 100 + port_encap_vlan_type: 'vlan' + port_encap_vlan: 100 + vlan_encap_mode: static + allow_micro_segmentation: true + switch_type: 'default' + switching_mode: native + enhanced_lagpolicy_name: 'ansible_check' + state: present + ignore_errors: true + register: nm_add_domvmprop6 + +- name: Verify nm_add_domvmprop6 + assert: + that: + - nm_add_domvmprop6 is not changed + - nm_add_domvmprop6.msg == "enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided." + +# USE NON-EXISTING EPG and ANP AT TEMPLATE LEVEL +- name: Add domain 1 to non-existent site EPG5 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP5 + epg: EPG5 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + ignore_errors: true + register: nm_add_dom1e5 + +- name: Verify nm_add_dom1e5 + assert: + that: + - nm_add_dom1e5 is not changed + +# USE NON-EXISTING EPG AT TEMPLATE LEVEL +- name: Add domain 1 to non-existent site EPG5 (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG6 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + state: present + ignore_errors: true + register: nm_add_dom1e6 + +- name: Verify nm_add_dom1e6 + assert: + that: + - nm_add_dom1e6 is not changed + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for domain (check_mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for domain (normal_mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for domain (check_mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for domain (normal_mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-EXISTING SITE +- name: Non-existing site for domain (check_mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + check_mode: true + ignore_errors: true + register: cm_non_existing_site + +- name: Non-existing site for domain (normal_mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site == nm_non_existing_site + - cm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + - nm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site EPG domain association to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + ignore_errors: true + check_mode: true + register: cm_no_site_associated + +- name: Add site EPG domain association to Template 3 without any site associated (normal mode) + mso_schema_site_anp_epg_domain: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: AP1 + epg: EPG1 + domain_association_type: vmmDomain + domain_profile: 'VMware-VMM' + deployment_immediacy: lazy + resolution_immediacy: pre-provision + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml new file mode 100644 index 000000000..ec69a7c47 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml @@ -0,0 +1,1103 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure azure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Ensure aws site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Associate aws site with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + register: aaws_nm + +- name: Associate azure site with access_type not present, with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure + state: present + register: aazure_shared_nm + +- name: Ensure schema 1 with Template 1, and Template 2, Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + - { template: Template 3} + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add aws site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + when: version.current.version is version('3', '<') + +- name: Add azure site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + when: version.current.version is version('3', '<') + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Ensure ANP exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2' } + +- name: Add a new CIDR in VRF1 at site level + mso_schema_site_vrf_region_cidr: &mso_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: '{{ item }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + primary: true + state: present + loop: + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +# ADD EPGs +- name: Ensure EPGs exist + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + epg: '{{ item.epg }}' + vrf: + name: VRF1 + schema: ansible_test + template: Template 1 + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_1' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_2' } + +- name: Add Selector to EPG (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: present + register: nm_add_selector_1 + +- name: Verify nm_add_selector_1 + assert: + that: + - nm_add_selector_1 is changed + - nm_add_selector_1.previous == {} + - nm_add_selector_1.current.name == "selector_1" + - nm_add_selector_1.current.expressions == [] + +- name: Add Selector 2 to EPG (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_2_template + operator: in + value: test + state: present + register: nm_add_selector_2 + +- name: Verify nm_add_selector_2 + assert: + that: + - nm_add_selector_2 is changed + - nm_add_selector_2.previous == {} + - nm_add_selector_2.current.name == "selector_2" + - nm_add_selector_2.current.expressions[0].key == "Custom:expression_2_template" + - nm_add_selector_2.current.expressions[0].operator == "in" + - nm_add_selector_2.current.expressions[0].value == "test" + +# ADD SELECTORS to site EPG +- name: Add selector site_selector_1 to site EPG ansible_test_1 with ANP (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: present + check_mode: true + register: cm_add_site_selector_1 + +- name: Verify cm_add_site_selector_1 + assert: + that: + - cm_add_site_selector_1.current.name == "site_selector_1" + - cm_add_site_selector_1.current.expressions == [] + - cm_add_site_selector_1 is changed + - cm_add_site_selector_1.previous == {} + +- name: Add selector site_selector_1 to site EPG ansible_test_1 with ANP (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: present + register: nm_add_site_selector_1 + +- name: Verify nm_add_site_selector_1 + assert: + that: + - nm_add_site_selector_1.current.name == "site_selector_1" + - nm_add_site_selector_1.current.expressions == [] + - nm_add_site_selector_1 is changed + - nm_add_site_selector_1.previous == {} + +# Add selector 1 again +- name: Add selector site_selector_1 to site EPG ansible_test_1 with ANP again (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: present + register: nm_add_site_selector_1_again + +- name: Verify nm_add_site_selector_1_again + assert: + that: + - nm_add_site_selector_1_again is not changed + - nm_add_site_selector_1_again.current.name == "site_selector_1" == nm_add_site_selector_1_again.previous.name + - nm_add_site_selector_1_again.current.expressions == [] == nm_add_site_selector_1_again.previous.expressions + +- name: Add Selector 1 to site EPG with space in selector name (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector 1 + state: present + ignore_errors: true + register: nm_add_selector1_with_space_in_name + +- name: Verify nm_add_selector1_with_space_in_name + assert: + that: + - nm_add_selector1_with_space_in_name is not changed + - nm_add_selector1_with_space_in_name.msg == "There should not be any space in selector name." + +- name: Add Selector 2 to site EPG with space in expression type (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression 2 + operator: in + value: test + state: present + ignore_errors: true + register: nm_add_selector2_with_space_in_expression_type + +- name: Verify nm_add_selector2_with_space_in_expression_type + assert: + that: + - nm_add_selector2_with_space_in_expression_type is not changed + - nm_add_selector2_with_space_in_expression_type.msg == "There should not be any space in 'type' attribute of expression 'expression 2'" + +- name: Add Selector 2 to site EPG (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_2 + expressions: + - type: expression_2 + operator: in + value: test + state: present + check_mode: true + register: cm_add_site_selector_2 + +- name: Verify cm_add_selector_2 + assert: + that: + - cm_add_site_selector_2 is changed + - cm_add_site_selector_2.previous == {} + - cm_add_site_selector_2.current.name == "site_selector_2" + - cm_add_site_selector_2.current.expressions[0].key == "Custom:expression_2" + - cm_add_site_selector_2.current.expressions[0].operator == "in" + - cm_add_site_selector_2.current.expressions[0].value == "test" + +- name: Add Selector_2 to site EPG (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_2 + expressions: + - type: expression_2 + operator: in + value: test + state: present + register: nm_add_site_selector_2 + +- name: Verify nm_add_site_selector_2 + assert: + that: + - nm_add_site_selector_2 is changed + - nm_add_site_selector_2.previous == {} + - nm_add_site_selector_2.current.name == "site_selector_2" + - nm_add_site_selector_2.current.expressions[0].key == "Custom:expression_2" + - nm_add_site_selector_2.current.expressions[0].operator == "in" + - nm_add_site_selector_2.current.expressions[0].value == "test" + +- name: Change Selector 2 - keyExist(normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_2 + expressions: + - type: expression_5 + operator: has_key + value: test + state: present + ignore_errors: true + register: nm_change_site_selector_2_key_exist + +- name: Verify nm_change_site_selector_2_key_exist + assert: + that: + - nm_change_site_selector_2_key_exist is not changed + - nm_change_site_selector_2_key_exist.msg == "Attribute 'value' is not supported for operator 'has_key' in expression 'expression_5'" + +- name: Change Selector 2 - keyNotExist (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_2 + expressions: + - type: expression_6 + operator: does_not_have_key + value: test + state: present + ignore_errors: true + register: nm_change_site_selector_2_key_not_exist + +- name: Verify nm_change_site_selector_2_key_not_exist + assert: + that: + - nm_change_site_selector_2_key_not_exist is not changed + - nm_change_site_selector_2_key_not_exist.msg == "Attribute 'value' is not supported for operator 'does_not_have_key' in expression 'expression_6'" + +- name: Change Selector 2 - equals (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_2 + expressions: + - type: expression_6 + operator: equals + state: present + ignore_errors: true + register: nm_change_site_selector_2_equals + +- name: Verify nm_change_site_selector_2_equals + assert: + that: + - nm_change_site_selector_2_equals is not changed + - nm_change_site_selector_2_equals.msg == "Attribute 'value' needed for operator 'equals' in expression 'expression_6'" + +# Remove site ANP +- name: Remove site ANP (normal_mode) + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: absent + +- name: Query site ANP + mso_schema_site_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: query + ignore_errors: true + register: query_site_ANP + +- name: Verify query_site_ANP + assert: + that: + - query_site_ANP.msg == "ANP 'ANP' not found" + when: version.current.version is version('4.0', '<') # no error msg is returned in NDO4.0 because site will be present when template is defined + +# Query without site ANP +- name: Query site_selectors without site ANP + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + ignore_errors: true + register: query_without_site_ANP + +- name: Verify query_without_site_ANP + assert: + that: + - query_without_site_ANP is not changed + - query_without_site_ANP.msg == "Anp 'ANP' does not exist in site level." + when: version.current.version is version('4.0', '<') # no error msg is returned in NDO4.0 because site will be present when template is defined + +# - name: Add selector without ANP exist in site level +- name: Add site selector 3 without site ANP exist (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_3 + expressions: + - type: expression_3 + operator: in + value: test + state: present + register: nm_add_site_selector_3_without_anp + +- name: Verify nm_add_site_selector_3_without_anp + assert: + that: + - nm_add_site_selector_3_without_anp is changed + - nm_add_site_selector_3_without_anp.previous == {} + - nm_add_site_selector_3_without_anp.current.name == "site_selector_3" + - nm_add_site_selector_3_without_anp.current.expressions[0].key == "Custom:expression_3" + - nm_add_site_selector_3_without_anp.current.expressions[0].operator == "in" + - nm_add_site_selector_3_without_anp.current.expressions[0].value == "test" + +# Remove site level EPG +- name: Remove site EPG + mso_schema_site_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: absent + +# Query without site level EPG +- name: Query site_selectors without site EPG + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + ignore_errors: true + register: query_without_site_EPG + +- name: Verify query_without_site_EPG + assert: + that: + - query_without_site_EPG is not changed + - query_without_site_EPG.msg == "Epg 'ansible_test_1' does not exist in site level." + when: version.current.version is version('4.0', '<') # no error msg is returned in NDO4.0 because site will be present when template is defined + +- name: Add site selector 1 without site EPG exist (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: present + register: nm_add_site_selector_1_without_epg + +- name: Verify nm_add_site_selector_1_without_epg + assert: + that: + - nm_add_site_selector_1_without_epg is changed + - nm_add_site_selector_1_without_epg.previous == {} + - nm_add_site_selector_1_without_epg.current.name == "site_selector_1" + - nm_add_site_selector_1_without_epg.current.expressions == [] + +- name: Add site_selector_1 site_selector_2 site_selector_3 again + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: '{{ item.selector }}' + expressions: + - type: '{{ item.type }}' + operator: in + value: test + state: present + loop: + - {selector: 'site_selector_1', type: 'expression_1'} + - {selector: 'site_selector_2', type: 'expression_2'} + - {selector: 'site_selector_3', type: 'expression_3'} + register: nm_add_site_selectors_again + +- name: Verify nm_add_site_selectors_again + assert: + that: + - nm_add_site_selectors_again is changed + +# Query all selectors +- name: Query selectors to site EPG + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + register: query_all_site_selectors + +- name: Verify query_all_site_selectors + assert: + that: + - query_all_site_selectors is not changed + - query_all_site_selectors.current | length == 3 + - query_all_site_selectors.current[0].name == "site_selector_1" + - query_all_site_selectors.current[0].expressions[0].key == "Custom:expression_1" + - query_all_site_selectors.current[0].expressions[0].operator == "in" + - query_all_site_selectors.current[0].expressions[0].value == "test" + - query_all_site_selectors.current[1].name == "site_selector_2" + - query_all_site_selectors.current[1].expressions[0].key == "Custom:expression_2" + - query_all_site_selectors.current[1].expressions[0].operator == "in" + - query_all_site_selectors.current[1].expressions[0].value == "test" + - query_all_site_selectors.current[2].name == "site_selector_3" + - query_all_site_selectors.current[2].expressions[0].key == "Custom:expression_3" + - query_all_site_selectors.current[2].expressions[0].operator == "in" + - query_all_site_selectors.current[2].expressions[0].value == "test" + +# Query sepecific seletor to site EPG +- name: Query selector to site EPG + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + register: query_site_selector_1 + +- name: Verify query_site_selector_1 + assert: + that: + - query_site_selector_1 is not changed + - query_site_selector_1.current.name == "site_selector_1" + - query_site_selector_1.current.expressions[0].key == "Custom:expression_1" + - query_site_selector_1.current.expressions[0].operator == "in" + - query_site_selector_1.current.expressions[0].value == "test" + +- name: Remove site selector 3 (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_3 + state: absent + register: nm_remove_site_selector_3 + +- name: Verify nm_remove_site_selector_3 + assert: + that: + - nm_remove_site_selector_3 is changed + - nm_remove_site_selector_3.current == {} + +- name: Remove site selector 3 again (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_3 + state: absent + register: nm_remove_site_selector_3_again + +- name: Verify nm_remove_site_selector_3_again + assert: + that: + - nm_remove_site_selector_3_again is not changed + - nm_remove_site_selector_3_again.current == {} + +# QUERY NON-EXISTING Selector to EPG +- name: Query non-existing selector (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: non_existing_selector + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_selector + +- name: Query non-existing selector (normal mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: non_existing_selector + state: query + ignore_errors: true + register: nm_query_non_selector + +- name: Verify cm_query_non_selector and nm_query_non_selector + assert: + that: + - cm_query_non_selector is not changed + - nm_query_non_selector is not changed + - cm_query_non_selector == nm_query_non_selector + - cm_query_non_selector.msg == "Selector 'non_existing_selector' not found" + - nm_query_non_selector.msg == "Selector 'non_existing_selector' not found" + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: non_existing_epg + selector: site_selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_epg + +- name: Query non-existing EPG (normal mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: non_existing_epg + selector: site_selector_1 + state: query + ignore_errors: true + register: nm_query_non_epg + +- name: Verify query_non_epg + assert: + that: + - cm_query_non_epg is not changed + - nm_query_non_epg is not changed + - cm_query_non_epg == nm_query_non_epg + - cm_query_non_epg.msg == nm_query_non_epg.msg == "Provided EPG 'non_existing_epg' does not exist. Existing EPGs{{':'}} ansible_test_1, ansible_test_2" + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: non_existing_anp + epg: ansible_test_1 + selector: site_selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_anp + +- name: Query non-existing ANP (normal mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: non_existing_anp + epg: ansible_test_1 + selector: site_selector_1 + state: query + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - cm_query_non_anp is not changed + - nm_query_non_anp is not changed + - cm_query_non_anp == nm_query_non_anp + - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP" + +# USE A NON-EXISTING STATE +- name: Non-existing state (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: non-existing-template + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: non-existing-template + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2, Template3" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: non-existing-schema + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: non-existing-schema + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING SITE +- name: Non-existing site (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non-existing-site + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site + +- name: Non-existing site (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non-existing-site + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site == nm_non_existing_site + - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non-existing-site' is not a valid site name." + +# USE A NON-EXISTING SITE-TEMPLATE +- name: Non-existing site-template (check_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site_template + +- name: Non-existing site-template (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + state: query + ignore_errors: true + register: nm_non_existing_site_template + +- name: Verify non_existing_site_template + assert: + that: + - cm_non_existing_site_template is not changed + - nm_non_existing_site_template is not changed + - cm_non_existing_site_template == nm_non_existing_site_template + - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site-template association 'aws_{{ mso_site | default("ansible_test") }}-Template3' does not exist." + +- name: Add Selector_4 to site EPG (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_4 + expressions: + - type: ip_address + operator: has_key + state: present + ignore_errors: true + register: nm_add_site_selector_4 + +- name: Verify nm_add_site_selector_4 + assert: + that: + - nm_add_site_selector_4 is not changed + - nm_add_site_selector_4.msg == "Operator 'has_key' is not supported when expression type is 'ip_address'" + +- name: Add Selector_4 to site EPG again (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_4 + expressions: + - type: ip_address + operator: in + value: test + state: present + register: nm_add_site_selector_4_again + +- name: Verify nm_add_site_selector_4 + assert: + that: + - nm_add_site_selector_4_again is changed + - nm_add_site_selector_4_again.current.name == "site_selector_4" + +- name: Add azure site_selector_1 to site EPG (normal_mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + expressions: + - type: zone + operator: in + value: test + state: present + ignore_errors: true + register: nm_add_azure_site_selector_1 + +- name: Verify nm_add_azure_site_selector_1 + assert: + that: + - nm_add_azure_site_selector_1 is not changed + - nm_add_azure_site_selector_1.msg == "Type 'zone' is only supported for aws" + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site EPG selector to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + expressions: + - type: zone + operator: in + value: test + state: present + ignore_errors: true + check_mode: true + register: cm_no_site_associated + +- name: Add site EPG selector to Template 3 without any site associated (normal mode) + mso_schema_site_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: ANP + epg: ansible_test_1 + selector: site_selector_1 + expressions: + - type: zone + operator: in + value: test + state: present + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml new file mode 100644 index 000000000..cb50a64e6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml @@ -0,0 +1,870 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add a new site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Add BD1 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 with AP1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + state: present + +- name: Ensure Template 1 and AP1 with EPG1 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP1 with EPG3 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG3 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 with AP2 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + state: present + +- name: Ensure Template 1 and AP2 with EPG2 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP2 with EPG4 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure Template 1 and AP2 with EPG6 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG6 + bd: + name: BD1 + vrf: + name: VRF1 + state: present + +# ADD STATIC PORTS +- name: Add static port 1 to site EPG1 of AP1 (check mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: 'native' + type: port + deployment_immediacy: immediate + state: present + check_mode: true + register: cm_add_stat1e1 + +- name: Verify cm_add_stat1e1 + assert: + that: + - cm_add_stat1e1 is changed + - cm_add_stat1e1.previous == {} + - cm_add_stat1e1.current.deploymentImmediacy == 'immediate' + - cm_add_stat1e1.current.portEncapVlan == 126 + - cm_add_stat1e1.current.path == 'topology/pod-1/paths-101/pathep-[eth1/1]' + - cm_add_stat1e1.current.mode == 'native' + - cm_add_stat1e1.current.type == 'port' + +- name: Add static port 1 to site EPG1 of AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: 'native' + deployment_immediacy: immediate + state: present + register: nm_add_stat1e1 + +- name: Verify nm_add_stat1e1 + assert: + that: + - nm_add_stat1e1 is changed + - nm_add_stat1e1.previous == {} + - nm_add_stat1e1.current.deploymentImmediacy == 'immediate' + - nm_add_stat1e1.current.portEncapVlan == 126 + - nm_add_stat1e1.current.path == 'topology/pod-1/paths-101/pathep-[eth1/1]' + - nm_add_stat1e1.current.mode == 'native' + - nm_add_stat1e1.current.type == 'port' + +- name: Add static port 2 to site EPG1 of AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-2 + leaf: 102 + path: eth1/2 + vlan: 100 + mode: 'regular' + type: port + primary_micro_segment_vlan: 199 + deployment_immediacy: immediate + state: present + register: nm_add_stat2e1 + +- name: Verify nm_add_stat2e1 + assert: + that: + - nm_add_stat2e1 is changed + - nm_add_stat2e1.previous == {} + - nm_add_stat2e1.current.deploymentImmediacy == 'immediate' + - nm_add_stat2e1.current.portEncapVlan == 100 + - nm_add_stat2e1.current.path == 'topology/pod-2/paths-102/pathep-[eth1/2]' + - nm_add_stat2e1.current.mode == 'regular' + - nm_add_stat2e1.current.type == 'port' + + +- name: Add static port 3 (vpc) to site EPG1 of AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-3 + leaf: 103-104 + path: ansible_polgrp + vlan: 101 + type: vpc + mode: untagged + deployment_immediacy: lazy + state: present + register: nm_add_stat3e1 + +- name: Verify nm_add_stat3e1 + assert: + that: + - nm_add_stat3e1 is changed + - nm_add_stat3e1.previous == {} + - nm_add_stat3e1.current.deploymentImmediacy == 'lazy' + - nm_add_stat3e1.current.portEncapVlan == 101 + - nm_add_stat3e1.current.path == 'topology/pod-3/protpaths-103-104/pathep-[ansible_polgrp]' + - nm_add_stat3e1.current.mode == 'untagged' + - nm_add_stat3e1.current.type == 'vpc' + +- name: Add static port 1 to site EPG3 of AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG3 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: 'native' + type: port + deployment_immediacy: immediate + state: present + register: nm_add_stat1e3 + +- name: Verify nm_add_stat1e3 + assert: + that: + - nm_add_stat1e3 is changed + - nm_add_stat1e3.previous == {} + - nm_add_stat1e3.current.deploymentImmediacy == 'immediate' + - nm_add_stat1e3.current.portEncapVlan == 126 + - nm_add_stat1e3.current.path == 'topology/pod-1/paths-101/pathep-[eth1/1]' + - nm_add_stat1e3.current.mode == 'native' + - nm_add_stat1e3.current.type == 'port' + +- name: Add static port 2 (dpc) to EPG6 of AP2 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG6 + pod: pod-2 + leaf: 102 + path: eth1/2 + vlan: 100 + deployment_immediacy: lazy + mode: regular + type: dpc + primary_micro_segment_vlan: 199 + state: present + register: nm_add_stat2e6 + +- name: Verify nm_add_stat2e6 + assert: + that: + - nm_add_stat2e6 is changed + - nm_add_stat2e6.previous == {} + - nm_add_stat2e6.current.deploymentImmediacy == 'lazy' + - nm_add_stat2e6.current.portEncapVlan == 100 + - nm_add_stat2e6.current.microSegVlan == 199 + - nm_add_stat2e6.current.path == 'topology/pod-2/paths-102/pathep-[eth1/2]' + - nm_add_stat2e6.current.mode == 'regular' + - nm_add_stat2e6.current.type == 'dpc' + +# QUERY STATIC PORTS +- name: Query STATIC PORTS of site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + state: query + register: nm_query_statse1 + +- name: Verify nm_query_statse1 + assert: + that: + - nm_query_statse1 is not changed + +# QUERY A STATIC PORT +- name: Query static port 3 (vpc) of site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-3 + leaf: 103-104 + path: ansible_polgrp + vlan: 101 + mode: untagged + type: vpc + deployment_immediacy: immediate + state: query + register: nm_query_stat3e1 + +- name: Verify nm_query_stat3e1 + assert: + that: + - nm_query_stat3e1 is not changed + +# QUERY REMOVED STATIC PORT +- name: Add static port 2 to site EPG2 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + pod: pod-2 + leaf: 102 + path: eth1/2 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + state: present + register: nm_add_stat2e2 + +- name: Verify nm_add_stat2e2 + assert: + that: + - nm_add_stat2e2 is changed + - nm_add_stat2e2.previous == {} + - nm_add_stat2e2.current.deploymentImmediacy == 'immediate' + - nm_add_stat2e2.current.portEncapVlan == 100 + - nm_add_stat2e2.current.path == 'topology/pod-2/paths-102/pathep-[eth1/2]' + - nm_add_stat2e2.current.mode == 'regular' + - nm_add_stat2e2.current.type == 'port' + +- name: Remove static port 2 from EPG2 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + pod: pod-2 + leaf: 102 + path: eth1/2 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + state: absent + register: nm_remove_stat2e2 + +- name: Verify nm_remove_stat2e2 + assert: + that: + - nm_remove_stat2e2 is changed + +- name: Query removed static port 2 from EPG2 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + pod: pod-2 + leaf: 102 + path: eth1/2 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + state: query + ignore_errors: true + register: nm_non_existent_dom2e2 + +- name: Verify non_existing_domain + assert: + that: + - nm_non_existent_dom2e2 is not changed + - nm_non_existent_dom2e2.msg == "Static port 'topology/pod-2/paths-102/pathep-[eth1/2]' not found" + +- name: Remove static port 2 from EPG2 again(normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG2 + pod: pod-2 + leaf: 101 + path: eth1/2 + vlan: 100 + mode: regular + type: port + deployment_immediacy: immediate + state: absent + ignore_errors: true + register: nm_remove_again_stat2e2 + +- name: Verify nm_remove_again_stat2e2 + assert: + that: + - nm_remove_again_stat2e2 is not changed + +# ADD EXISTING STATIC PORT +- name: Add static port 1 to site EPG1 again (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: 'native' + type: port + deployment_immediacy: immediate + state: present + register: nm_add_stat1e1_2 + +- name: Verify nm_add_stat1e1_2 + assert: + that: + - nm_add_stat1e1_2 is not changed + +# ADD STATIC PORT WITH NO STATE +- name: Add static port 1 to site EPG1 again with no state (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: native + type: port + deployment_immediacy: immediate + ignore_errors: true + register: nm_add_stateless_stat1e1_2 + +- name: Verify nm_add_stateless_stat1e1_2 + assert: + that: + - nm_add_stateless_stat1e1_2 is not changed + +# ADD STATIC FEX PORT +- name: Add static fex port to site EPG1 with AP1 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-4 + leaf: 101 + path: eth1/1 + vlan: 126 + fex: 151 + type: port + mode: native + deployment_immediacy: lazy + state: present + register: nm_add_statfex + +- name: Verify nm_add_statfex + assert: + that: + - nm_add_statfex is changed + - nm_add_statfex.previous == {} + - nm_add_statfex.current.deploymentImmediacy == 'lazy' + - nm_add_statfex.current.portEncapVlan == 126 + - nm_add_statfex.current.path == 'topology/pod-4/paths-101/extpaths-151/pathep-[eth1/1]' + - nm_add_statfex.current.mode == 'native' + - nm_add_statfex.current.type == 'port' + +# VERIFY NON EXISTENT DEPLOYMENT IMMEDIACY +- name: Add static port 1 to site EPG4 with AP2 with no deployment immediacy (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + pod: pod-4 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + state: present + register: nm_add_stat_di + +- name: Verify nm_add_stat_di + assert: + that: + - nm_add_stat_di.current.deploymentImmediacy == 'lazy' + +# VERIFY NON EXISTENT MODE +- name: Add static port 1 to site EPG4 with AP2 with no mode (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP2 + epg: EPG4 + pod: pod-4 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + deployment_immediacy: lazy + state: present + register: nm_add_stat_mode + +- name: Verify nm_add_stat_mode + assert: + that: + - nm_add_stat_mode.current.mode == 'untagged' + +# USE NON-EXISTING EPG and ANP AT TEMPLATE LEVEL +- name: Add static port 1 to non-existent site EPG5 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP5 + epg: EPG5 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: native + type: port + deployment_immediacy: immediate + state: present + ignore_errors: true + register: nm_add_stat1e5 + +- name: Verify nm_add_stat1e5 + assert: + that: + - nm_add_stat1e5 is not changed + +# USE NON-EXISTING EPG AT TEMPLATE LEVEL +- name: Add static port 1 to non-existent site EPG5 (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG6 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: native + deployment_immediacy: immediate + state: present + ignore_errors: true + register: nm_add_stat1e6 + +- name: Verify nm_add_stat1e6 + assert: + that: + - nm_add_stat1e6 is not changed + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for static port (check_mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + mode: native + type: port + deployment_immediacy: immediate + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for static port (normal_mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for static port (check_mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for static port (normal_mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-EXISTING SITE +- name: Non-existing site for static port (check_mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + check_mode: true + ignore_errors: true + register: cm_non_existing_site + +- name: Non-existing site for static port (normal_mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site == nm_non_existing_site + - cm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + - nm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site EPG static port association to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + ignore_errors: true + check_mode: true + register: cm_no_site_associated + +- name: Add site EPG static port association to Template 3 without any site associated (normal mode) + mso_schema_site_anp_epg_staticport: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + anp: AP1 + epg: EPG1 + pod: pod-1 + leaf: 101 + path: eth1/1 + vlan: 126 + type: port + mode: native + deployment_immediacy: immediate + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml new file mode 100644 index 000000000..a7de528ea --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml @@ -0,0 +1,749 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + ignore_errors: true + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1, and Template 2, Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + - { template: Template 3} + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add physical site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{item.template}}' + state: present + loop: + - { template: Template 1} + - { template: Template 2} + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + layer3_multicast: true + state: present + +- name: Ensure ansible_test_1 BD does not exist + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + vrf: + name: VRF1 + state: absent + +- name: Add template BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + vrf: + name: VRF1 + state: present + register: nm_add_bd + +- name: Verify nm_add_bd + assert: + that: + - nm_add_bd is changed + - nm_add_bd.previous == {} + - nm_add_bd.current.name == "ansible_test_1" + - nm_add_bd.current.vrfRef.templateName == "Template1" + - nm_add_bd.current.vrfRef.vrfName == "VRF1" + +- name: Add template BD 2 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + vrf: + name: VRF1 + template: Template 1 + state: present + register: nm_add_bd_2 + +- name: Verify nm_add_bd_2 + assert: + that: + - nm_add_bd_2 is changed + - nm_add_bd_2.previous == {} + - nm_add_bd_2.current.name == "ansible_test_2" + - nm_add_bd_2.current.vrfRef.templateName == "Template1" + - nm_add_bd_2.current.vrfRef.vrfName == "VRF1" + +- name: Add template BD 3 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_3 + vrf: + name: VRF1 + template: Template 1 + state: present + register: nm_add_bd_3 + +- name: Verify nm_add_bd_3 + assert: + that: + - nm_add_bd_3 is changed + - nm_add_bd_3.previous == {} + - nm_add_bd_3.current.name == "ansible_test_3" + - nm_add_bd_3.current.vrfRef.templateName == "Template1" + - nm_add_bd_3.current.vrfRef.vrfName == "VRF1" + +- name: Add template BD 4 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_4 + vrf: + name: VRF1 + template: Template 1 + state: present + register: nm_add_bd_4 + +- name: Verify nm_add_bd_4 + assert: + that: + - nm_add_bd_4 is changed + - nm_add_bd_4.previous == {} + - nm_add_bd_4.current.name == "ansible_test_4" + - nm_add_bd_4.current.vrfRef.templateName == "Template1" + - nm_add_bd_4.current.vrfRef.vrfName == "VRF1" + +- name: Add site BD (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: present + check_mode: true + register: cm_add_site_bd + +- name: Verify cm_add_site_bd + assert: + that: + - cm_add_site_bd.current.bdRef.bdName == "ansible_test_1" + - cm_add_site_bd.current.bdRef.templateName == "Template1" + - cm_add_site_bd.current.hostBasedRouting == false + +- name: Verify cm_add_site_bd + assert: + that: + - cm_add_site_bd is changed + - cm_add_site_bd.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add site BD (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: present + register: nm_add_site_bd + +- name: Verify nm_add_site_bd + assert: + that: + - nm_add_site_bd.current.bdRef.bdName == "ansible_test_1" + - nm_add_site_bd.current.bdRef.templateName == "Template1" + - nm_add_site_bd.current.hostBasedRouting == false + - cm_add_site_bd.current.bdRef.schemaId == nm_add_site_bd.current.bdRef.schemaId + +- name: Verify nm_add_site_bd + assert: + that: + - nm_add_site_bd is changed + - nm_add_site_bd.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add site BD again (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: present + check_mode: true + register: cm_add_site_bd_again + +- name: Add site BD again (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: present + register: nm_add_site_bd_again + +- name: Verify cm_add_site_bd_again and nm_add_site_bd_again + assert: + that: + - cm_add_site_bd_again is not changed + - nm_add_site_bd_again is not changed + - cm_add_site_bd_again.previous.bdRef.bdName == nm_add_site_bd_again.previous.bdRef.bdName == cm_add_site_bd_again.current.bdRef.bdName == nm_add_site_bd_again.current.bdRef.bdName == "ansible_test_1" + +- name: Change site BD (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + host_route: true + state: present + check_mode: true + register: cm_change_site_bd + +- name: Change site BD (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + host_route: true + state: present + register: nm_change_site_bd + +- name: Verify cm_change_site_bd and nm_change_site_bd + assert: + that: + - cm_change_site_bd is changed + - nm_change_site_bd is changed + - cm_change_site_bd.previous.bdRef == cm_change_site_bd.current.bdRef + - nm_change_site_bd.previous.bdRef == nm_change_site_bd.current.bdRef + - cm_change_site_bd.previous.hostBasedRouting == false + - cm_change_site_bd.current.hostBasedRouting == true + - nm_change_site_bd.previous.hostBasedRouting == false + - nm_change_site_bd.current.hostBasedRouting == true + +- name: Add site BD with host_route (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + host_route: true + state: present + check_mode: true + register: cm_add_site_bd_with_host_route + +- name: Add site BD with host_route (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + host_route: true + state: present + register: nm_add_site_bd_with_host_route + +- name: Verify cm_add_site_bd_with_host_route and nm_add_site_bd_with_host_route + assert: + that: + - cm_add_site_bd_with_host_route is changed + - nm_add_site_bd_with_host_route is changed + - cm_add_site_bd_with_host_route.current.hostBasedRouting == true + - nm_add_site_bd_with_host_route.current.hostBasedRouting == true + +- name: Verify cm_add_site_bd_with_host_route and nm_add_site_bd_with_host_route + assert: + that: + - cm_add_site_bd_with_host_route.previous == {} + - nm_add_site_bd_with_host_route.previous == {} + when: version.current.version is version('4.0', '<') # already present when template is defined, thus previous exists + +- name: Add site BD 3 (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_3 + host_route: true + state: present + register: nm_add_site_bd_3 + +- name: Verify nm_add_site_bd_3 + assert: + that: + - nm_add_site_bd_3 is changed + - nm_add_site_bd_3.current.hostBasedRouting == true + - nm_add_site_bd_3.current.bdRef.bdName == "ansible_test_3" + - nm_add_site_bd_3.current.bdRef.templateName == "Template1" + +- name: Verify nm_add_site_bd_3 + assert: + that: + - nm_add_site_bd_3.previous == {} + when: version.current.version is version('4.0', '<') # already be present when template is defined, thus previous exists + +- name: Add site BD 4 (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_4 + svi_mac: 00:22:23:F1:21:F9 + state: present + register: nm_add_site_bd_4 + +- name: Verify nm_add_site_bd_4 + assert: + that: + - nm_add_site_bd_4 is changed + - nm_add_site_bd_4.current.mac == "00:22:23:F1:21:F9" + - nm_add_site_bd_4.current.bdRef.bdName == "ansible_test_4" + - nm_add_site_bd_4.current.bdRef.templateName == "Template1" + +- name: Verify nm_add_site_bd_4 + assert: + that: + - nm_add_site_bd_4.previous == {} + when: version.current.version is version('4.0', '<') # already be present when template is defined, thus previous exists + +- name: Query a specific BD (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + state: query + check_mode: true + register: cm_query_bd_2 + +- name: Query a specific BD (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + state: query + register: nm_query_bd_2 + +- name: Verify cm_query_bd_2 and nm_query_bd_2 + assert: + that: + - cm_query_bd_2 is not changed + - nm_query_bd_2 is not changed + - cm_query_bd_2.current.bdRef.bdName == "ansible_test_2" == nm_query_bd_2.current.bdRef.bdName + - cm_query_bd_2.current.bdRef.schemaId == nm_query_bd_2.current.bdRef.schemaId + - cm_query_bd_2.current.bdRef.templateName == nm_query_bd_2.current.bdRef.templateName == "Template2" + - cm_query_bd_2.current.hostBasedRouting == nm_query_bd_2.current.hostBasedRouting == true + +- name: Query all BDs (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: query + check_mode: true + register: cm_query_all_bd + +- name: Query all BDs (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: query + register: nm_query_all_bd + +- name: Verify cm_query_all_bd and cm_query_all_bd + assert: + that: + - cm_query_all_bd is not changed + - nm_query_all_bd is not changed + - cm_query_all_bd.current[0].bdRef.bdName == nm_query_all_bd.current[0].bdRef.bdName == "ansible_test_1" + - cm_query_all_bd.current[0].bdRef.schemaId == nm_query_all_bd.current[0].bdRef.schemaId + - cm_query_all_bd.current[0].bdRef.templateName == nm_query_all_bd.current[0].bdRef.templateName == "Template1" + - cm_query_all_bd.current[1].bdRef.bdName == nm_query_all_bd.current[1].bdRef.bdName == "ansible_test_3" + - cm_query_all_bd.current[1].bdRef.schemaId == nm_query_all_bd.current[1].bdRef.schemaId + - cm_query_all_bd.current[1].bdRef.templateName == nm_query_all_bd.current[1].bdRef.templateName == "Template1" + +- name: Remove BD 2 (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + state: absent + check_mode: true + register: cm_remove_site_bd_2 + +- name: Remove BD 2 (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + state: absent + register: nm_remove_site_bd_2 + +- name: Verify cm_remove_site_bd_2 and nm_remove_site_bd_2 + assert: + that: + - cm_remove_site_bd_2 is changed + - nm_remove_site_bd_2 is changed + - cm_remove_site_bd_2.previous.bdRef.bdName == nm_remove_site_bd_2.previous.bdRef.bdName == "ansible_test_2" + - cm_remove_site_bd_2.previous.bdRef.schemaId == nm_remove_site_bd_2.previous.bdRef.schemaId + - cm_remove_site_bd_2.previous.bdRef.templateName == nm_remove_site_bd_2.previous.bdRef.templateName == "Template2" + - cm_remove_site_bd_2.current == nm_remove_site_bd_2.current == {} + +- name: Remove BD 2 again(normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + state: absent + register: nm_remove_site_bd_2_again + +- name: Verify nm_remove_site_bd_2_again + assert: + that: + - nm_remove_site_bd_2_again is not changed + - nm_remove_site_bd_2_again.previous == nm_remove_site_bd_2_again.current == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Query site without BD (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: query + register: nm_query_without_bd + +- name: Verify nm_query_without_bd + assert: + that: + - nm_query_without_bd is not changed + - nm_query_without_bd.current == [] + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + +# QUERY NON-EXISTING BD +- name: Query non-existing BD (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: non_existing_bd + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_bd + +- name: Query non-existing BD (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: non_existing_bd + state: query + ignore_errors: true + register: nm_query_non_bd + +- name: Verify cm_query_non_bd and nm_query_non_bd + assert: + that: + - cm_query_non_bd is not changed + - nm_query_non_bd is not changed + - cm_query_non_bd.msg == nm_query_non_bd.msg == "BD 'non_existing_bd' not found" + +# USE NON-EXISTING STATE +- name: non_existing_state state (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: non_existing_state + ignore_errors: true + register: cm_non_existing_state + +- name: non_existing_state state (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: non_existing_state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify cm_non_existing_state and nm_non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state" + +# USE A NON_EXISTING_TEMPLATE +- name: non_existing_template (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + bd: ansible_test_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: non_existing_template (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + bd: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify cm_non_existing_template and nm_non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2, Template3" + +# USE A NON_EXISTING_SCHEMA +- name: non_existing_schema (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: non_existing_schema (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify cm_non_existing_schema and nm_non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON_EXISTING_SITE +- name: non_existing_site (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non_existing_site + template: Template 1 + bd: ansible_test_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site + +- name: non_existing_site (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non_existing_site + template: Template 1 + bd: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_site + +- name: Verify cm_non_existing_site and nm_non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name." + +# USE A NON_EXISTING_SITE_TEMPLATE +- name: non_existing_site_template (check_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + bd: ansible_test_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site_template + +- name: non_existing_site_template (normal_mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + bd: ansible_test_1 + state: query + ignore_errors: true + register: nm_non_existing_site_template + +- name: Verify cm_non_existing_site_template and nm_non_existing_site_template + assert: + that: + - cm_non_existing_site_template is not changed + - nm_non_existing_site_template is not changed + - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site-template association 'ansible_test-Template3' does not exist." + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site BD to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + bd: ansible_test_1 + state: present + check_mode: true + ignore_errors: true + register: cm_no_site_associated + +- name: Add site BD to Template 3 without any site associated (normal mode) + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 3 + bd: ansible_test_1 + state: present + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml new file mode 100644 index 000000000..1e1914522 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml @@ -0,0 +1,447 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + ignore_errors: true + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema with Template1 and Template2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template1} + - { template: Template2} + +- name: Ensure schema 2 with Template3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template3 + state: present + +- name: Add physical site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: present + +- name: Add physical site to a schema2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF1 + state: present + +- name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + vrf: VRF2 + state: present + +- name: Ensure VRF3 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + vrf: VRF3 + state: present + +- name: Add template BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + vrf: + name: VRF1 + state: present + register: nm_add_bd + +- name: Verify nm_add_bd + assert: + that: + - nm_add_bd is changed + - nm_add_bd.previous == {} + - nm_add_bd.current.name == "ansible_test_1" + - nm_add_bd.current.vrfRef.templateName == "Template1" + - nm_add_bd.current.vrfRef.vrfName == "VRF1" + +- name: Add a new L3out + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + state: present + register: nm_add_l3out + +- name: Add a new L3out2 in different template + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + l3out: L3out2 + vrf: + name: VRF2 + state: present + register: nm_add_l3out + +- name: Add a new L3out3 in different schema + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + l3out: L3out3 + vrf: + name: VRF3 + state: present + register: nm_add_l3out + +- name: Verify nm_add_l3out + assert: + that: + - nm_add_l3out is changed + - nm_add_l3out.previous == {} + +- name: Add BD to site + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: present + register: nm_add_site_bd + +- name: Verify nm_add_site_bd + assert: + that: + - nm_add_site_bd.current.bdRef.bdName == "ansible_test_1" + - nm_add_site_bd.current.bdRef.templateName == "Template1" + - nm_add_site_bd.current.hostBasedRouting == false + +- name: Verify nm_add_site_bd + assert: + that: + - nm_add_site_bd is changed + - nm_add_site_bd.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + +- name: Add l3out to BD Site + mso_schema_site_bd_l3out: &site_bd_l3out_again + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + l3out: + name: L3out1 + state: present + register: nm_bd_site_l3out + +- name: Add l3out to BD Site again + mso_schema_site_bd_l3out: + <<: *site_bd_l3out_again + register: nm_bd_site_l3out_again + +- name: Verify nm_bd_site_l3out + assert: + that: + - nm_bd_site_l3out is changed + - nm_bd_site_l3out.previous == {} + - nm_bd_site_l3out.current.l3outName == "L3out1" + - nm_bd_site_l3out_again is not changed + +- name: Query a specific BD site l3out + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + l3out: + name: L3out1 + state: query + register: query_result + +- name: Verify query_result + assert: + that: + - query_result is not changed + - nm_bd_site_l3out.current.l3outName == "L3out1" + +- name: Add l3out2 from different template to BD Site + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + l3out: + name: L3out2 + template: Template2 + state: present + register: nm_bd_site_l3out2 + +- name: Add l3out3 from different schema to BD Site + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + l3out: + name: L3out3 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + state: present + register: nm_bd_site_l3out3 + +- name: Verify nm_bd_site_l3out2 and nm_bd_site_l3out3 + assert: + that: + - nm_bd_site_l3out2 is changed + - nm_bd_site_l3out2.previous == {} + - nm_bd_site_l3out2.current.l3outName == "L3out2" + - nm_bd_site_l3out3 is changed + - nm_bd_site_l3out3.previous == {} + - nm_bd_site_l3out3.current.l3outName == "L3out3" + +- name: Query all BD site l3outs + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current.0.l3outName == "L3out1" + - query_all.current.1.l3outName == "L3out2" + - query_all.current.2.l3outName == "L3out3" + +# Checking error conditions +- name: Use non_existing template + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + bd: ansible_test_1 + l3out: + name: L3out1 + state: query + ignore_errors: true + register: non_existing_template + +- name: Use non_existing BD + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: BD1 + state: query + ignore_errors: true + register: non_existing_bd + +- name: Query non_existing BD site L3out + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + l3out: + name: non_existing_L3out + state: query + ignore_errors: true + register: non_existing_l3out + +- name: Verify error query + assert: + that: + - non_existing_template is not changed + - non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2" + - non_existing_bd is not changed + - non_existing_bd.msg == "Provided BD 'BD1' not matching existing bd(s){{':'}} ansible_test_1" + - non_existing_l3out is not changed + - non_existing_l3out.msg == "L3out 'non_existing_L3out' not found" + +# Check addition of l3out to Site BD without adding BD to site +- name: Remove l3out from BD Site + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + l3out: + name: L3out1 + state: absent + register: remove_bd_site_l3out + +- name: Verify remove_bd_site_l3out + assert: + that: + - remove_bd_site_l3out is changed + - remove_bd_site_l3out.previous.l3outName == "L3out1" + - remove_bd_site_l3out.current == {} + +- name: Remove BD from site + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: absent + register: nm_remove_site_bd + +- name: Verify nm_remove_site_bd + assert: + that: + - nm_remove_site_bd is changed + - nm_remove_site_bd.previous.bdRef.bdName == "ansible_test_1" + - nm_remove_site_bd.previous.bdRef.templateName == "Template1" + - nm_remove_site_bd.current == {} + +- name: Remove template BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: absent + register: nm_remove_bd + +- name: Verify nm_remove_bd + assert: + that: + - nm_remove_bd is changed + - nm_remove_bd.current == {} + +- name: Add new template BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_bd + vrf: + name: VRF1 + state: present + register: nm_add_bd_template + +- name: Verify nm_add_bd_template + assert: + that: + - nm_add_bd_template is changed + - nm_add_bd_template.previous == {} + +- name: Add a new l3 out to BD (BD not associated to Site) + mso_schema_site_bd_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_bd + l3out: + name: L3out1 + state: present + register: add_bd_site_l3out + +- name: Verify add_bd_site_l3out + assert: + that: + - add_bd_site_l3out is changed + - add_bd_site_l3out.previous == {} + - add_bd_site_l3out.current.l3outName == "L3out1" diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml new file mode 100644 index 000000000..1f6f94c02 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml @@ -0,0 +1,665 @@ +# Test code for the MSO modules +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template1, Template2, Template4 and Template5 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template1 + - Template2 + - Template4 + - Template5 + +- name: Ensure schema 2 with Template3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template3 + state: present + +- name: Add physical site to templates + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{ item }}' + state: present + loop: + - Template1 + - Template2 + - Template5 + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF1 + layer3_multicast: true + state: present + +- name: Add template BD to Template1 for create in site subnet + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_site_bd_from_subnet + layer2_stretch: false + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + +- name: Add template BD to Template3 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + bd: ansible_test_3 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_add_bd_template_3 + +- name: Add template BD to Template2 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + bd: ansible_test_2 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_add_bd_template_2 + +- name: Add template BD to Template4 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template4 + bd: ansible_test_4 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_add_bd_template_4 + +- name: Add template BD to Template1 without disabling layer2_stretch + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + vrf: + name: VRF1 + state: present + register: nm_add_bd + +- name: Add site BD + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: present + register: nm_add_site_bd + +- name: Add site BD subnet with layer2_stretch enabled + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: present + ignore_errors: true + register: add_site_bd_subnet_with_l2Stretch_enabled + +- name: Verify add_site_bd_subnet_with_l2Stretch_enabled + assert: + that: + - add_site_bd_subnet_with_l2Stretch_enabled.msg == "The l2Stretch of template bd should be false in order to create a site bd subnet. Set l2Stretch as false using mso_schema_template_bd" + +- name: Disable layer2_stretch in template BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + layer2_stretch: false + vrf: + name: VRF1 + state: present + register: nm_add_bd + +- name: Add site BD subnet with layer2_stretch disabled (check_mode) + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: present + check_mode: true + register: cm_add_site_bd_subnet + +- name: Verify cm_add_site_bd_subnet + assert: + that: + - cm_add_site_bd_subnet is changed + - cm_add_site_bd_subnet.previous == {} + - cm_add_site_bd_subnet.current.ip == "10.1.0.1/16" + - cm_add_site_bd_subnet.current.scope == "private" + - cm_add_site_bd_subnet.current.description == "10.1.0.1/16" + - cm_add_site_bd_subnet.current.shared == False + - cm_add_site_bd_subnet.current.noDefaultGateway == False + - cm_add_site_bd_subnet.current.querier == False + +- name: Add site BD subnet with layer2_stretch disabled (normal_mode) + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: present + register: nm_add_site_bd_subnet + +- name: Verify nm_add_site_bd_subnet + assert: + that: + - nm_add_site_bd_subnet is changed + - nm_add_site_bd_subnet.previous == {} + - nm_add_site_bd_subnet.current.ip == "10.1.0.1/16" + - nm_add_site_bd_subnet.current.scope == "private" + - nm_add_site_bd_subnet.current.description == "10.1.0.1/16" + - nm_add_site_bd_subnet.current.shared == False + - nm_add_site_bd_subnet.current.noDefaultGateway == False + - nm_add_site_bd_subnet.current.querier == False + +- name: Add site BD subnet again + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: present + register: nm_add_site_bd_subnet_again + +- name: Verify nm_add_site_bd_subnet_again + assert: + that: + - nm_add_site_bd_subnet_again is not changed + +- name: Add another site BD subnet + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.10.10.1/16 + description: another subnet + scope: public + shared: true + no_default_gateway: true + querier: true + state: present + register: nm_add_another_site_bd_subnet + +- name: Verify nm_add_another_site_bd_subnet + assert: + that: + - nm_add_another_site_bd_subnet is changed + - nm_add_another_site_bd_subnet.previous == {} + - nm_add_another_site_bd_subnet.current.description == "another subnet" + - nm_add_another_site_bd_subnet.current.scope == "public" + - nm_add_another_site_bd_subnet.current.shared == true + - nm_add_another_site_bd_subnet.current.noDefaultGateway == true + - nm_add_another_site_bd_subnet.current.querier == true + +- name: Add BD ansible_test_5 to Schema1, template5 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template5 + bd: ansible_test_5 + layer2_stretch: false + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + +- name: Add site BD5 + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template5 + bd: ansible_test_5 + state: present + +- name: Add site BD5 subnet with layer2_stretch disabled (normal_mode) + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template5 + bd: ansible_test_5 + subnet: 10.1.0.5/16 + is_virtual_ip: true + scope: public + shared: true + no_default_gateway: true + querier: true + primary: true + state: present + register: nm_add_site_bd_subnet5 + +- name: Verify nm_add_site_bd_subnet5 for a version that's < 3.1 + assert: + that: + - nm_add_site_bd_subnet5 is changed + - nm_add_site_bd_subnet5.previous == {} + - nm_add_site_bd_subnet5.current.ip == "10.1.0.5/16" + - nm_add_site_bd_subnet5.current.scope == "public" + - nm_add_site_bd_subnet5.current.description == "10.1.0.5/16" + - nm_add_site_bd_subnet5.current.shared == True + - nm_add_site_bd_subnet5.current.noDefaultGateway == True + - nm_add_site_bd_subnet5.current.querier == True + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_add_site_bd_subnet5 for a version that's >= 3.1 + assert: + that: + - nm_add_site_bd_subnet5 is changed + - nm_add_site_bd_subnet5.previous == {} + - nm_add_site_bd_subnet5.current.ip == "10.1.0.5/16" + - nm_add_site_bd_subnet5.current.scope == "public" + - nm_add_site_bd_subnet5.current.description == "10.1.0.5/16" + - nm_add_site_bd_subnet5.current.shared == True + - nm_add_site_bd_subnet5.current.noDefaultGateway == True + - nm_add_site_bd_subnet5.current.querier == True + - nm_add_site_bd_subnet5.current.virtual == True + when: version.current.version is version('3.1.1g', '>=') + +- name: Verify nm_add_site_bd_subnet5 for a version that's >= 3.1.1h + assert: + that: + - nm_add_site_bd_subnet5.current.primary == True + when: version.current.version is version('3.1.1h', '>=') + +- name: Add site BD subnet with non existing site bd (normal_mode) + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_site_bd_from_subnet + subnet: 10.1.0.1/16 + state: present + register: nm_add_site_bd_subnet_and_site_bd + +- name: Verify nm_add_site_bd_subnet_and_site_bd + assert: + that: + - nm_add_site_bd_subnet_and_site_bd is changed + - nm_add_site_bd_subnet_and_site_bd.previous == {} + - nm_add_site_bd_subnet_and_site_bd.current.ip == "10.1.0.1/16" + - nm_add_site_bd_subnet_and_site_bd.current.scope == "private" + - nm_add_site_bd_subnet_and_site_bd.current.description == "10.1.0.1/16" + - nm_add_site_bd_subnet_and_site_bd.current.shared == False + - nm_add_site_bd_subnet_and_site_bd.current.noDefaultGateway == False + - nm_add_site_bd_subnet_and_site_bd.current.querier == False + +- name: Query site bd + mso_schema_site_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_site_bd_from_subnet + state: query + register: query_site_bd + +- name: Verify query_site_bd + assert: + that: + - query_site_bd.current.bdRef.bdName == "ansible_test_site_bd_from_subnet" + +- name: Add site BD subnet with now existing site bd again (normal_mode) + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_site_bd_from_subnet + subnet: 10.1.0.1/16 + state: present + register: nm_add_site_bd_subnet_and_site_bd_again + +- name: Verify nm_add_site_bd_subnet_and_site_bd_again + assert: + that: + - nm_add_site_bd_subnet_and_site_bd_again is not changed + +- name: Query all subnets + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current | length == 2 + - query_all.current.0.ip == "10.1.0.1/16" + - query_all.current.1.ip == "10.10.10.1/16" + +- name: Query a specific site BD subnet + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: query + register: query_subnet + +- name: Verify query_subnet + assert: + that: + - query_subnet is not changed + - query_subnet.current.ip == "10.1.0.1/16" + +- name: Query a specific site BD5 subnet + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template5 + bd: ansible_test_5 + subnet: 10.1.0.5/16 + state: query + register: query_subnet5 + +- name: Verify query_subnet5 for version before 3.1 + assert: + that: + - query_subnet5 is not changed + - query_subnet5.current.ip == "10.1.0.5/16" + when: version.current.version is version('3.1.1g', '<') + +- name: Verify query_subnet5 for 3.1 version and later + assert: + that: + - query_subnet5 is not changed + - query_subnet5.current.ip == "10.1.0.5/16" + - query_subnet5.current.virtual == true + when: version.current.version is version('3.1.1g', '>=') + +- name: Verify query_subnet5 for 3.1.1h version and later + assert: + that: + - query_subnet5.current.primary == true + when: version.current.version is version('3.1.1h', '>=') + +- name: Remove a site BD subnet + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: absent + register: rm_subnet + +- name: Verify rm_subnet + assert: + that: + - rm_subnet is changed + - rm_subnet.current == {} + - rm_subnet.previous.ip == "10.1.0.1/16" + +- name: Remove the site BD subnet again + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: absent + register: rm_subnet_again + +- name: Verify rm_subnet_again + assert: + that: + - rm_subnet_again is not changed + - rm_subnet_again.previous == rm_subnet_again.current == {} + +- name: Remove a site BD 5 subnet + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template5 + bd: ansible_test_5 + subnet: 10.1.0.5/16 + state: absent + register: rm_subnet5 + +- name: Verify rm_subnet5 + assert: + that: + - rm_subnet5 is changed + - rm_subnet5.current == {} + - rm_subnet5.previous.ip == "10.1.0.5/16" + +# Use non_existing_schema +- name: Query subnet by non_existing_schema + mso_schema_site_bd_subnet: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: query + ignore_errors: true + register: non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# Use non_existing_template +- name: Query subnet by non_existing_template + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + bd: ansible_test_1 + subnet: 10.1.0.1/16 + state: query + ignore_errors: true + register: non_existing_template + +- name: Verify non_existing_template + assert: + that: + - non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2, Template4, Template5" + +# Use non_existing_template_bd +- name: Query subnet by non_existing_template_bd + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: non_existing_template_bd + subnet: 10.1.0.1/16 + state: query + ignore_errors: true + register: non_existing_template_bd + +- name: Verify non_existing_template_bd + assert: + that: + - non_existing_template_bd.msg == "Provided BD 'non_existing_template_bd' not matching existing bd(s){{':'}} ansible_test_site_bd_from_subnet, ansible_test_1" + +# Use template without site associated +- name: Query with no site associated to template + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + bd: ansible_test_3 + subnet: 10.1.0.1/16 + state: query + ignore_errors: true + register: template_without_sites + +- name: Verify template_without_sites + assert: + that: + - template_without_sites.msg == "No sites associated with schema 'ansible_test_2'. Associate the site with the schema using (M) mso_schema_site." + +# Use non_existing_subnet +- name: Query with non_existing_subnet + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: non_existing_subnet + state: query + ignore_errors: true + register: non_existing_subnet + +- name: Verify non_existing_subnet + assert: + that: + - non_existing_subnet.msg.startswith("Provided subnet 'non_existing_subnet' not matching existing site bd subnet(s){{':'}}") + +# Use non_existing_site_template_association +- name: Query with non_existing_site_template_association + mso_schema_site_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template4 + bd: ansible_test_4 + subnet: 10.1.0.1/16 + state: query + ignore_errors: true + register: non_existing_site_template_association + +- name: Verify non_existing_site_template_association + assert: + that: + - non_existing_site_template_association.msg == "Provided site 'ansible_test' not associated with template 'Template4'. Site is currently associated with template(s){{':'}} Template1, Template2, Template5" + +- name: Remove schemas for next ci test case + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml new file mode 100644 index 000000000..5b8c799db --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml @@ -0,0 +1,833 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + ignore_errors: true + +- name: Associate non-cloud site with ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + register: add_ncs + +- name: Verify add_ncs + assert: + that: + - add_ncs is not changed + +- name: Remove a site from a schema with Template1 and Template2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{ item }}' + state: absent + ignore_errors: true + loop: + - Template2 + - Template1 + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +# Ensure pre requisites exist +- name: Ensure schema 1 with Template1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template1 + - Template2 + - Template3 + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF1 + state: present + +- name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + vrf: VRF2 + state: present + +- name: Ensure L3Out1 Exists + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: + name: VRF1 + l3out: L3out1 + state: present + +- name: Ensure L3Out2 Exists + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + vrf: + name: VRF2 + l3out: L3out2 + state: present + +# ADD external EPG to template +- name: Add external EPG at template level(check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: + name: L3out1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + check_mode: true + register: cm_add_epg + +- name: Verify cm_add_epg + assert: + that: + - cm_add_epg is changed + - cm_add_epg.previous == {} + - cm_add_epg.current.name == "ansible_test_1" + - cm_add_epg.current.vrfRef.templateName == "Template1" + - cm_add_epg.current.vrfRef.vrfName == "VRF1" + +- name: Add external EPG at template level(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: + name: L3out1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_add_epg + +- name: Verify nm_add_epg + assert: + that: + - nm_add_epg is changed + - nm_add_epg.previous == {} + - nm_add_epg.current.name == "ansible_test_1" + - nm_add_epg.current.vrfRef.templateName == "Template1" + - nm_add_epg.current.vrfRef.vrfName == "VRF1" + - cm_add_epg.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId + +# Add External EPG to Site when MSO version >= 3.3 +- name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + # Associate site to schema/template after creating External EPG + - name: Add non-cloud site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: present + register: add_site + + - name: Verify add_site + assert: + that: + - add_site.current.siteId is match ("[0-9a-zA-Z]*") + - add_site.current.templateName == "Template1" + + - name: Add site L3Out (normal_mode) + mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: '{{item.l3out}}' + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + loop: + - { l3out: L3out1} + + # ADD External EPGs to site + - name: ADD External EPG1 to site (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: present + check_mode: true + register: cm_add_epg + + - name: Verify cm_add_epg + assert: + that: + - cm_add_epg.current.externalEpgRef.externalEpgName == "ansible_test_1" + - cm_add_epg.current.externalEpgRef.templateName == "Template1" + + - name: Verify cm_add_epg + assert: + that: + - cm_add_epg is changed + - cm_add_epg.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + - name: Add external EPG to site (normal mode) + mso_schema_site_external_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: present + register: nm_add_epg + + - name: Verify nm_add_epg + assert: + that: + - nm_add_epg.current.externalEpgRef.externalEpgName == "ansible_test_1" + - nm_add_epg.current.externalEpgRef.templateName == "Template1" + - cm_add_epg.current.externalEpgRef.schemaId == nm_add_epg.current.externalEpgRef.schemaId + + - name: Verify nm_add_epg + assert: + that: + - nm_add_epg is changed + - nm_add_epg.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + - name: ADD External EPG1 to site again + mso_schema_site_external_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: present + register: add_epg_again + + - name: Verify add_epg_again + assert: + that: + - add_epg_again is not changed + - add_epg_again.current.externalEpgRef.externalEpgName == "ansible_test_1" + - add_epg_again.current.externalEpgRef.templateName == "Template1" + + # QUERY ALL EPG + - name: Query all external EPGs in site (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: query + check_mode: true + register: cm_query_all_epgs + + - name: Query all EPG (normal mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: query + register: nm_query_all_epgs + + - name: Verify query_all_epgs + assert: + that: + - cm_query_all_epgs is not changed + - nm_query_all_epgs is not changed + + # QUERY AN EPG + - name: Query epg 1(check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: query + check_mode: true + register: cm_query_epg_1 + + - name: Query epg 1(normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: query + register: nm_query_epg_1 + + - name: Verify cm_query_epg_1 and nm_query_epg_1 + assert: + that: + - cm_query_epg_1 is not changed + - nm_query_epg_1 is not changed + + - name: Query epg 1 without l3Out + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + state: query + register: nm_query_epg_l3out + + - name: Verify nm_query_epg_l3out + assert: + that: + - nm_query_epg_l3out is not changed + + # REMOVE EPG + - name: Remove EPG (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: absent + check_mode: true + register: cm_remove_epg + + - name: Verify cm_remove_epg + assert: + that: + - cm_remove_epg is changed + - cm_remove_epg.current == {} + + - name: Remove EPG (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + l3out: L3out1 + state: absent + register: nm_remove_epg + + - name: Add site external EPG without L3Out when template external EPG type is on-premise (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + state: present + ignore_errors: true + register: nm_add_epg_no_l3out + + - name: Verify nm_add_epg_no_l3out + assert: + that: + - nm_add_epg_no_l3out is not changed + - nm_add_epg_no_l3out.msg == "L3Out cannot be empty when template external EPG type is 'on-premise'." + + - name: Remove external EPG at template level + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_1 + state: absent + register: nm_remove_epg + + - name: Verify nm_remove_epg + assert: + that: + - nm_remove_epg is changed + - nm_remove_epg.current == {} + + # Associate site to schema/template before creating External EPG + - name: Add non-cloud site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template2 + state: present + when: version.current.version is version('3.3', '>=') + register: add_site + + - name: Verify add_site + assert: + that: + - add_site.current.siteId is match ("[0-9a-zA-Z]*") + - add_site.current.templateName == "Template2" + + # Create template External EPG after site association + - name: Add external EPG (at template level) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + external_epg: ansible_test_2 + vrf: + name: VRF2 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + l3out: + name: L3out2 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + state: present + register: nm_add_ex_epg + + - name: Verify nm_add_ex_epg + assert: + that: + - nm_add_ex_epg is changed + - nm_add_ex_epg.previous == {} + - nm_add_ex_epg.current.name == "ansible_test_2" + - nm_add_ex_epg.current.vrfRef.vrfName == "VRF2" + + - name: Add external EPG to site (normal mode) + mso_schema_site_external_epg: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + external_epg: ansible_test_2 + l3out: L3out2 + state: present + register: nm_add_epg + + - name: Verify nm_add_epg + assert: + that: + - nm_add_epg.current.externalEpgRef.externalEpgName == "ansible_test_2" + - nm_add_epg.current.externalEpgRef.templateName == "Template2" + + - name: Verify nm_add_epg + assert: + that: + - nm_add_epg is changed + - nm_add_epg.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + # QUERY NON-EXISTING external EPG + - name: Query non-existing External EPG (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: non_existing_epg + l3out: L3out1 + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_external_epg + + - name: Query non-existing External EPG (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: non_existing_epg + l3out: L3out1 + state: query + ignore_errors: true + register: nm_query_non_external_epg + + - name: Verify cm_query_non_external_epg and nm_query_non_external_epg + assert: + that: + - cm_query_non_external_epg is not changed + - nm_query_non_external_epg is not changed + - cm_query_non_external_epg.msg == nm_query_non_external_epg.msg == "External EPG 'non_existing_epg' not found" + + # USE NON-EXISTING STATE + - name: non_existing_state state (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_2 + l3out: L3out1 + state: non_existing_state + ignore_errors: true + register: cm_non_existing_state + + - name: non_existing_state state (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_2 + l3out: L3out1 + state: non_existing_state + ignore_errors: true + register: nm_non_existing_state + + - name: Verify cm_non_existing_state and nm_non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state" + + # USE A NON_EXISTING_TEMPLATE + - name: non_existing_template (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + external_epg: ansible_test_2 + l3out: L3out1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + + - name: non_existing_template site association(normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + external_epg: ansible_test_2 + l3out: L3out1 + state: query + ignore_errors: true + register: nm_non_existing_template + + - name: Verify cm_non_existing_template and nm_non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2, Template3" + + # USE A NON_EXISTING_SCHEMA + - name: non_existing_schema (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_2 + l3out: L3out1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + + - name: non_existing_schema (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + external_epg: ansible_test_2 + l3out: L3out1 + state: query + ignore_errors: true + register: nm_non_existing_schema + + - name: Verify cm_non_existing_schema and nm_non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + + # USE A NON_EXISTING_SITE + - name: non_existing_site (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non_existing_site + template: Template1 + external_epg: ansible_test_2 + l3out: L3out1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site + + - name: non_existing_site (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non_existing_site + template: Template1 + external_epg: ansible_test_2 + l3out: L3out1 + state: query + ignore_errors: true + register: nm_non_existing_site + + - name: Verify cm_non_existing_site and nm_non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name." + + # USE A NON_EXISTING_SITE_TEMPLATE + - name: non_existing_site_template (check_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + external_epg: ansible_test_2 + l3out: L3out1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site_template + + - name: non_existing_site_template (normal_mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + external_epg: ansible_test_2 + l3out: L3out1 + state: query + ignore_errors: true + register: nm_non_existing_site_template + + - name: Verify cm_non_existing_site_template and nm_non_existing_site_template + assert: + that: + - cm_non_existing_site_template is not changed + - nm_non_existing_site_template is not changed + - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site 'ansible_test' not associated with template 'Template3'. Site is currently associated with template(s){{':'}} Template1, Template2" + + # USE A TEMPLATE WITHOUT ANY SITE + - name: Add site L3Out to Schema Template2 without any site associated (check mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + external_epg: ansible_test_2 + l3out: L3out1 + state: present + check_mode: true + ignore_errors: true + register: cm_no_site_associated + + - name: Add site L3Out to Template2 without any site associated (normal mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + external_epg: ansible_test_2 + l3out: L3out1 + state: present + ignore_errors: true + register: nm_no_site_associated + + - name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "Provided site 'ansible_test' not associated with template 'Template3'. Site is currently associated with template(s){{':'}} Template1, Template2" + +# Verify route_reachability argument when template_external_epg is associated with Azure site and +# template_external_epg type argument is set to cloud +- name: Ensure ANP exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + anp: ANP2 + state: present + +- name: Ensure VRF3 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template3 + vrf: VRF3 + state: present + +- name: Ensure L3Out3 Exists + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template3 + vrf: + name: VRF3 + l3out: L3out3 + state: present + +- name: Add external EPG3 at template3 level type cloud (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template3 + external_epg: ext_epg_3 + type: cloud + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template3 + anp: + name: ANP3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template3 + state: present + register: nm_add_ext_epg_3 + +- name: Add azure site to a schema Template3 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template3 + state: present + when: version.current.version is version('3.3', '>=') + register: add_cloud_site + +- name: Add external EPG to site (normal mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template3 + external_epg: ext_epg_3 + route_reachability: site-ext + state: present + register: nm_add_ext_epg_site + +- name: Verify nm_add_ext_epg_site + assert: + that: + - nm_add_ext_epg_site.current.externalEpgRef.externalEpgName == "ext_epg_3" + - nm_add_ext_epg_site.current.routeReachabilityInternetType == "site-ext" + +- name: Add external EPG to site again(normal mode) + mso_schema_site_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template3 + external_epg: ext_epg_3 + route_reachability: site-ext + state: present + register: nm_add_ext_epg_site_again + +- name: Verify nm_add_ext_epg_site_again + assert: + that: + - nm_add_ext_epg_site_again is not changed + - nm_add_ext_epg_site_again.current.externalEpgRef.externalEpgName == "ext_epg_3" + - nm_add_ext_epg_site_again.current.routeReachabilityInternetType == "site-ext"
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml new file mode 100644 index 000000000..3701ba781 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml @@ -0,0 +1,533 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure azure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Ensure aws site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Associate aws site with ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + +- name: Associate azure site with ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Ensure Template 1 with AP1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + state: present + +- name: Ensure L3Out Exists + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: + name: VRF1 + l3out: L3out1 + state: present + +- name: Ensure External EPG1 exists + mso_schema_template_externalepg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + externalepg: extEPG1 + vrf: + name: VRF1 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + l3out: + name: L3out1 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + anp: + name: AP1 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + +- name: Ensure External EPG2 exists + mso_schema_template_externalepg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + externalepg: extEPG2 + vrf: + name: VRF1 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + l3out: + name: L3out1 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + anp: + name: AP1 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + +- name: Add Azure site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + when: version.current.version is version('3', '<') + +- name: Add AWS site to a schema + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + when: version.current.version is version('3', '<') + +- name: Add a new CIDR in VRF1 at site level + mso_schema_site_vrf_region_cidr: &mso_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: '{{ item }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + primary: true + state: present + loop: + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +# ADD SELECTORS +- name: Add a selector to Azure in check mode + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + selector: e1 + expressions: + - type: ip_address + operator: equals + value: 10.0.0.0 + state: present + check_mode: true + register: cm_azure_e1 + +- name: Verify cm_azure_e1 + assert: + that: + - cm_azure_e1 is changed + - cm_azure_e1.previous == {} + - cm_azure_e1.current.subnets[0].ip == '10.0.0.0' + - cm_azure_e1.current.subnets[0].name == 'e1' + + +- name: Add a selector to Azure in normal mode + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + selector: e1 + expressions: + - type: ip_address + operator: equals + value: 10.0.0.0 + state: present + register: nm_azure_e1 + +- name: Verify nm_azure_e1 + assert: + that: + - nm_azure_e1 is changed + - nm_azure_e1.previous == {} + - nm_azure_e1.current.subnets[0].ip == '10.0.0.0' + - nm_azure_e1.current.subnets[0].name == 'e1' + +- name: Add a selector to AWS in normal mode + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG2 + selector: e2 + expressions: + - type: ip_address + operator: equals + value: 10.1.1.1 + state: present + register: nm_aws_e2 + +- name: Verify nm_aws_e2 + assert: + that: + - nm_aws_e2 is changed + - nm_aws_e2.previous == {} + - nm_aws_e2.current.subnets[0].ip == '10.1.1.1' + - nm_aws_e2.current.subnets[0].name == 'e2' + +- name: Add a selector to AWS in normal mode again + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG2 + selector: e2 + expressions: + - type: ip_address + operator: equals + value: 10.1.1.1 + state: present + register: nm_aws_e1_again + +- name: Verify nm_aws_e1_again + assert: + that: + - nm_aws_e1_again is not changed + +- name: Add a selector to AWS in normal mode again with no expressions + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG2 + selector: e2 + state: present + ignore_errors: true + register: nm_aws_e1_again_noexp + +- name: Verify nm_aws_e1_again_noexp + assert: + that: + - nm_aws_e1_again_noexp is not changed + - nm_aws_e1_again_noexp.msg == "Missing expressions in selector" + +# QUERY A SELECTOR +- name: Query a selector of Azure + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + selector: e1 + state: query + register: query_azure_e1 + +- name: Verify query_azure_e1 + assert: + that: + - query_azure_e1 is not changed + +# QUERY ALL +- name: Query all selectors of Azure + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + +# REMOVE A SELECTOR +- name: Remove a selector of Azure + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + selector: e1 + state: absent + register: remove_azure_e1 + +- name: Verify remove_azure_e1 + assert: + that: + - remove_azure_e1 is changed + +- name: Remove a selector of Azure again + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + selector: e1 + state: absent + ignore_errors: true + register: remove_azure_e1_again + +- name: Verify remove_azure_e1_again + assert: + that: + - remove_azure_e1_again is not changed + +# QUERY REMOVED SELECTOR +- name: Query a removed selector of Azure + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + external_epg: extEPG1 + selector: e1 + state: query + ignore_errors: true + register: query_removed_azure_e1 + +- name: Verify query_removed_azure_e1 + assert: + that: + - query_removed_azure_e1 is not changed + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for selector (check_mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: non_existing_schema + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG2 + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for selector (normal_mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: non_existing_schema + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG2 + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for selector (check_mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: non_existing_template + external_epg: extEPG2 + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for selector (normal_mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: non_existing_template + external_epg: extEPG2 + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-EXISTING SITE +- name: Non-existing site for static port (check_mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 2 + external_epg: extEPG3 + check_mode: true + ignore_errors: true + register: cm_non_existing_site + +- name: Non-existing site for static port (normal_mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 2 + external_epg: extEPG3 + ignore_errors: true + register: nm_non_existing_site + +- name: Verify non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site == nm_non_existing_site + - cm_non_existing_site.msg is match("Provided site/siteId/template 'azure_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + - nm_non_existing_site.msg is match("Provided site/siteId/template 'azure_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site external EPG selector to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 3 + external_epg: extEPG3 + ignore_errors: true + check_mode: true + register: cm_no_site_associated + +- name: Add site external EPG selector to Template 3 without any site associated (normal mode) + mso_schema_site_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 3 + external_epg: extEPG3 + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml new file mode 100644 index 000000000..1ed86e254 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml @@ -0,0 +1,614 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + ansible.builtin.fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + l3outs: [ "L3out1", "L3out2"] + +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exists + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + ignore_errors: true + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + cisco.mso.mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template1, and Template2 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template1} + - { template: Template2} + +- name: Ensure schema 2 with Template3 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template3 + state: present + +- name: Ensure VRF1 exists + cisco.mso.mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF1 + state: present + +- name: Add new L3Out + cisco.mso.mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: '{{ item }}' + vrf: + name: VRF1 + state: present + register: add_l3out + loop: '{{ l3outs }}' + +- name: Verify add l3out (template level) + ansible.builtin.assert: + that: + - add_l3out is changed + +- name: Add physical site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: present + +# Add L3out to Site when MSO version >= 3.0 +- name: Execute tasks only for MSO version >= 3.0 + when: version.current.version is version('3.0', '>=') + block: + # Add l3out to site + - name: Add site L3Out (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + check_mode: true + register: cm_add_site_l3out + + - name: Verify cm_add_site_l3out + ansible.builtin.assert: + that: + - cm_add_site_l3out.current.vrfRef.vrfName == "VRF1" + - cm_add_site_l3out.current.vrfRef.templateName == "Template1" + + - name: Verify cm_add_site_l3out + ansible.builtin.assert: + that: + - cm_add_site_l3out is changed + - cm_add_site_l3out.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + - name: Add site L3Out (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + register: nm_add_site_l3out + + - name: Verify nm_add_site_l3out + ansible.builtin.assert: + that: + - nm_add_site_l3out.current.vrfRef.vrfName == "VRF1" + - nm_add_site_l3out.current.vrfRef.templateName == "Template1" + - cm_add_site_l3out.current.vrfRef.schemaId == nm_add_site_l3out.current.vrfRef.schemaId + + - name: Verify nm_add_site_l3out + ansible.builtin.assert: + that: + - nm_add_site_l3out is changed + - nm_add_site_l3out.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + - name: Add site L3Out 2 (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out2 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + + - name: Add site L3Out again (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out2 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + register: nm_add_site_l3out_again + + - name: Verify cm_add_site_l3out_again and nm_add_site_l3out_again + ansible.builtin.assert: + that: + - nm_add_site_l3out_again is not changed + - nm_add_site_l3out_again.current.vrfRef.vrfName == "VRF1" + + # No options to do changes in site level yet + + - name: Query all L3Outs (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: query + check_mode: true + register: cm_query_all_l3out + + - name: Query all L3Outs (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: query + register: nm_query_all_l3out + + - name: Verify cm_query_all_l3out and cm_query_all_l3out + ansible.builtin.assert: + that: + - cm_query_all_l3out is not changed + - nm_query_all_l3out is not changed + - cm_query_all_l3out.current | length == 2 + - nm_query_all_l3out.current | length == 2 + - "'{{cm_query_all_l3out.current[0].l3outRef.l3outName}}' in l3outs" + - "'{{cm_query_all_l3out.current[1].l3outRef.l3outName}}' in l3outs" + - "'{{nm_query_all_l3out.current[0].l3outRef.l3outName}}' in l3outs" + - "'{{nm_query_all_l3out.current[1].l3outRef.l3outName}}' in l3outs" + - cm_query_all_l3out.current[0].l3outRef.schemaId == nm_query_all_l3out.current[0].l3outRef.schemaId + - cm_query_all_l3out.current[0].l3outRef.templateName == nm_query_all_l3out.current[0].l3outRef.templateName == "Template1" + + - name: Query a specific L3Out (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + check_mode: true + register: cm_query_l3out + + - name: Query a specific L3Out (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: query + register: nm_query_l3out + + - name: Verify cm_query_l3out and nm_query_l3out + ansible.builtin.assert: + that: + - cm_query_l3out is not changed + - nm_query_l3out is not changed + + - name: Remove L3Out (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: absent + check_mode: true + register: cm_remove_site_l3out + + - name: Remove L3Out (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: absent + register: nm_remove_site_l3out + + - name: Verify cm_remove_site_l3out and nm_remove_site_l3out + ansible.builtin.assert: + that: + - cm_remove_site_l3out is changed + - nm_remove_site_l3out is changed + - cm_remove_site_l3out.current == nm_remove_site_l3out.current == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + - name: Remove L3Out again(normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: absent + register: nm_remove_site_l3out_again + + - name: Verify nm_remove_site_l3out_again + ansible.builtin.assert: + that: + - nm_remove_site_l3out_again is not changed + - nm_remove_site_l3out_again.previous == nm_remove_site_l3out_again.current == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined + + + # QUERY NON-EXISTING L3Out + - name: Query non-existing L3Out (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: non_existing_l3out + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_l3out + + - name: Query non-existing L3Out (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: non_existing_l3out + state: query + ignore_errors: true + register: nm_query_non_l3out + + - name: Verify cm_query_non_l3out and nm_query_non_l3out + ansible.builtin.assert: + that: + - cm_query_non_l3out is not changed + - nm_query_non_l3out is not changed + - cm_query_non_l3out.msg == nm_query_non_l3out.msg == "L3Out 'non_existing_l3out' not found" + + # USE NON-EXISTING STATE + - name: non_existing_state state (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: non_existing_state + ignore_errors: true + register: cm_non_existing_state + + - name: non_existing_state state (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: non_existing_state + ignore_errors: true + register: nm_non_existing_state + + - name: Verify cm_non_existing_state and nm_non_existing_state + ansible.builtin.assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state" + + # USE A NON_EXISTING_TEMPLATE + - name: non_existing_template (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + + - name: non_existing_template (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: non_existing_template + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: nm_non_existing_template + + - name: Verify cm_non_existing_template and nm_non_existing_template + ansible.builtin.assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + + # USE A NON_EXISTING_SCHEMA + - name: non_existing_schema (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + + - name: non_existing_schema (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: non_existing_schema + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: nm_non_existing_schema + + - name: Verify cm_non_existing_schema and nm_non_existing_schema + ansible.builtin.assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + + # USE A NON_EXISTING_SITE + - name: non_existing_site (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non_existing_site + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site + + - name: non_existing_site (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: non_existing_site + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: nm_non_existing_site + + - name: Verify cm_non_existing_site and nm_non_existing_site + ansible.builtin.assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name." + + # USE A NON_EXISTING_SITE_TEMPLATE + - name: non_existing_site_template (check_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template5 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site_template + + - name: non_existing_site_template (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template5 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: nm_non_existing_site_template + + - name: Verify cm_non_existing_site_template and nm_non_existing_site_template + ansible.builtin.assert: + that: + - cm_non_existing_site_template is not changed + - nm_non_existing_site_template is not changed + - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided template 'Template5' does not exist. Existing templates{{':'}} Template1, Template2" + + - name: nm_non_existing_template_site (normal_mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template2 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: nm_non_existing_template_site + + - name: Verify nm_non_existing_template_site + ansible.builtin.assert: + that: + - nm_non_existing_template_site is not changed + - nm_non_existing_template_site.msg == "Provided template 'Template2' is not associated to site" + + # USE A TEMPLATE WITHOUT ANY SITE + - name: Add site L3Out to Schema Template3 without any site associated (check mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + check_mode: true + ignore_errors: true + register: cm_no_site_associated + + - name: Add site L3Out to Template3 without any site associated (normal mode) + cisco.mso.mso_schema_site_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template3 + l3out: L3out1 + vrf: + name: VRF1 + template: Template1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + ignore_errors: true + register: nm_no_site_associated + + - name: Verify cm_no_site_associated and nm_no_site_associated + ansible.builtin.assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml new file mode 100644 index 000000000..a9b60bab6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml @@ -0,0 +1,795 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +# Service Graph at Site Level is blocked by validations in MSO/NDO before v3.3. +# It is supported after v3.3 by using validate=false. +- name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + - name: Ensure site exists + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + ignore_errors: true + + - name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: absent + + - name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + + - name: Ensure tenant ansible_test exist + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + + - name: Associate site with ansible_test + cisco.mso.mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + + - name: Add a tenant on APIC + cisco.aci.aci_tenant: + host: '{{ apic_hostname }}' + username: '{{ apic_username }}' + password: '{{ apic_password }}' + validate_certs: no + name: "ansible_test" + + - name: Add devices to APIC + cisco.aci.aci_rest: + host: '{{ apic_hostname }}' + username: '{{ apic_username }}' + password: '{{ apic_password }}' + validate_certs: no + path: /api/node/mo/uni/tn-ansible_test.json + method: post + content: + vnsLDevVip: + attributes: + svcType: '{{ item.type }}' + managed: 'false' + name: '{{ item.name }}' + children: + - vnsCDev: + attributes: + name: '{{ item.name }}' + loop: + - { type: FW, name: ansible_test_firewall1 } + - { type: FW, name: ansible_test_firewall2 } + - { type: ADC, name: ansible_test_adc } + - { type: OTHERS, name: ansible_test_other } + + - name: Ensure schema 1 with Template1 and 2 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - 'Template1' + - 'Template2' + + - name: Ensure schema 2 with Template1 exists + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template1 + state: present + + - name: Add Node1 + cisco.mso.mso_service_node_type: + <<: *mso_info + name: ansible_test_other1 + display_name: ansible_test_other1 + state: present + + - name: Add Node2 + cisco.mso.mso_service_node_type: + <<: *mso_info + name: ansible_test_other2 + display_name: ansible_test_other2 + state: present + + - name: Create a service graph 1 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + display_name: sg1 + service_nodes: + - type: firewall + filter_after_first_node: allow_all + state: present + + - name: Create service graph 2 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG2 + display_name: sg2 + service_nodes: + - type: firewall + - type: load-balancer + filter_after_first_node: allow_all + state: present + register: sg1_again + + - name: Create service graph 3 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG3 + display_name: sg3 + service_nodes: + - type: firewall + - type: load-balancer + - type: other + filter_after_first_node: allow_all + state: present + register: sg1_again + + - name: Create a service graph 4 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + display_name: sg4 + service_nodes: + - type: other + - type: load-balancer + - type: firewall + filter_after_first_node: filters_from_contract + state: present + + - name: Create a service graph 5 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG5 + display_name: sg5 + service_nodes: + - type: other + - type: firewall + - type: firewall + filter_after_first_node: filters_from_contract + state: present + + - name: Create a service graph 6 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG6 + display_name: sg6 + service_nodes: + - type: other + - type: other + - type: other + filter_after_first_node: filters_from_contract + state: present + + - name: Create a service graph 7 at Template level + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG7 + display_name: sg7 + service_nodes: + - type: load-balancer + - type: load-balancer + filter_after_first_node: filters_from_contract + state: present + + - name: Add physical site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + state: present + + - name: Add a new Graph at site level (check mode) + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall1 + state: present + register: cm_sg1 + check_mode: true + + - name: Verify cm_sg1 + assert: + that: + - cm_sg1 is changed + - cm_sg1.current.serviceGraphRef.serviceGraphName == "SG1" + - cm_sg1.current.serviceGraphRef.templateName == "Template1" + - cm_sg1.current.serviceNodes | length == 1 + - cm_sg1.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + + - name: Add a new Graph SG1 at site level (normal mode) + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall1 + state: present + register: nm_sg1 + + - name: Verify change_sg1 + assert: + that: + - nm_sg1 is changed + - nm_sg1.current.serviceGraphRef.serviceGraphName == "SG1" + - nm_sg1.current.serviceGraphRef.templateName == "Template1" + - nm_sg1.current.serviceNodes | length == 1 + - nm_sg1.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + + - name: Add Graph SG1 at site level again + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall1 + state: present + register: nm_sg1_again + + - name: Verify change_sg1 + assert: + that: + - nm_sg1_again is not changed + - nm_sg1_again.current.serviceGraphRef.serviceGraphName == "SG1" + - nm_sg1_again.current.serviceGraphRef.templateName == "Template1" + - nm_sg1_again.current.serviceNodes | length == 1 + - nm_sg1_again.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + + - name: Change service graph SG1 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall2 + state: present + register: change_sg1 + + - name: Verify change_sg1 + assert: + that: + - change_sg1 is changed + - change_sg1.current.serviceGraphRef.serviceGraphName == "SG1" + - change_sg1.current.serviceGraphRef.templateName == "Template1" + - change_sg1.current.serviceNodes | length == 1 + - change_sg1.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall2" + + - name: Add a new Graph SG2 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG2 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall1 + - name: ansible_test_adc + state: present + register: sg2 + + - name: Verify sg2 + assert: + that: + - sg2 is changed + - sg2.current.serviceGraphRef.serviceGraphName == "SG2" + - sg2.current.serviceGraphRef.templateName == "Template1" + - sg2.current.serviceNodes | length == 2 + - sg2.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + - sg2.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc" + + - name: Add a new Graph SG3 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG3 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall1 + - name: ansible_test_adc + - name: ansible_test_other + state: present + register: sg3 + + - name: Verify sg3 + assert: + that: + - sg3 is changed + - sg3.current.serviceGraphRef.serviceGraphName == "SG3" + - sg3.current.serviceGraphRef.templateName == "Template1" + - sg3.current.serviceNodes | length == 3 + - sg3.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + - sg3.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc" + - sg3.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + + - name: Add a new Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + - name: ansible_test_adc + - name: ansible_test_firewall1 + state: present + register: sg4 + + - name: Verify sg4 + assert: + that: + - sg4 is changed + - sg4.current.serviceGraphRef.serviceGraphName == "SG4" + - sg4.current.serviceGraphRef.templateName == "Template1" + - sg4.current.serviceNodes | length == 3 + - sg4.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + - sg4.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc" + - sg4.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + + - name: Change Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + - name: ansible_test_adc + - name: ansible_test_firewall2 + state: present + register: change1_sg4 + + - name: Verify change1_sg4 + assert: + that: + - change1_sg4 is changed + - change1_sg4.current.serviceGraphRef.serviceGraphName == "SG4" + - change1_sg4.current.serviceGraphRef.templateName == "Template1" + - change1_sg4.current.serviceNodes | length == 3 + - change1_sg4.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + - change1_sg4.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc" + - change1_sg4.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall2" + + - name: Change Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + state: present + ignore_errors: true + register: change2_sg4 + + - name: Verify change2_sg4 + assert: + that: + - change2_sg4 is not changed + - change2_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '1' service node(s) were given for the service graph" + + - name: Change Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_adc + state: present + ignore_errors: true + register: change3_sg4 + + - name: Verify change3_sg4 + assert: + that: + - change3_sg4 is not changed + - change3_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '1' service node(s) were given for the service graph" + + - name: Change Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_firewall1 + state: present + ignore_errors: true + register: change4_sg4 + + - name: Verify change4_sg4 + assert: + that: + - change4_sg4 is not changed + - change4_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '1' service node(s) were given for the service graph" + + - name: Change Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + - name: ansible_test_adc + state: present + ignore_errors: true + register: change5_sg4 + + - name: Verify change5_sg4 + assert: + that: + - change5_sg4 is not changed + - change5_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '2' service node(s) were given for the service graph" + + - name: Change Graph SG4 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + - name: ansible_test_other + - name: ansible_test_adc + state: present + ignore_errors: true + register: change6_sg4 + + - name: Verify change6_sg4 + assert: + that: + - change6_sg4 is not changed + - change6_sg4.msg == "Provided device 'ansible_test_other' of type 'ADC' does not exist." + + - name: Add Graph SG5 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG5 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + - name: ansible_test_firewall1 + - name: ansible_test_firewall1 + state: present + register: sg5 + + - name: Verify sg5 + assert: + that: + - sg5 is changed + - sg5.current.serviceGraphRef.serviceGraphName == "SG5" + - sg5.current.serviceGraphRef.templateName == "Template1" + - sg5.current.serviceNodes | length == 3 + - sg5.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + - sg5.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + - sg5.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1" + + - name: Add Graph SG6 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG6 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_other + - name: ansible_test_other + - name: ansible_test_other + state: present + register: sg6 + + - name: Verify sg6 + assert: + that: + - sg6 is changed + - sg6.current.serviceGraphRef.serviceGraphName == "SG6" + - sg6.current.serviceGraphRef.templateName == "Template1" + - sg6.current.serviceNodes | length == 3 + - sg6.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + - sg6.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + - sg6.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other" + + - name: Add Graph SG7 at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG7 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + devices: + - name: ansible_test_adc + - name: ansible_test_adc + state: present + register: sg7 + + - name: Verify sg7 + assert: + that: + - sg7 is changed + - sg7.current.serviceGraphRef.serviceGraphName == "SG7" + - sg7.current.serviceGraphRef.templateName == "Template1" + - sg7.current.serviceNodes | length == 2 + - sg7.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc" + - sg7.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc" + + - name: Query service graph SG at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + state: query + register: query_sg + + - name: Verify query_sg + assert: + that: + - query_sg is not changed + - query_sg.current.serviceGraphRef is match("/schemas/[0-9a-zA-Z]*/templates/Template1/serviceGraphs/SG1") + + - name: Query all service graphs at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + state: query + register: query_all + + - name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current | length == 7 + + - name: Query non_existing service graph at site level + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + site: '{{ mso_site | default("ansible_test") }}' + service_graph: non_existent + tenant: ansible_test + state: query + ignore_errors: true + register: query_non_existing_sg + + - name: Verify query_non_existing_sg + assert: + that: + - query_non_existing_sg.msg == "Service Graph 'non_existent' not found" + + - name: Use non_existing schema + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: non_existing_schema + template: Template1 + service_graph: SG + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: query_non_existing_schema + + - name: Verify non_existing_schema + assert: + that: + - query_non_existing_schema is not changed + - query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + + - name: Use non_existing template + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + service_graph: SG + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: query_non_existing_template + + - name: Verify query_non_existing_template + assert: + that: + - query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + + - name: Use non_existing_site_template + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + service_graph: SG + template: Template2 + tenant: ansible_test + state: query + ignore_errors: true + register: nm_non_existing_site_template + + - name: Verify cm_non_existing_site_template and nm_non_existing_site_template + assert: + that: + - nm_non_existing_site_template is not changed + - nm_non_existing_site_template.msg == "Provided site-template association 'ansible_test-Template2' does not exist." + + - name: Add site Service Graph to Template2 without any site association + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + tenant: ansible_test + devices: + - name: ansible_test_firewall2 + state: present + ignore_errors: true + register: nm_no_site_associated + + - name: Verify nm_no_site_associated + assert: + that: + - nm_no_site_associated is not changed + - nm_no_site_associated.msg == "No site associated with template 'Template1'. Associate the site with the template using mso_schema_site." + + - name: Remove service graph from site level(check mode) + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + site: '{{ mso_site | default("ansible_test") }}' + tenant: ansible_test + state: absent + register: rm_sg_cm + check_mode: true + + - name: Verify rm_sg_cm + assert: + that: + - rm_sg_cm is changed + - rm_sg_cm.current == {} + - rm_sg_cm.previous.serviceGraphRef is match("/schemas/[0-9a-zA-Z]*/templates/Template1/serviceGraphs/SG1") + + - name: Remove service graph from site level (normal mode) + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: absent + register: rm_sg + + - name: Verify rm_sg + assert: + that: + - rm_sg is changed + - rm_sg.current == {} + - rm_sg.previous.serviceGraphRef is match("/schemas/[0-9a-zA-Z]*/templates/Template1/serviceGraphs/SG1") + + - name: Remove service graph again + cisco.mso.mso_schema_site_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: absent + register: rm_sg_again + + - name: Verify rm_sg_again + assert: + that: + - rm_sg_again is not changed + - rm_sg_again.current == {} + - rm_sg_again.previous == {} + when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml new file mode 100644 index 000000000..c31a31eeb --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml @@ -0,0 +1,588 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + sites: "['aws_{{ mso_site | default(\"ansible_test\") }}', + 'azure_{{ mso_site | default(\"ansible_test\") }}', + '{{ mso_site | default(\"ansible_test\") }}']" + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure aws site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure azure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure AWS site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: '000000000000' + aws_access_key: 1 + secret_key: 0 + state: present + +- name: Ensure Azure site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Add region and cidr in VRF1 at AWS site level + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + primary: true + state: present + register: aws_add_region_cidr + +- name: Add region and cidr in VRF1 at Azure site level + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + primary: true + state: present + register: azure_add_region_cidr + +- name: Add another region and cidr in VRF1 at AWS site level + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + cidr: 10.10.0.0/16 + primary: true + state: present + register: aws_add_another_region_cidr + +- name: Add another region and cidr in VRF1 at Azure site level + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + cidr: 10.10.0.0/16 + primary: true + state: present + register: azure_add_another_region_cidr + +# Execute context underlay parameters only when when MSO version >= 3.3 +- name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + - name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: present + + - name: Add region and cidr in VRF2 at Azure site level + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF2 + region: westus + cidr: 10.10.0.0/16 + primary: true + state: present + register: azure_add_another_region_cidr + + - name: Add region and cidr in VRF2 at AWS site level + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF2 + region: westus + cidr: 10.10.0.0/16 + primary: true + state: present + register: azure_add_another_region_cidr + + - name: Add context underlay parameters in VRF1 at Azure site level + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + container_overlay: true + underlay_context_profile: + vrf: VRF2 + region: westus + state: present + register: azure_add_context_underlay + + - name: Add context underlay parameters in VRF1 at AWS site level + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + container_overlay: true + underlay_context_profile: + vrf: VRF2 + region: westus + state: present + register: aws_add_context_underlay + + - name: Verify add_context_underlay + assert: + that: + - azure_add_context_underlay is changed + - azure_add_context_underlay.current.contextProfileType == "container-overlay" + - aws_add_context_underlay is changed + - aws_add_context_underlay.current.contextProfileType == "container-overlay" + + - name: Get Validation status + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + register: query_validate + + - name: Verify query_validate + assert: + that: + - query_validate is not changed + + - name: Verify query_validate result < 4.0 + assert: + that: + - query_validate.current.result == "true" + when: version.current.version is version('4.0', '<') + + - name: Verify query_validate result => 4.0 + assert: + that: + - query_validate.current.result == true + when: version.current.version is version('4.0', '>=') + +- name: Query all aws regions + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + state: query + register: aws_query_all + +- name: Query all azure regions + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + state: query + register: azure_query_all + +- name: Query specific aws region + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + register: aws_query_region + +- name: Query specific azure region + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + register: azure_query_region + +- name: Verify query + assert: + that: + - aws_query_all is not changed + - azure_query_all is not changed + - aws_query_region is not changed + - azure_query_region is not changed + - aws_query_all.current | length == 2 + - azure_query_all.current | length == 2 + - aws_query_region.current.name == "us-west-1" + - aws_query_region.current.cidrs.0.ip == "10.0.0.0/16" + - aws_query_region.current.cidrs.0.primary == true + - aws_query_region.current.cidrs.0.subnets == [] + - azure_query_region.current.name == "us-west-1" + - azure_query_region.current.cidrs.0.ip == "10.0.0.0/16" + - azure_query_region.current.cidrs.0.primary == true + - azure_query_region.current.cidrs.0.subnets == [] + +- name: Remove aws VRF region (check mode) + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + state: absent + check_mode: true + register: cm_rm_aws_region + +- name: Remove aws VRF region (normal mode) + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + state: absent + register: nm_rm_aws_region + +- name: Remove azure VRF region (check mode) + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: absent + check_mode: true + register: cm_rm_azure_region + +- name: Remove azure VRF region (normal mode) + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: absent + register: nm_rm_azure_region + +- name: Verify deletion + assert: + that: + - cm_rm_aws_region is changed + - nm_rm_aws_region is changed + - cm_rm_azure_region is changed + - nm_rm_azure_region is changed + - cm_rm_aws_region.previous == nm_rm_aws_region.previous + - cm_rm_azure_region.previous == nm_rm_azure_region.previous + - cm_rm_aws_region.current == nm_rm_aws_region.current == {} + - cm_rm_azure_region.current == nm_rm_azure_region.current == {} + - cm_rm_aws_region.previous.name == nm_rm_aws_region.previous.name == "us-east-1" + - cm_rm_azure_region.previous.name == nm_rm_azure_region.previous.name == "us-west-1" + +- name: Add VPN Gateway Router to Region for AWS + cisco.mso.mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + vpn_gateway_router: true + state: present + when: version.current.version is version('3.0.0a', '>=') + register: add_vpn_gateway_router_aws + +- name: Add VPN Gateway Router to Region for Azure + cisco.mso.mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + vpn_gateway_router: true + state: present + when: version.current.version is version('3.0.0a', '>=') + register: add_vpn_gateway_router_azure + +- name: Verify adding VPN Gateway Router + assert: + that: + - add_vpn_gateway_router_aws.current.isVpnGatewayRouter == add_vpn_gateway_router_azure.current.isVpnGatewayRouter == true + when: version.current.version is version('3.0.0a', '>=') + +- name: Remove VPN Gateway Router at Region for AWS (check mode) + cisco.mso.mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + vpn_gateway_router: false + state: present + check_mode: true + when: version.current.version is version('3.0.0a', '>=') + register: cm_rm_vpn_gateway_router_aws + +- name: Remove VPN Gateway Router at Region for AWS (normal mode) + cisco.mso.mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + vpn_gateway_router: false + state: present + when: version.current.version is version('3.0.0a', '>=') + register: nm_rm_vpn_gateway_router_aws + +- name: Remove VPN Gateway Router at Region for Azure (check mode) + cisco.mso.mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + vpn_gateway_router: false + state: present + check_mode: true + when: version.current.version is version('3.0.0a', '>=') + register: cm_rm_vpn_gateway_router_azure + +- name: Remove VPN Gateway Router at Region for Azure (normal mode) + cisco.mso.mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-east-1 + vpn_gateway_router: false + state: present + when: version.current.version is version('3.0.0a', '>=') + register: nm_rm_vpn_gateway_router_azure + +- name: Verify removing VPN Gateway Router + assert: + that: + - cm_rm_vpn_gateway_router_aws is changed + - nm_rm_vpn_gateway_router_aws is changed + - cm_rm_vpn_gateway_router_azure is changed + - nm_rm_vpn_gateway_router_azure is changed + - cm_rm_vpn_gateway_router_aws.previous.isVpnGatewayRouter == nm_rm_vpn_gateway_router_aws.previous.isVpnGatewayRouter == true + - cm_rm_vpn_gateway_router_azure.previous.isVpnGatewayRouter == nm_rm_vpn_gateway_router_azure.previous.isVpnGatewayRouter == true + - cm_rm_vpn_gateway_router_aws.current.isVpnGatewayRouter == nm_rm_vpn_gateway_router_aws.current.isVpnGatewayRouter == false + - cm_rm_vpn_gateway_router_azure.current.isVpnGatewayRouter == nm_rm_vpn_gateway_router_azure.current.isVpnGatewayRouter == false + when: version.current.version is version('3.0.0a', '>=') + +- name: Use non_existing schema + mso_schema_site_vrf_region: + <<: *mso_info + schema: non_existing + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + register: non_existing_schema + ignore_errors: true + +- name: Use non_existing site + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: non_existing + vrf: VRF1 + region: us-west-1 + state: query + register: non_existing_site + ignore_errors: true + +- name: Use non_existing site/template association + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + register: non_existing_site_template + ignore_errors: true + +- name: Use non_existing VRF + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: non_existing + region: us-west-1 + state: query + register: non_existing_vrf + ignore_errors: true + +- name: Use non_existing region + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: non_existing + state: query + register: non_existing_region + ignore_errors: true + +- name: Verify non_existing + assert: + that: + - non_existing_schema.msg == "Provided schema 'non_existing' does not exist." + - non_existing_site.msg == "Site 'non_existing' is not a valid site name." + - non_existing_site_template.msg == "Provided site-template association 'aws_ansible_test-Template2' does not exist." + - non_existing_region.msg == "Region 'non_existing' not found" + +- name: Verify non_existing (version < 3.3) + assert: + that: + - non_existing_vrf.msg == "Provided vrf 'non_existing' does not exist. Existing vrfs{{':'}} VRF1" + when: version.current.version is version('3.3', '<') + +- name: Verify non_existing (version >= 3.3) + assert: + that: + - non_existing_vrf.msg == "Provided vrf 'non_existing' does not exist. Existing vrfs{{':'}} VRF1, VRF2" + when: version.current.version is version('3.3', '>=') + +- name: Delete non_existing region + mso_schema_site_vrf_region: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: non_existing + state: absent + register: rm_non_existing_region + +- name: Verify rm_non_existing_region + assert: + that: + - rm_non_existing_region is not changed + - rm_non_existing_region.previous == rm_non_existing_region.current == {}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml new file mode 100644 index 000000000..64d08821c --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml @@ -0,0 +1,721 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> (based on mso_schema_anp_epg_domain) +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) + + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + sites: "['aws_{{ mso_site | default(\"ansible_test\") }}', + 'azure_{{ mso_site | default(\"ansible_test\") }}', + '{{ mso_site | default(\"ansible_test\") }}']" + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + sites: + - '{{ mso_site | default("ansible_test") }}' + users: + - '{{ mso_username }}' + state: present + +- name: Ensure AWS site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: '000000000000' + aws_access_key: 1 + secret_key: 0 + state: present + +- name: Ensure Azure site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add physical site to Template 1 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + vrf: VRF1 + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' } + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3' } + +- name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: present + when: version.current.version is version('3', '<') + +- name: Ensure VRF1 exists at Site level for the physical site + mso_schema_site_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: '{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + state: present + +# ADD SUBNET +- name: Add a new CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: &mso_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + primary: true + state: present + check_mode: true + register: cm_add_cidr + +- name: Verify cm_add_cidr + assert: + that: + - cm_add_cidr is changed + - cm_add_cidr.previous == {} + - cm_add_cidr.current.ip == '10.0.0.0/16' + - cm_add_cidr.current.primary == true + +- name: Add a new CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + register: nm_add_cidr + +- name: Verify nm_add_cidr + assert: + that: + - nm_add_cidr is changed + - nm_add_cidr.previous == {} + - nm_add_cidr.current.ip == '10.0.0.0/16' + - nm_add_cidr.current.primary == true + +- name: Add same CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + register: cm_add_cidr_again + +- name: Verify cm_add_cidr_again + assert: + that: + - cm_add_cidr_again is not changed + - cm_add_cidr_again.current.ip == cm_add_cidr_again.previous.ip == '10.0.0.0/16' + - cm_add_cidr_again.current.primary == cm_add_cidr_again.previous.primary == true + +- name: Add same CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + register: nm_add_cidr_again + +- name: Verify nm_add_cidr_again + assert: + that: + - nm_add_cidr_again is not changed + - nm_add_cidr_again.current.ip == nm_add_cidr_again.previous.ip == '10.0.0.0/16' + - nm_add_cidr_again.current.primary == nm_add_cidr_again.previous.primary == true + +- name: Add a CIDR in VRF1 at Azure site level (check mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + primary: true + check_mode: true + register: cm_add_cidr_2 + +- name: Verify cm_add_cidr_2 + assert: + that: + - cm_add_cidr_2 is changed + - cm_add_cidr_2.previous == {} + - cm_add_cidr_2.current.ip == '10.1.0.0/16' + - cm_add_cidr_2.current.primary == true + +- name: Add a CIDR in VRF1 at Azure site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + primary: true + register: nm_add_cidr_2 + +- name: Verify nm_add_cidr_2 + assert: + that: + - nm_add_cidr_2 is changed + - nm_add_cidr_2.previous == {} + - nm_add_cidr_2.current.ip == '10.1.0.0/16' + - nm_add_cidr_2.current.primary == true + +- name: Add a second CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: &mso_present_2 + <<: *mso_present + site: 'aws_{{ mso_site | default("ansible_test") }}' + region: us-west-1 + cidr: 10.2.0.0/16 + primary: false + check_mode: true + register: cm_add_cidr_3 + +- name: Verify cm_add_cidr_3 + assert: + that: + - cm_add_cidr_3 is changed + - cm_add_cidr_3.previous == {} + - cm_add_cidr_3.current.ip == '10.2.0.0/16' + - cm_add_cidr_3.current.primary == false + +- name: Add a second CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present_2 + register: nm_add_cidr_3 + +- name: Verify nm_add_cidr_3 + assert: + that: + - nm_add_cidr_3 is changed + - nm_add_cidr_3.previous == {} + - nm_add_cidr_3.current.ip == '10.2.0.0/16' + - nm_add_cidr_3.current.primary == false + +- name: Add a second CIDR in VRF1 at Azure site level (check mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.3.0.0/16 + primary: false + check_mode: true + register: cm_add_cidr_4 + +- name: Verify cm_add_cidr_4 + assert: + that: + - cm_add_cidr_4 is changed + - cm_add_cidr_4.previous == {} + - cm_add_cidr_4.current.ip == '10.3.0.0/16' + - cm_add_cidr_4.current.primary == false + +- name: Add a second CIDR in VRF1 at Azure site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.3.0.0/16 + primary: false + register: nm_add_cidr_4 + +- name: Verify nm_add_cidr_4 + assert: + that: + - nm_add_cidr_4 is changed + - nm_add_cidr_4.previous == {} + - nm_add_cidr_4.current.ip == '10.3.0.0/16' + - nm_add_cidr_4.current.primary == false + +# QUERY CIDR +- name: Query CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: &mso_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + state: query + check_mode: true + register: cm_query_cidr + +- name: Verify cm_query_cidr + assert: + that: + - cm_query_cidr is not changed + - cm_query_cidr.current.ip == '10.0.0.0/16' + - cm_query_cidr.current.primary == true + +- name: Query CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + register: nm_query_cidr + +- name: Query CIDR in VRF1 at Azure site level (check mode) + mso_schema_site_vrf_region_cidr: &mso_query_2 + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: westus + cidr: 10.1.0.0/16 + state: query + check_mode: true + register: cm_query_cidr_2 + +- name: Verify cm_query_cidr_2 + assert: + that: + - cm_query_cidr_2 is not changed + - cm_query_cidr_2.current.ip == '10.1.0.0/16' + - cm_query_cidr_2.current.primary == true + +- name: Query CIDR in VRF1 at Azure site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query_2 + register: nm_query_cidr_2 + +- name: Verify nm_query_cidr_2 + assert: + that: + - nm_query_cidr_2 is not changed + - nm_query_cidr_2.current.ip == '10.1.0.0/16' + - nm_query_cidr_2.current.primary == true + +# QUERY ALL CIDR +- name: Query all CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: &mso_query_all + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + check_mode: true + register: cm_query_cidr_all_aws + +- name: Query CIDR in VRF1 at Azure site level (check mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query_all + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + state: query + check_mode: true + register: cm_query_cidr_all_azure + +- name: Verify cm_query_cidr_all_aws and cm_query_cidr_all_azure + assert: + that: + - cm_query_cidr_all_aws is not changed + - cm_query_cidr_all_aws.current[0].ip == '10.0.0.0/16' + - cm_query_cidr_all_aws.current[0].primary == true + - cm_query_cidr_all_aws.current[1].ip == '10.2.0.0/16' + - cm_query_cidr_all_aws.current[1].primary == false + - cm_query_cidr_all_azure is not changed + - cm_query_cidr_all_azure.current[0].ip == '10.1.0.0/16' + - cm_query_cidr_all_azure.current[0].primary == true + - cm_query_cidr_all_azure.current[1].ip == '10.3.0.0/16' + - cm_query_cidr_all_azure.current[1].primary == false + +- name: Query CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query_all + register: nm_query_cidr_all_aws + +- name: Query CIDR in VRF1 at Azure site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query_all + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + register: nm_query_cidr_all_azure + +- name: Verify nm_query_cidr_all_aws and nm_query_cidr_all_azure + assert: + that: + - nm_query_cidr_all_aws is not changed + - nm_query_cidr_all_aws.current[0].ip == '10.0.0.0/16' + - nm_query_cidr_all_aws.current[0].primary == true + - nm_query_cidr_all_aws.current[1].ip == '10.2.0.0/16' + - nm_query_cidr_all_aws.current[1].primary == false + - nm_query_cidr_all_azure is not changed + - nm_query_cidr_all_azure.current[0].ip == '10.1.0.0/16' + - nm_query_cidr_all_azure.current[0].primary == true + - nm_query_cidr_all_azure.current[1].ip == '10.3.0.0/16' + - nm_query_cidr_all_azure.current[1].primary == false + +- name: Query CIDR in VRF2 (not present a Site level) for AWS Site (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query_all + vrf: VRF2 + ignore_errors: true + register: nm_query_cidr_all_aws_2 + +- name: Query CIDR in VRF1 (with VRF present a Site level) for Physical Site (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query_all + site: '{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + ignore_errors: true + register: nm_query_cidr_all_aws_3 + +- name: Verify nm_query_cidr_all_aws_2 and nm_query_cidr_all_aws_3 + assert: + that: + - nm_query_cidr_all_aws_2.msg == "Provided vrf 'VRF2' does not exist at site level." + - nm_query_cidr_all_aws_3.msg == "Provided region 'us-west-1' does not exist. Existing regions{{':'}} " + when: version.current.version is version('3', '<') + +# REMOVE CIDR +- name: Remove CIDR (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present_2 + state: absent + check_mode: true + register: cm_remove_cidr + +- name: Verify cm_remove_cidr + assert: + that: + - cm_remove_cidr is changed + - cm_remove_cidr.current == {} + +- name: Remove CIDR (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present_2 + state: absent + register: nm_remove_cidr + +- name: Verify nm_remove_cidr + assert: + that: + - nm_remove_cidr is changed + - nm_remove_cidr.current == {} + +- name: Remove CIDR again (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present_2 + state: absent + check_mode: true + register: cm_remove_cidr_again + +- name: Verify cm_remove_cidr_again + assert: + that: + - cm_remove_cidr_again is not changed + - cm_remove_cidr_again.current == {} + +- name: Remove CIDR again (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present_2 + state: absent + register: nm_remove_cidr_again + +- name: Verify nm_remove_cidr_again + assert: + that: + - nm_remove_cidr_again is not changed + - nm_remove_cidr_again.current == {} + +# QUERY NON-EXISTING CIDR +- name: Query non-existing CIDR (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + cidr: non_existing_cidr + check_mode: true + ignore_errors: true + register: cm_query_non_cidr + +- name: Query non-existing CIDR (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + cidr: non_existing_cidr + ignore_errors: true + register: nm_query_non_cidr + +- name: Verify query_non_cidr + assert: + that: + - cm_query_non_cidr is not changed + - nm_query_non_cidr is not changed + - cm_query_non_cidr == nm_query_non_cidr + - cm_query_non_cidr.msg == nm_query_non_cidr.msg == "CIDR IP 'non_existing_cidr' not found" + +# QUERY NON-EXISTING region +- name: Query non-existing region (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + region: non_existing_region + check_mode: true + ignore_errors: true + register: cm_query_non_region + +- name: Query non-existing region (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + region: non_existing_region + ignore_errors: true + register: nm_query_non_region + +- name: Verify query_non_region + assert: + that: + - cm_query_non_region is not changed + - nm_query_non_region is not changed + - cm_query_non_region == nm_query_non_region + - cm_query_non_region.msg == nm_query_non_region.msg == "Provided region 'non_existing_region' does not exist. Existing regions{{':'}} us-west-1" + +# QUERY NON-EXISTING VRF +- name: Query non-existing VRF (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + vrf: non_existing_vrf + check_mode: true + ignore_errors: true + register: cm_query_non_vrf + +- name: Query non-existing VRF (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + vrf: non_existing_vrf + ignore_errors: true + register: nm_query_non_vrf + +- name: Verify query_non_vrf + assert: + that: + - cm_query_non_vrf is not changed + - nm_query_non_vrf is not changed + - cm_query_non_vrf == nm_query_non_vrf + +- name: Verify query_non_vrf (version < 3.0) + assert: + that: + - cm_query_non_vrf.msg == nm_query_non_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF1, VRF2" + when: version.current.version is version('3', '<') + +# USE A NON-EXISTING STATE +- name: Non-existing state for site cidr (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for site cidr (normal_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for site cidr (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + schema: non-existing-schema + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for site cidr (normal_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for site cidr (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + template: non-existing-template + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for site cidr (normal_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-ASSOCIATED TEMPLATE +- name: Non-associated template for site cidr (check_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + template: Template 2 + check_mode: true + ignore_errors: true + register: cm_non_associated_template + +- name: Non-associated template for site cidr (normal_mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_query + template: Template 2 + ignore_errors: true + register: nm_non_associated_template + +- name: Verify non_associated_template + assert: + that: + - cm_non_associated_template is not changed + - nm_non_associated_template is not changed + - cm_non_associated_template == nm_non_associated_template + - cm_non_associated_template.msg == "Provided site-template association 'aws_{{ mso_site | default("ansible_test") }}-Template2' does not exist." + - nm_non_associated_template.msg == "Provided site-template association 'aws_{{ mso_site | default("ansible_test") }}-Template2' does not exist." + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site VRF region cidr to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + check_mode: true + register: cm_no_site_associated + +- name: Add site VRF region cidr to Template 3 without any site associated (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is changed + - nm_no_site_associated is changed
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml new file mode 100644 index 000000000..e5e8d3ca7 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml @@ -0,0 +1,886 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> (based on mso_schema_anp_epg_domain) +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) + + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + sites: "[ { 'site': 'aws_{{ mso_site | default(\"ansible_test\") }}', 'region': 'us-west-1', 'cidr': '10.0.0.0/16'}, + { 'site': 'azure_{{ mso_site | default(\"ansible_test\") }}', 'region': 'westus', 'cidr': '10.1.0.0/16'}]" + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure AWS site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: '000000000000' + aws_access_key: 1 + secret_key: 0 + state: present + +- name: Ensure Azure site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Add a new sites to a Template 1 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ item.site }}' + template: Template 1 + state: present + loop: '{{ sites }}' + when: version.current.version is version('3', '<') + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Ensure region for VRF1 at site level exists + mso_schema_site_vrf_region_cidr: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: '{{ item.site }}' + vrf: VRF1 + region: '{{ item.region }}' + cidr: '{{ item.cidr }}' + state: present + loop: '{{ sites }}' + + +# ADD SUBNET +- name: Add a new subnet to AWS CIDR in VRF1 at site level (check mode) + mso_schema_site_vrf_region_cidr_subnet: &mso_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + subnet: 10.0.0.0/24 + zone: us-west-1a + state: present + check_mode: true + register: cm_add_subnet + +- name: Verify cm_add_subnet + assert: + that: + - cm_add_subnet is changed + - cm_add_subnet.previous == {} + - cm_add_subnet.current.ip == '10.0.0.0/24' + - cm_add_subnet.current.zone == 'us-west-1a' + +- name: Add a new subnet to AWS CIDR in VRF1 at site level (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + register: nm_add_subnet + +- name: Verify nm_add_subnet + assert: + that: + - nm_add_subnet is changed + - nm_add_subnet.previous == {} + - nm_add_subnet.current.ip == '10.0.0.0/24' + - nm_add_subnet.current.zone == 'us-west-1a' + +- name: Add same subnet again to AWS CIDR in VRF1 at site level (check mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + register: cm_add_subnet_again + +- name: Verify cm_add_subnet_again + assert: + that: + - cm_add_subnet_again is not changed + - cm_add_subnet_again.current.ip == cm_add_subnet_again.previous.ip == '10.0.0.0/24' + - cm_add_subnet_again.current.zone == cm_add_subnet_again.previous.zone == 'us-west-1a' + +- name: Add same subnet again to AWS CIDR in VRF1 at site level (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + register: nm_add_subnet_again + +- name: Verify nm_add_subnet_again + assert: + that: + - nm_add_subnet_again is not changed + - nm_add_subnet_again.current.ip == nm_add_subnet_again.previous.ip == '10.0.0.0/24' + - nm_add_subnet_again.current.zone == nm_add_subnet_again.previous.zone == 'us-west-1a' + +- name: Add a new subnet to Azure CIDR in VRF1 at site level (check mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + subnet: 10.1.0.0/24 + zone: null + check_mode: true + register: cm_add_subnet_2 + +- name: Verify cm_add_subnet_2 + assert: + that: + - cm_add_subnet_2 is changed + - cm_add_subnet_2.previous == {} + - cm_add_subnet_2.current.ip == '10.1.0.0/24' + - cm_add_subnet_2.current.zone == '' + +- name: Add a new subnet to Azure CIDR in VRF1 at site level (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + subnet: 10.1.0.0/24 + zone: null + register: nm_add_subnet_2 + +- name: Verify nm_add_subnet_2 + assert: + that: + - nm_add_subnet_2 is changed + - nm_add_subnet_2.previous == {} + - nm_add_subnet_2.current.ip == '10.1.0.0/24' + - nm_add_subnet_2.current.zone == '' + +- name: Add a second subnet to Azure CIDR in VRF1 at site level for VGW (check mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + subnet: 10.1.1.0/24 + zone: null + vgw: true + check_mode: true + register: cm_add_subnet_3 + +- name: Verify cm_add_subnet_3 + assert: + that: + - cm_add_subnet_3 is changed + - cm_add_subnet_3.previous == {} + - cm_add_subnet_3.current.ip == '10.1.1.0/24' + - cm_add_subnet_3.current.zone == '' + - cm_add_subnet_3.current.usage == 'gateway' + +# VGW +- name: Add a second subnet to Azure CIDR in VRF1 at site level for VGW (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + subnet: 10.1.1.0/24 + zone: null + vgw: true + register: nm_add_subnet_3 + +- name: Verify nm_add_subnet_3 + assert: + that: + - nm_add_subnet_3 is changed + - nm_add_subnet_3.previous == {} + - nm_add_subnet_3.current.ip == '10.1.1.0/24' + - nm_add_subnet_3.current.zone == '' + - nm_add_subnet_3.current.usage == 'gateway' + +# Private Link Label +- name: Add a new subnet to Azure CIDR in VRF1 at site level for Private Link Label (MSO >3.3) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + subnet: 10.1.0.0/24 + private_link_label: 'New_Private_Link_Label' + zone: null + register: nm_add_subnet_4 + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_add_subnet_4 + assert: + that: + - nm_add_subnet_4 is changed + - nm_add_subnet_4.current.ip == '10.1.0.0/24' + - nm_add_subnet_4.current.privateLinkLabel.name == 'New_Private_Link_Label' + when: version.current.version is version('3.3', '>=') + +# QUERY SUBNETS +- name: Query subnet to AWS CIDR in VRF1 at site level (check mode) + mso_schema_site_vrf_region_cidr_subnet: &mso_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + subnet: 10.0.0.0/24 + state: query + check_mode: true + register: cm_query_subnet + +- name: Verify cm_query_subnet + assert: + that: + - cm_query_subnet is not changed + - cm_query_subnet.current.ip == '10.0.0.0/24' + - cm_query_subnet.current.zone == 'us-west-1a' + +- name: Query subnet to AWS CIDR in VRF1 at site level (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + register: nm_query_subnet + +- name: Verify nm_query_subnet + assert: + that: + - nm_query_subnet is not changed + +# QUERY ALL SUBNETS +- name: Query all subnets to AWS CIDR in VRF1 at site level (check mode) + mso_schema_site_vrf_region_cidr_subnet: &mso_query_all + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + state: query + check_mode: true + register: cm_query_subnet_all_aws + +- name: Query all subnets to Azure CIDR in VRF1 at site level (check mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query_all + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + state: query + check_mode: true + register: cm_query_subnet_all_azure + +- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure + assert: + that: + - cm_query_subnet_all_aws is not changed + - cm_query_subnet_all_aws.current[0].ip == '10.0.0.0/24' + - cm_query_subnet_all_aws.current[0].zone == 'us-west-1a' + - cm_query_subnet_all_azure is not changed + - cm_query_subnet_all_azure.current[0].ip == '10.1.0.0/24' + - cm_query_subnet_all_azure.current[1].ip == '10.1.1.0/24' + +- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone + assert: + that: + - cm_query_subnet_all_azure.current[0].zone == '' + - cm_query_subnet_all_azure.current[1].zone == '' + when: version.current.version is version('4.0', '<') + + +- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone + assert: + that: + - cm_query_subnet_all_azure.current[0].zone == 'default' + - cm_query_subnet_all_azure.current[1].zone == 'default' + when: version.current.version is version('4.0', '>=') + +- name: Query subnet to AWS CIDR in VRF1 at site level (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query_all + register: nm_query_subnet_all_aws + +- name: Query subnet to AWS CIDR in VRF1 at site level (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query_all + site: 'azure_{{ mso_site | default("ansible_test") }}' + region: westus + cidr: 10.1.0.0/16 + state: query + register: nm_query_subnet_all_azure + +- name: Verify nm_query_subnet_all_aws and nm_query_subnet_all_azure + assert: + that: + - nm_query_subnet_all_aws is not changed + - nm_query_subnet_all_aws.current[0].ip == '10.0.0.0/24' + - nm_query_subnet_all_aws.current[0].zone == 'us-west-1a' + - nm_query_subnet_all_azure is not changed + - nm_query_subnet_all_azure.current[0].ip == '10.1.0.0/24' + - nm_query_subnet_all_azure.current[1].ip == '10.1.1.0/24' + +- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone + assert: + that: + - nm_query_subnet_all_azure.current[0].zone == '' + - nm_query_subnet_all_azure.current[1].zone == '' + when: version.current.version is version('4.0', '<') + + +- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone + assert: + that: + - nm_query_subnet_all_azure.current[0].zone == 'default' + - nm_query_subnet_all_azure.current[1].zone == 'default' + when: version.current.version is version('4.0', '>=') + +# Execute Hosted VRF parameters only when when MSO version >= 3.3 +- name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + - name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: present + + - name: Add a secondary CIDR in VRF1 at AWS site level + mso_schema_site_vrf_region_cidr: &secondary_cidr + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.2.0.0/16 + primary: false + register: nm_add_cidr_3 + + - name: Verify nm_add_cidr_3 + assert: + that: + - nm_add_cidr_3 is changed + - nm_add_cidr_3.previous == {} + - nm_add_cidr_3.current.ip == '10.2.0.0/16' + - nm_add_cidr_3.current.primary == false + + - name: Add a secondary CIDR in VRF1 at Azure site level + mso_schema_site_vrf_region_cidr: + <<: *secondary_cidr + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: westus + cidr: 10.3.0.0/16 + primary: false + register: nm_add_cidr_4 + + - name: Verify nm_add_cidr_4 + assert: + that: + - nm_add_cidr_4 is changed + - nm_add_cidr_4.previous == {} + - nm_add_cidr_4.current.ip == '10.3.0.0/16' + - nm_add_cidr_4.current.primary == false + + - name: Add hosted vrf parameters in VRF1 at Azure site level + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'azure_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: westus + cidr: 10.3.0.0/16 + subnet: 10.3.0.0/24 + zone: westus + hosted_vrf: VRF2 + vgw: true + state: present + register: azure_add_hosted_vrf + + - name: Verify azure_add_hosted_vrf + assert: + that: + - azure_add_hosted_vrf is changed + - azure_add_hosted_vrf.previous == {} + - azure_add_hosted_vrf.current.ip == '10.3.0.0/24' + - azure_add_hosted_vrf.current.vrfRef.vrfName == 'VRF2' + + - name: Add hosted vrf parameters in VRF1 at AWS site level + mso_schema_site_vrf_region_cidr_subnet: &aws_cidr + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.2.0.0/16 + subnet: 10.2.0.0/24 + zone: us-west-1a + hosted_vrf: VRF2 + vgw: true + state: present + register: aws_add_hosted_vrf + + - name: Verify aws_add_hosted_vrf + assert: + that: + - aws_add_hosted_vrf is changed + - aws_add_hosted_vrf.previous == {} + - aws_add_hosted_vrf.current.ip == '10.2.0.0/24' + - aws_add_hosted_vrf.current.vrfRef.vrfName == 'VRF2' + + - name: Get Validation status + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + register: query_validate + + - name: Verify query_validate + assert: + that: + - query_validate is not changed + + - name: Verify query_validate result < 4.0 + assert: + that: + - query_validate.current.result == "true" + when: version.current.version is version('4.0', '<') + + - name: Verify query_validate result => 4.0 + assert: + that: + - query_validate.current.result == true + when: version.current.version is version('4.0', '>=') + +# REMOVE SUBNETS +- name: Remove Subnet from CIDR (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + state: absent + check_mode: true + register: cm_remove_subnet + +- name: Verify cm_remove_subnet + assert: + that: + - cm_remove_subnet is changed + - cm_remove_subnet.current == {} + +- name: Remove Subnet from CIDR (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + state: absent + register: nm_remove_subnet + +- name: Verify nm_remove_subnet + assert: + that: + - nm_remove_subnet is changed + - nm_remove_subnet.current == {} + +- name: Remove Subnet from CIDR again (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + state: absent + check_mode: true + register: cm_remove_subnet_again + +- name: Verify cm_remove_subnet_again + assert: + that: + - cm_remove_subnet_again is not changed + - cm_remove_subnet_again.current == {} + +- name: Remove Subnet from CIDR again (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_present + state: absent + register: nm_remove_subnet_again + +- name: Verify nm_remove_subnet_again + assert: + that: + - nm_remove_subnet_again is not changed + - nm_remove_subnet_again.current == {} + +- name: Remove Subnet from CIDR (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *aws_cidr + state: absent + register: remove_subnet + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_remove_subnet + assert: + that: + - remove_subnet is changed + - remove_subnet.current == {} + when: version.current.version is version('3.3', '>=') + +# QUERY NON-EXISTING subnet in CIDR +- name: Query non-existing subnet (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + subnet: non_existing_subnet + check_mode: true + ignore_errors: true + register: cm_query_non_subnet + +- name: Query non-existing subnet (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + subnet: non_existing_subnet + ignore_errors: true + register: nm_query_non_subnet + +- name: Verify query_non_subnet + assert: + that: + - cm_query_non_subnet is not changed + - nm_query_non_subnet is not changed + - cm_query_non_subnet == nm_query_non_subnet + - cm_query_non_subnet.msg is match("Subnet IP 'non_existing_subnet' not found") + - nm_query_non_subnet.msg is match("Subnet IP 'non_existing_subnet' not found") + +# QUERY NON-EXISTING CIDR +- name: Query non-existing CIDR (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + cidr: non_existing_cidr + check_mode: true + ignore_errors: true + register: cm_query_non_cidr + +- name: Query non-existing CIDR (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + cidr: non_existing_cidr + ignore_errors: true + register: nm_query_non_cidr + +- name: Verify query_non_cidr + assert: + that: + - cm_query_non_cidr is not changed + - nm_query_non_cidr is not changed + - cm_query_non_cidr == nm_query_non_cidr + +- name: Verify query_non_cidr value (version < 3.3) + assert: + that: + - cm_query_non_cidr.msg == nm_query_non_cidr.msg == "Provided CIDR IP 'non_existing_cidr' does not exist. Existing CIDR IPs{{':'}} 10.0.0.0/16. Use mso_schema_site_vrf_region_cidr to create it." + when: version.current.version is version('3.3', '<') + +- name: Verify query_non_cidr value (version >= 3.3) + assert: + that: + - cm_query_non_cidr.msg == nm_query_non_cidr.msg == "Provided CIDR IP 'non_existing_cidr' does not exist. Existing CIDR IPs{{':'}} 10.0.0.0/16, 10.2.0.0/16. Use mso_schema_site_vrf_region_cidr to create it." + when: version.current.version is version('3.3', '>=') + +# QUERY NON-EXISTING region +- name: Query non-existing region (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + region: non_existing_region + check_mode: true + ignore_errors: true + register: cm_query_non_region + +- name: Query non-existing region (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + region: non_existing_region + ignore_errors: true + register: nm_query_non_region + +- name: Verify query_non_region + assert: + that: + - cm_query_non_region is not changed + - nm_query_non_region is not changed + - cm_query_non_region == nm_query_non_region + - cm_query_non_region.msg == nm_query_non_region.msg == "Provided region 'non_existing_region' does not exist. Existing regions{{':'}} us-west-1. Use mso_schema_site_vrf_region_cidr to create it." + +# QUERY NON-EXISTING VRF +- name: Query non-existing VRF (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + vrf: non_existing_vrf + check_mode: true + ignore_errors: true + register: cm_query_non_vrf + +- name: Query non-existing VRF (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + vrf: non_existing_vrf + ignore_errors: true + register: nm_query_non_vrf + +- name: Verify query_non_vrf + assert: + that: + - cm_query_non_vrf is not changed + - nm_query_non_vrf is not changed + - cm_query_non_vrf == nm_query_non_vrf + - cm_query_non_vrf.msg == nm_query_non_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist at site level. Use mso_schema_site_vrf_region_cidr to create it." + +# USE A NON-EXISTING STATE +- name: Non-existing state for site cidr subnet (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for site cidr subnet (normal_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for site cidr subnet (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + schema: non-existing-schema + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for site cidr subnet (normal_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for site cidr subnet (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + template: non-existing-template + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for site cidr subnet (normal_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-ASSOCIATED TEMPLATE +- name: Non-associated template for site cidr subnet (check_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + template: Template 2 + check_mode: true + ignore_errors: true + register: cm_non_associated_template + +- name: Non-associated template for site cidr subnet (normal_mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + template: Template 2 + ignore_errors: true + register: nm_non_associated_template + +- name: Verify non_associated_template + assert: + that: + - cm_non_associated_template is not changed + - nm_non_associated_template is not changed + - cm_non_associated_template == nm_non_associated_template + - cm_non_associated_template.msg is match("Provided site/siteId/template 'aws_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + - nm_non_associated_template.msg is match("Provided site/siteId/template 'aws_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1") + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site cidr subnet to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + check_mode: true + ignore_errors: true + register: cm_no_site_associated + +- name: Add site cidr subnet to Template 3 without any site associated (normal mode) + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." + +# Checking if issue when adding subnet to Hub Network (#126) +- name: Add hub network in VRF1 region us-west-1 at AWS site level + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + +- name: Add a new subnet to AWS CIDR in VRF1 at site level + mso_schema_site_vrf_region_cidr_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + subnet: 10.0.0.0/24 + zone: us-west-1a + hub_network: true + state: present + register: nm_add_subnet_hub_network + +- name: Verify nm_add_subnet_hub_network + assert: + that: + - nm_add_subnet_hub_network is changed + - nm_add_subnet_hub_network.current.usage == 'gateway'
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml new file mode 100644 index 000000000..33fde6710 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml @@ -0,0 +1,719 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + sites: "['aws_{{ mso_site | default(\"ansible_test\") }}', + 'azure_{{ mso_site | default(\"ansible_test\") }}', + '{{ mso_site | default(\"ansible_test\") }}']" + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Remove Schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure sites removed from tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + # sites: + # - '{{ mso_site | default("ansible_test") }}' + users: + - '{{ mso_username }}' + state: present + +- name: Ensure AWS site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: '000000000000' + aws_access_key: 1 + secret_key: 0 + state: present + +- name: Ensure Azure site is present under tenant ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + state: present + +- name: Ensure schema 1 with Template 1 and 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Add a new CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: &mso_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.0.0.0/16 + primary: true + state: present + check_mode: true + register: cm_add_cidr + +- name: Verify cm_add_cidr + assert: + that: + - cm_add_cidr is changed + - cm_add_cidr.previous == {} + - cm_add_cidr.current.ip == '10.0.0.0/16' + - cm_add_cidr.current.primary == true + +- name: Add a new CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *mso_present + register: nm_add_cidr + +- name: Verify nm_add_cidr + assert: + that: + - nm_add_cidr is changed + - nm_add_cidr.previous == {} + - nm_add_cidr.current.ip == '10.0.0.0/16' + - nm_add_cidr.current.primary == true + +# ADD Hub Network +- name: Add hub network in VRF1 region us-west-1 at AWS site level (check mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + check_mode: true + register: cm_add_hub_network + +- name: Add hub network in VRF1 region us-west-1 at AWS site level (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + register: nm_add_hub_network + +- name: Verify cm_add_hub_network and nm_add_hub_network + assert: + that: + - cm_add_hub_network is changed + - nm_add_hub_network is changed + - cm_add_hub_network.previous == {} + - nm_add_hub_network.previous == {} + - cm_add_hub_network.current.name == "hub-test" + - cm_add_hub_network.current.tenantName == "infra" + - nm_add_hub_network.current.name == "hub-test" + - nm_add_hub_network.current.tenantName == "infra" + +# Add hub network again +- name: Add hub network again in VRF1 region us-west-1 at AWS site level (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + register: nm_add_hub_network_again + +- name: Verify nm_add_hub_network_again + assert: + that: + - nm_add_hub_network_again is not changed + - nm_add_hub_network_again.previous.name == nm_add_hub_network_again.current.name == "hub-test" + - nm_add_hub_network_again.previous.tenantName == nm_add_hub_network_again.current.tenantName == "infra" + +# Update hub network +- name: Update hub network in VRF1 region us-west-1 at AWS site level (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-default + tenant: infra + state: present + check_mode: true + register: cm_update_hub_network + +- name: Update hub network in VRF1 region us-west-1 at AWS site level (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-default + tenant: infra + state: present + register: nm_update_hub_network + +- name: Verify cm_update_hub_network and nm_update_hub_network + assert: + that: + - cm_update_hub_network is changed + - nm_update_hub_network is changed + - cm_update_hub_network.previous.name == "hub-test" + - cm_update_hub_network.previous.tenantName == "infra" + - cm_update_hub_network.current.name == "hub-default" + - cm_update_hub_network.current.tenantName == "infra" + - nm_update_hub_network.previous.name == "hub-test" + - nm_update_hub_network.previous.tenantName == "infra" + - nm_update_hub_network.current.name == "hub-default" + - nm_update_hub_network.current.tenantName == "infra" + +# Query Hub Network +- name: Query hub network in VRF1 region us-west-1 at AWS site level + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + check_mode: true + register: cm_query_hub_network + +- name: Verify cm_query_hub_network + assert: + that: + - cm_query_hub_network is not changed + - cm_query_hub_network.current.name == "hub-default" + - cm_query_hub_network.current.tenantName == "infra" + +# Remove Hub Network +- name: Remove hub network in VRF1 region us-west-1 at AWS site level (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: absent + check_mode: true + register: cm_remove_hub_network + +- name: Remove hub network in VRF1 region us-west-1 at AWS site level (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: absent + register: nm_remove_hub_network + +- name: Verify cm_remove_hub_network and nm_remove_hub_network + assert: + that: + - cm_remove_hub_network is changed + - cm_remove_hub_network.current == {} + - cm_remove_hub_network.previous.name == "hub-default" + - cm_remove_hub_network.previous.tenantName == "infra" + - nm_remove_hub_network is changed + - nm_remove_hub_network.current == {} + - nm_remove_hub_network.previous.name == "hub-default" + - nm_remove_hub_network.previous.tenantName == "infra" + +# Remove Hub Network again +- name: Remove again hub network in VRF1 region us-west-1 at AWS site level (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: absent + check_mode: true + register: cm_remove_hub_network_again + +- name: Remove again hub network in VRF1 region us-west-1 at AWS site level (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: absent + register: nm_remove_hub_network_again + +- name: Verify cm_remove_hub_network_again and nm_remove_hub_network_again + assert: + that: + - cm_remove_hub_network_again is not changed + - nm_remove_hub_network_again is not changed + - cm_remove_hub_network_again.previous == cm_remove_hub_network_again.current == {} + - nm_remove_hub_network_again.previous == nm_remove_hub_network_again.current == {} + +# query when hub network does not exist +- name: Query non_existing_hub_network + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + ignore_errors: true + register: query_non_existing_hub_network + +- name: Verify query_non_existing_hub_network + assert: + that: + - query_non_existing_hub_network.msg == "Hub network not found" + +# Re-Add hub network +- name: Re-Add hub network in VRF1 region us-west-1 at AWS site level (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + register: re_add_hub_network + +- name: Verify re_add_hub_network + assert: + that: + - re_add_hub_network is changed + - re_add_hub_network.previous == {} + - re_add_hub_network.current.name == "hub-test" + - re_add_hub_network.current.tenantName == "infra" + +# QUERY NON-EXISTING region +- name: Query non-existing region (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: non_existing_region + state: query + ignore_errors: true + check_mode: true + register: cm_query_non_region + +- name: Query non-existing region (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: non_existing_region + state: query + ignore_errors: true + register: nm_query_non_region + +- name: Verify query_non_region + assert: + that: + - cm_query_non_region is not changed + - nm_query_non_region is not changed + - cm_query_non_region == nm_query_non_region + - cm_query_non_region.msg == nm_query_non_region.msg == "Provided region 'non_existing_region' does not exist. Existing regions{{':'}} us-west-1" + +# QUERY NON-EXISTING VRF +- name: Query non-existing VRF (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: non_existing_vrf + region: us-west-1 + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_vrf + +- name: Query non-existing VRF (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: non_existing_vrf + region: us-west-1 + state: query + ignore_errors: true + register: nm_query_non_vrf + +- name: Verify query_non_vrf + assert: + that: + - cm_query_non_vrf is not changed + - nm_query_non_vrf is not changed + - cm_query_non_vrf == nm_query_non_vrf + - cm_query_non_vrf.msg == nm_query_non_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF1" + +# USE A NON-EXISTING STATE +- name: Non-existing state for site hub network (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for hub network (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for site hub network (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for site hub network (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for site hub network (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for site hub network (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON_EXISTING_SITE_TEMPLATE +- name: non_existing_site_template (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site_template + +- name: non_existing_site_template (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + ignore_errors: true + register: nm_non_existing_site_template + +- name: Verify cm_non_existing_site_template and nm_non_existing_site_template + assert: + that: + - cm_non_existing_site_template is not changed + - nm_non_existing_site_template is not changed + - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site-template association 'aws_ansible_test-Template2' does not exist." + +# USE A NON_EXISTING_SITE +- name: non_existing_site (check_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: non_existing_site + vrf: VRF1 + region: us-west-1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_site + +- name: non_existing_site (normal_mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: non_existing_site + vrf: VRF1 + region: us-west-1 + state: query + ignore_errors: true + register: nm_non_existing_site + +- name: Verify cm_non_existing_site and nm_non_existing_site + assert: + that: + - cm_non_existing_site is not changed + - nm_non_existing_site is not changed + - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name." + +# use mso_schema_site_vrf_region_cidr_subnet module to update region +- name: Add a new CIDR in VRF1 at AWS site level (check mode) + mso_schema_site_vrf_region_cidr: &cidr_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + cidr: 10.1.0.0/16 + primary: false + state: present + check_mode: true + register: cm_add_cidr + +- name: Verify cm_add_cidr + assert: + that: + - cm_add_cidr is changed + - cm_add_cidr.previous == {} + - cm_add_cidr.current.ip == '10.1.0.0/16' + - cm_add_cidr.current.primary == false + +- name: Add a new CIDR in VRF1 at AWS site level (normal mode) + mso_schema_site_vrf_region_cidr: + <<: *cidr_present + register: nm_add_cidr + +- name: Verify nm_add_cidr + assert: + that: + - nm_add_cidr is changed + - nm_add_cidr.previous == {} + - nm_add_cidr.current.ip == '10.1.0.0/16' + - nm_add_cidr.current.primary == false + +# query hub network after using mso_schema_site_vrf_region_cidr_subnet module to update region +- name: Query hub_network after region updated + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + state: query + register: query_after_region_update + +- name: Verify query_after_region_update + assert: + that: + - query_after_region_update is not changed + - query_after_region_update.current.name == "hub-test" + - query_after_region_update.current.tenantName == "infra" + +# USE A TEMPLATE WITHOUT ANY SITE +- name: Add site VRF region hub network to Schema 2 Template 3 without any site associated (check mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + ignore_errors: true + check_mode: true + register: cm_no_site_associated + +- name: Add site VRF region hub network to Template 3 without any site associated (normal mode) + mso_schema_site_vrf_region_hub_network: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + site: 'aws_{{ mso_site | default("ansible_test") }}' + vrf: VRF1 + region: us-west-1 + hub_network: + name: hub-test + tenant: infra + state: present + ignore_errors: true + register: nm_no_site_associated + +- name: Verify cm_no_site_associated and nm_no_site_associated + assert: + that: + - cm_no_site_associated is not changed + - nm_no_site_associated is not changed + - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml new file mode 100644 index 000000000..6eb612ad0 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml @@ -0,0 +1,32 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Import hub_network tasks if MSO version is higher than 3.0 + import_tasks: hub_network.yml + when: version.current.version is version('3', '>')
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml new file mode 100644 index 000000000..9823bcfd4 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml @@ -0,0 +1,391 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + ignore_errors: true + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_3' + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: '{{ item }}' + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + loop: + - 'ansible_test' + - 'ansible_test_2' + +- name: Ensure schema 1 with Template 1 exists in check mode + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + check_mode: true + register: add_template1_schema1_cm + +- name: Ensure schema 1 with Template 1 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + register: add_template1_schema1 + +- name: Ensure schema 1 with Template 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + register: add_template2_schema1 + +- name: Ensure schema 2 with Template 3 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + register: add_template3_schema2 + +- name: Ensure schema 2 with Template 3 exists again + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + register: add_template3_schema2_again + +- name: Ensure schema 3 with Template 1 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_3' + tenant: ansible_test + template: Template 1 + state: present + register: add_template1_schema3 + +- name: Ensure schema 3 with Template 2 exists + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_3' + tenant: ansible_test_2 + template: Template 2 + state: present + register: add_template2_schema3 + +- name: Update display name of Template 3 in schema 2 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + display_name: Temp 3 + state: present + register: update_template3_schema2 + +- name: Verify add + assert: + that: + - add_template1_schema1_cm is changed + - add_template1_schema1_cm.current.name == 'Template1' + - add_template1_schema1 is changed + - add_template1_schema1.current.name == 'Template1' + - add_template2_schema1 is changed + - add_template2_schema1.current.name == 'Template2' + - add_template3_schema2 is changed + - add_template3_schema2.current.name == 'Template3' + - update_template3_schema2 is changed + - add_template3_schema2_again is not changed + - add_template1_schema3 is changed + - add_template1_schema3.current.name == 'Template1' + - add_template2_schema3 is changed + - add_template2_schema3.current.name == 'Template2' + - update_template3_schema2.current.displayName == 'Temp 3' + +- name: Query Template 1 in Schema 1 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: query + register: query_template1_schema1 + +- name: Query all Templates in Schema 1 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + state: query + register: query_all_templates_schema1 + +- name: Query Template 1 in Schema 3 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_3' + tenant: ansible_test + template: Template 1 + state: query + register: query_template1_schema3 + +- name: Query Template 2 in Schema 3 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_3' + tenant: ansible_test_2 + template: Template 2 + state: query + register: query_template2_schema3 + +- name: Verify query + assert: + that: + - query_template1_schema1 is not changed + - query_template1_schema1.current.name == 'Template1' + - query_all_templates_schema1 is not changed + - query_all_templates_schema1.current | length == 2 + - query_template1_schema3 is not changed + - query_template1_schema3.current.name == 'Template1' + - query_template2_schema3 is not changed + - query_template2_schema3.current.name == 'Template2' + +- name: Remove Template 1 of Schema 1 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: absent + ignore_errors: true + register: remove_template1_schema1 + +- name: Remove Template 2 of Schema 1 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: absent + register: remove_template2_schema1 + +- name: Remove non_existing_template + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: non_existing_template + state: absent + ignore_errors: true + register: remove_template_non_existing_template + +- name: Remove Template 3 in schema 2 in check mode + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: absent + check_mode: true + register: remove_template3_schema2_cm + +- name: Remove Template 3 in schema 2 in normal mode + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: absent + register: remove_template3_schema2_nm + +- name: Remove Template 3 in schema 2 again + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: absent + register: remove_template3_schema2_nm_again + +- name: Remove Template 1 of Schema 3 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_3' + tenant: ansible_test + template: Template 1 + state: absent + register: remove_template1_schema3 + +- name: Remove Template 2 of Schema 3 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_3' + tenant: ansible_test_2 + template: Template 2 + state: absent + register: remove_template2_schema3 + +- name: non_existing_schema + mso_schema_template: + <<: *mso_info + schema: non_schema + tenant: ansible_test + template: Template 4 + state: absent + ignore_errors: true + register: remove_template_non_existing_schema + +- name: Verify remove + assert: + that: + - remove_template1_schema1.current == {} + - remove_template1_schema1.previous.name == 'Template1' + - remove_template2_schema1.current == {} + - remove_template2_schema1.previous.name == 'Template2' + - remove_template3_schema2_cm.current == {} + - remove_template3_schema2_cm.previous.name == 'Template3' + - remove_template3_schema2_nm.current == {} + - remove_template3_schema2_nm.previous.name == 'Template3' + - remove_template3_schema2_nm_again is not changed + - remove_template_non_existing_schema is not changed + - remove_template_non_existing_template is not changed + - remove_template1_schema3.current == {} + - remove_template2_schema3.current == {} + - remove_template1_schema3.previous.name == 'Template1' + - remove_template2_schema3.previous.name == 'Template2' + +# USE NON-EXISTING STATE +- name: non_existing_state state + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: non_existing_state + ignore_errors: true + register: non_existing_state + +- name: Verify non_existing_state + assert: + that: + - non_existing_state is not changed + - non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state" + +# USE A NON_EXISTING_TEMPLATE +- name: non_existing_template + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: non_existing_template + state: query + ignore_errors: true + register: non_existing_template + +- name: Verify non_existing_template + assert: + that: + - non_existing_template is not changed + - non_existing_template.msg == "Template 'non_existing_template' not found" + +- name: Template attribute absent in task + mso_schema_template: + <<: *mso_info + schema: non_schema + tenant: ansible_test + state: query + ignore_errors: true + register: absent_template + +- name: Verify absent_template + assert: + that: + - absent_template is not changed + - absent_template.current == [] + +- name: Update description schema 1 with Template 1 when version is greater than 3.3 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + schema_description: "this is schema" + tenant: ansible_test + template: Template 1 + template_description: "this is template" + state: present + register: add_description + when: version.current.version is version('3.3', '>=') + +- name: Verify add description + assert: + that: + - add_description is changed + when: version.current.version is version('3.3', '>=') + +# REMOVE Schemas for next CI Run +- name: Remove schemas for next ci test + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_3' + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +# REMOVE Tenant2 specific to this test case +- name: Remove tenant2 + mso_tenant: + <<: *mso_info + tenant: ansible_test_2 + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml new file mode 100644 index 000000000..bbd5bb1cb --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml @@ -0,0 +1,311 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_3' + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove tenant2 + mso_tenant: + <<: *mso_info + tenant: ansible_test_2 + state: absent + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure ANP exist (check_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + check_mode: true + register: cm_create_anp + +- name: Ensure ANP exist (normal_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + register: nm_create_anp + +- name: Create ANP again (normal_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + register: nm_create_anp_again + +- name: Verify cm_create_anp, nm_create_anp and nm_create_anp_again + assert: + that: + - cm_create_anp is changed + - nm_create_anp is changed + - nm_create_anp_again is not changed + - cm_create_anp.previous == {} + - cm_create_anp.current.displayName == "ANP" + - cm_create_anp.current.name == "ANP" + - cm_create_anp.current.epgs == [] + - nm_create_anp.previous == {} + - nm_create_anp.current.displayName == "ANP" + - nm_create_anp.current.name == "ANP" + - nm_create_anp.current.epgs == [] + - nm_create_anp_again.previous == nm_create_anp_again.current + - nm_create_anp_again.current.displayName == "ANP" + - nm_create_anp_again.current.name == "ANP" + - nm_create_anp_again.current.epgs == [] + +- name: Create another anp (normal_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP_2 + display_name: another anp + state: present + register: nm_create_another_anp + +- name: Verify nm_create_another_anp + assert: + that: + - nm_create_another_anp is changed + - nm_create_another_anp.previous == {} + - nm_create_another_anp.current.displayName == "another anp" + - nm_create_another_anp.current.name == "ANP_2" + - nm_create_another_anp.current.epgs == [] + + +# Add description for version >= 3.3 +- name: Create another anp with description(normal_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP_3 + display_name: another anp 3 + description: "Description of an ANP_3" + state: present + register: nm_create_another_anp + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_create_another_anp with description + assert: + that: + - nm_create_another_anp is changed + - nm_create_another_anp.previous == {} + - nm_create_another_anp.current.displayName == "another anp 3" + - nm_create_another_anp.current.name == "ANP_3" + - nm_create_another_anp.current.description == "Description of an ANP_3" + - nm_create_another_anp.current.epgs == [] + when: version.current.version is version('3.3', '>=') + +- name: Change anp (normal_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + display_name: displayName for ANP + state: present + register: nm_change_anp + +- name: Verify nm_change_anp + assert: + that: + - nm_change_anp is changed + - nm_change_anp.previous.name == nm_change_anp.current.name == "ANP" + - nm_change_anp.previous.displayName == "ANP" + - nm_change_anp.current.displayName == "displayName for ANP" + - nm_change_anp.previous.epgs == nm_change_anp.current.epgs == [] + +- name: Query anp + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: query + register: query_anp + +- name: Verify query_anp + assert: + that: + - query_anp is not changed + - query_anp.current.name == "ANP" + - query_anp.current.epgs == [] + - query_anp.current.displayName == "displayName for ANP" + +- name: Query all + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current | length == 2 + when: version.current.version is version('3.3', '<') + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current | length == 3 + when: version.current.version is version('3.3', '>=') + +- name: Query non_existing anp + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: non_existing_anp + state: query + ignore_errors: true + register: query_non_existing_anp + +- name: Verify query_non_existing_anp + assert: + that: + - query_non_existing_anp.msg == "ANP 'non_existing_anp' not found" + +- name: Use non_existing schema + mso_schema_template_anp: + <<: *mso_info + schema: non_existing_schema + template: Template 1 + anp: ANP + state: query + ignore_errors: true + register: query_non_existing_schema + +- name: Use non_existing template + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + anp: ANP + state: query + ignore_errors: true + register: query_non_existing_template + +- name: Verify query_non_existing_schema and query_non_existing_template + assert: + that: + - query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + - query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +- name: Remove anp (check_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: absent + check_mode: true + register: cm_rm_anp + +- name: Remove anp (normal_mode) + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: absent + register: nm_rm_anp + +- name: Verify cm_rm_anp and nm_rm_anp + assert: + that: + - cm_rm_anp is changed + - nm_rm_anp is changed + - nm_rm_anp.previous == cm_rm_anp.previous + - nm_rm_anp.current == cm_rm_anp.current == {} + - nm_rm_anp.previous.name == cm_rm_anp.previous.name == "ANP" + - nm_rm_anp.previous.displayName == cm_rm_anp.previous.displayName == "displayName for ANP" + - nm_rm_anp.previous.epgs == cm_rm_anp.previous.epgs == [] + +- name: Remove anp again + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: absent + register: nm_rm_anp_again + +- name: Verify nm_rm_anp_again + assert: + that: + - nm_rm_anp_again is not changed + - nm_rm_anp_again.previous == nm_rm_anp_again.current == {} diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml new file mode 100644 index 000000000..a4af6fb6d --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml @@ -0,0 +1,1263 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF exist + mso_schema_template_vrf: &vrf_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + layer3_multicast: true + state: present + +- name: Ensure VRF2 exist + mso_schema_template_vrf: + <<: *vrf_present + vrf: VRF2 + state: present + +- name: Ensure VRF3 exist + mso_schema_template_vrf: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF3 + state: present + +- name: Ensure VRF4 exist + mso_schema_template_vrf: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF4 + state: present + +- name: Ensure ANP exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + +- name: Ensure ANP2 exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + anp: ANP2 + state: present + +- name: Ensure ANP3 exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP3 + state: present + +- name: Ensure Filter 1 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1-Entry + state: present + +- name: Ensure Contract1 exist + mso_schema_template_contract_filter: &contract_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Filter 2 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + filter: Filter2 + entry: Filter2-Entry + state: present + +- name: Ensure Contract2 exist + mso_schema_template_contract_filter: &contract2_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + contract: Contract2 + filter: Filter2 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 2 + state: present + +- name: Ensure ansible_test_1 BD exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + vrf: + name: VRF + layer3_multicast: true + state: present + +- name: Ensure ansible_test_2 BD exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_2 + vrf: + name: VRF2 + template: Template 1 + layer3_multicast: true + state: present + +- name: Ensure ansible_test_3 BD exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_3 + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + layer3_multicast: true + state: present + +- name: Ensure ansible_test_4 BD exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + bd: ansible_test_4 + vrf: + name: VRF4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + layer3_multicast: true + state: present + +# ADD EPG +- name: Add EPG (check_mode) + mso_schema_template_anp_epg: &epg_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + bd: + name: ansible_test_1 + vrf: + name: VRF + state: present + check_mode: true + register: cm_add_epg + +- name: Verify cm_add_epg + assert: + that: + - cm_add_epg is changed + - cm_add_epg.previous == {} + - cm_add_epg.current.name == "ansible_test_1" + - cm_add_epg.current.vrfRef.templateName == "Template1" + - cm_add_epg.current.vrfRef.vrfName == "VRF" + - cm_add_epg.current.bdRef.templateName == "Template1" + - cm_add_epg.current.bdRef.bdName == "ansible_test_1" + +- name: Add EPG (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + register: nm_add_epg + +- name: Verify nm_add_epg + assert: + that: + - nm_add_epg is changed + - nm_add_epg.previous == {} + - nm_add_epg.current.name == "ansible_test_1" + - nm_add_epg.current.vrfRef.templateName == "Template1" + - nm_add_epg.current.vrfRef.vrfName == "VRF" + - nm_add_epg.current.bdRef.templateName == "Template1" + - nm_add_epg.current.bdRef.bdName == "ansible_test_1" + - cm_add_epg.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId + - cm_add_epg.current.bdRef.schemaId == nm_add_epg.current.bdRef.schemaId + +- name: Add EPG again (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + check_mode: true + register: cm_add_epg_again + +- name: Verify cm_add_epg_again + assert: + that: + - cm_add_epg_again is not changed + - cm_add_epg_again.current.name == cm_add_epg_again.previous.name == "ansible_test_1" + - cm_add_epg_again.current.vrfRef.templateName == cm_add_epg_again.previous.vrfRef.templateName == "Template1" + - cm_add_epg_again.current.vrfRef.vrfName == cm_add_epg_again.previous.vrfRef.vrfName == "VRF" + - cm_add_epg_again.current.bdRef.templateName == cm_add_epg_again.previous.bdRef.templateName == "Template1" + - cm_add_epg_again.current.bdRef.bdName == cm_add_epg_again.previous.bdRef.bdName == "ansible_test_1" + - cm_add_epg_again.previous.vrfRef.schemaId == cm_add_epg_again.current.vrfRef.schemaId + - cm_add_epg_again.previous.bdRef.schemaId == cm_add_epg_again.current.bdRef.schemaId + + +- name: Add EPG again (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + register: nm_add_epg_again + +- name: Verify nm_add_epg_again + assert: + that: + - nm_add_epg_again is not changed + - nm_add_epg_again.current.name == nm_add_epg_again.previous.name == "ansible_test_1" + - nm_add_epg_again.current.vrfRef.templateName == nm_add_epg_again.previous.vrfRef.templateName == "Template1" + - nm_add_epg_again.current.vrfRef.vrfName == nm_add_epg_again.previous.vrfRef.vrfName == "VRF" + - nm_add_epg_again.current.bdRef.templateName == nm_add_epg_again.previous.bdRef.templateName == "Template1" + - nm_add_epg_again.current.bdRef.bdName == nm_add_epg_again.previous.bdRef.bdName == "ansible_test_1" + - nm_add_epg_again.previous.vrfRef.schemaId == nm_add_epg_again.current.vrfRef.schemaId + - nm_add_epg_again.previous.bdRef.schemaId == nm_add_epg_again.current.bdRef.schemaId + +- name: Add EPG 2 (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + schema: '{{ mso_schema | default("ansible_test") }}' + epg: ansible_test_2 + +- name: Add EPG 3 in template of schema 1(normal mode) + mso_schema_template_anp_epg: &epg_schema_1_template_2 + <<: *epg_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + anp: ANP2 + epg: ansible_test_3 + bd: + name: ansible_test_3 + template: Template 2 + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + useg_epg: true + intra_epg_isolation: enforced + intersite_multicast_source: true + proxy_arp: true + preferred_group: true + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + - subnet: 172.16.0.1/24 + description: "My description for a subnet" + scope: public + shared: true + no_default_gateway: false + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + register: nm_add_epg_3 + +- name: Add EPG 4 in template3 of schema 2(normal mode) + mso_schema_template_anp_epg: &epg_schema_2_template_3 + <<: *epg_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP3 + epg: ansible_test_4 + bd: + name: ansible_test_4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: + name: VRF4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + register: nm_add_epg_4 + +- name: Verify nm_add_epg_3 and nm_add_epg_4 + assert: + that: + - nm_add_epg_3 is changed + - nm_add_epg_4 is changed + - nm_add_epg_3.current.name == "ansible_test_3" + - nm_add_epg_4.current.name == "ansible_test_4" + - nm_add_epg_3.current.vrfRef.templateName == "Template2" + - nm_add_epg_4.current.vrfRef.templateName == "Template3" + - nm_add_epg_3.current.vrfRef.vrfName == "VRF3" + - nm_add_epg_4.current.vrfRef.vrfName == "VRF4" + - nm_add_epg_3.current.bdRef.templateName == "Template2" + - nm_add_epg_4.current.bdRef.templateName == "Template3" + - nm_add_epg_3.current.bdRef.bdName == "ansible_test_3" + - nm_add_epg_4.current.bdRef.bdName == "ansible_test_4" + - nm_add_epg_3.current.uSegEpg == true + - nm_add_epg_3.current.intraEpg == 'enforced' + - nm_add_epg_3.current.mCastSource == true + - nm_add_epg_3.current.proxyArp == true + - nm_add_epg_3.current.preferredGroup == true + - nm_add_epg_3.current.subnets[0].description == "10.0.0.128/24" + - nm_add_epg_3.current.subnets[0].ip == "10.0.0.128/24" + - nm_add_epg_3.current.subnets[0].noDefaultGateway == false + - nm_add_epg_3.current.subnets[0].scope == "private" + - nm_add_epg_3.current.subnets[0].shared == false + - nm_add_epg_3.current.subnets[1].description == "1234567890" + - nm_add_epg_3.current.subnets[1].ip == "10.0.1.254/24" + - nm_add_epg_3.current.subnets[1].noDefaultGateway == false + - nm_add_epg_3.current.subnets[1].scope == "private" + - nm_add_epg_3.current.subnets[1].shared == false + - nm_add_epg_3.current.subnets[2].description == "My description for a subnet" + - nm_add_epg_3.current.subnets[2].ip == "172.16.0.1/24" + - nm_add_epg_3.current.subnets[2].noDefaultGateway == false + - nm_add_epg_3.current.subnets[2].scope == "public" + - nm_add_epg_3.current.subnets[2].shared == true + - nm_add_epg_3.current.subnets[3].description == "My description for a subnet" + - nm_add_epg_3.current.subnets[3].ip == "192.168.0.254/24" + - nm_add_epg_3.current.subnets[3].noDefaultGateway == true + - nm_add_epg_3.current.subnets[3].scope == "private" + - nm_add_epg_3.current.subnets[3].shared == false + +# CHANGE EPG +- name: Change EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + vrf: + name: VRF2 + bd: + name: ansible_test_2 + check_mode: true + register: cm_change_epg + +- name: Verify cm_change_epg + assert: + that: + - cm_change_epg is changed + - cm_change_epg.current.name == 'ansible_test_1' + - cm_change_epg.current.vrfRef.vrfName == 'VRF2' + - cm_change_epg.current.bdRef.templateName == cm_change_epg.current.vrfRef.templateName == "Template1" + - cm_change_epg.current.vrfRef.schemaId == cm_change_epg.previous.vrfRef.schemaId + - cm_change_epg.current.bdRef.bdName == 'ansible_test_2' + - cm_change_epg.current.bdRef.schemaId == cm_change_epg.previous.bdRef.schemaId + +- name: Change EPG (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + vrf: + name: VRF2 + bd: + name: ansible_test_2 + output_level: debug + register: nm_change_epg + +- name: Verify nm_change_epg + assert: + that: + - nm_change_epg is changed + - nm_change_epg.current.name == 'ansible_test_1' + - nm_change_epg.current.vrfRef.vrfName == 'VRF2' + - nm_change_epg.current.bdRef.templateName == nm_change_epg.current.vrfRef.templateName == "Template1" + - nm_change_epg.current.vrfRef.schemaId == nm_change_epg.previous.vrfRef.schemaId + - nm_change_epg.current.bdRef.bdName == 'ansible_test_2' + - nm_change_epg.current.bdRef.schemaId == nm_change_epg.previous.bdRef.schemaId + +- name: Change EPG again (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + vrf: + name: VRF2 + bd: + name: ansible_test_2 + check_mode: true + register: cm_change_epg_again + +- name: Verify cm_change_epg_again + assert: + that: + - cm_change_epg_again is not changed + - cm_change_epg_again.current.name == 'ansible_test_1' + - cm_change_epg_again.current.vrfRef.vrfName == 'VRF2' + - cm_change_epg_again.current.vrfRef.templateName == cm_change_epg_again.current.bdRef.templateName == "Template1" + - cm_change_epg_again.current.vrfRef.schemaId == cm_change_epg_again.previous.vrfRef.schemaId + - cm_change_epg_again.current.bdRef.bdName == 'ansible_test_2' + - cm_change_epg_again.current.bdRef.schemaId == cm_change_epg_again.previous.bdRef.schemaId + +- name: Change EPG again (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + vrf: + name: VRF2 + bd: + name: ansible_test_2 + register: nm_change_epg_again + +- name: Verify nm_change_epg_again + assert: + that: + - nm_change_epg_again is not changed + - nm_change_epg_again.current.name == 'ansible_test_1' + - nm_change_epg_again.current.vrfRef.vrfName == 'VRF2' + - nm_change_epg_again.current.vrfRef.templateName == nm_change_epg_again.current.bdRef.templateName == "Template1" + - nm_change_epg_again.current.vrfRef.schemaId == nm_change_epg_again.previous.vrfRef.schemaId + - nm_change_epg_again.current.bdRef.bdName == 'ansible_test_2' + - nm_change_epg_again.current.bdRef.schemaId == nm_change_epg_again.previous.bdRef.schemaId + +- name: Change EPG to VRF in different template (normal mode) + mso_schema_template_anp_epg: + <<: *epg_schema_1_template_2 + vrf: + name: VRF + template: Template 1 + bd: + name: ansible_test_1 + template: Template 1 + register: nm_change_epg_vrf3 + +- name: Change EPG 4 to VRF (normal mode) + mso_schema_template_anp_epg: + <<: *epg_schema_2_template_3 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP3 + epg: ansible_test_4 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: + name: ansible_test_1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + register: nm_change_epg_vrf4 + +- name: Verify nm_change_epg_vrf3 and nm_change_epg_vrf4 + assert: + that: + - nm_change_epg_vrf3 is changed + - nm_change_epg_vrf3.current.name == 'ansible_test_3' + - nm_change_epg_vrf4.current.name == 'ansible_test_4' + - nm_change_epg_vrf3.current.vrfRef.vrfName == 'VRF' + - nm_change_epg_vrf3.current.bdRef.bdName == 'ansible_test_1' + - nm_change_epg_vrf3.current.vrfRef.templateName == nm_change_epg_vrf3.current.bdRef.templateName == "Template1" + - nm_change_epg_vrf4.current.vrfRef.vrfName == 'VRF' + - nm_change_epg_vrf4.current.bdRef.bdName == 'ansible_test_1' + - nm_change_epg_vrf4.current.vrfRef.templateName == nm_change_epg_vrf4.current.bdRef.templateName == "Template1" + +- name: Change EPG 1 settings(normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + useg_epg: true + intra_epg_isolation: enforced + intersite_multicast_source: true + proxy_arp: true + preferred_group: true + subnets: + - subnet: 10.1.0.128/24 + - subnet: 10.1.1.254/24 + description: 1234567890 + - subnet: 172.17.0.1/24 + description: "My description for a subnet" + scope: public + shared: true + no_default_gateway: false + - ip: 192.168.1.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + register: nm_change_epg_1_settings + +- name: Change EPG 1 subnets (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + useg_epg: true + intra_epg_isolation: enforced + intersite_multicast_source: true + proxy_arp: true + preferred_group: true + subnets: + - subnet: 10.1.0.127/24 + - subnet: 172.17.0.1/24 + description: "New description for a subnet" + scope: private + shared: false + no_default_gateway: false + - ip: 192.168.1.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: false + register: nm_change_epg_1_subnets + +- name: Verify nm_change_epg_1_subnets + assert: + that: + - nm_change_epg_1_settings is changed + - nm_change_epg_1_settings.current.name == "ansible_test_1" + - nm_change_epg_1_settings.current.vrfRef.templateName == nm_change_epg_1_settings.current.bdRef.templateName == "Template1" + - nm_change_epg_1_settings.current.vrfRef.vrfName == "VRF" + - nm_change_epg_1_settings.current.bdRef.bdName == "ansible_test_1" + - nm_change_epg_1_settings.current.uSegEpg == true + - nm_change_epg_1_settings.current.intraEpg == 'enforced' + - nm_change_epg_1_settings.current.mCastSource == true + - nm_change_epg_1_settings.current.proxyArp == true + - nm_change_epg_1_settings.current.preferredGroup == true + - nm_change_epg_1_settings.current.subnets[0].description == "10.1.0.128/24" + - nm_change_epg_1_settings.current.subnets[0].ip == "10.1.0.128/24" + - nm_change_epg_1_settings.current.subnets[0].noDefaultGateway == false + - nm_change_epg_1_settings.current.subnets[0].scope == "private" + - nm_change_epg_1_settings.current.subnets[0].shared == false + - nm_change_epg_1_settings.current.subnets[1].description == "1234567890" + - nm_change_epg_1_settings.current.subnets[1].ip == "10.1.1.254/24" + - nm_change_epg_1_settings.current.subnets[1].noDefaultGateway == false + - nm_change_epg_1_settings.current.subnets[1].scope == "private" + - nm_change_epg_1_settings.current.subnets[1].shared == false + - nm_change_epg_1_settings.current.subnets[2].description == "My description for a subnet" + - nm_change_epg_1_settings.current.subnets[2].ip == "172.17.0.1/24" + - nm_change_epg_1_settings.current.subnets[2].noDefaultGateway == false + - nm_change_epg_1_settings.current.subnets[2].scope == "public" + - nm_change_epg_1_settings.current.subnets[2].shared == true + - nm_change_epg_1_settings.current.subnets[3].description == "My description for a subnet" + - nm_change_epg_1_settings.current.subnets[3].ip == "192.168.1.254/24" + - nm_change_epg_1_settings.current.subnets[3].noDefaultGateway == true + - nm_change_epg_1_settings.current.subnets[3].scope == "private" + - nm_change_epg_1_settings.current.subnets[3].shared == false + - nm_change_epg_1_subnets is changed + - nm_change_epg_1_subnets.current.subnets | length == 3 + - nm_change_epg_1_subnets.current.name == "ansible_test_1" + - nm_change_epg_1_subnets.current.vrfRef.templateName == nm_change_epg_1_subnets.current.bdRef.templateName == "Template1" + - nm_change_epg_1_subnets.current.vrfRef.vrfName == "VRF" + - nm_change_epg_1_subnets.current.bdRef.bdName == "ansible_test_1" + - nm_change_epg_1_subnets.current.uSegEpg == true + - nm_change_epg_1_subnets.current.intraEpg == 'enforced' + - nm_change_epg_1_subnets.current.mCastSource == true + - nm_change_epg_1_subnets.current.proxyArp == true + - nm_change_epg_1_subnets.current.preferredGroup == true + - nm_change_epg_1_subnets.current.subnets[0].description == "10.1.0.127/24" + - nm_change_epg_1_subnets.current.subnets[0].ip == "10.1.0.127/24" + - nm_change_epg_1_subnets.current.subnets[0].noDefaultGateway == false + - nm_change_epg_1_subnets.current.subnets[0].scope == "private" + - nm_change_epg_1_subnets.current.subnets[0].shared == false + - nm_change_epg_1_subnets.current.subnets[1].description == "New description for a subnet" + - nm_change_epg_1_subnets.current.subnets[1].ip == "172.17.0.1/24" + - nm_change_epg_1_subnets.current.subnets[1].noDefaultGateway == false + - nm_change_epg_1_subnets.current.subnets[1].scope == "private" + - nm_change_epg_1_subnets.current.subnets[1].shared == false + - nm_change_epg_1_subnets.current.subnets[2].description == "My description for a subnet" + - nm_change_epg_1_subnets.current.subnets[2].ip == "192.168.1.254/24" + - nm_change_epg_1_subnets.current.subnets[2].noDefaultGateway == false + - nm_change_epg_1_subnets.current.subnets[2].scope == "private" + - nm_change_epg_1_subnets.current.subnets[2].shared == false + + +# # QUERY ALL EPGs +- name: Query all EPGs in an ANP (check_mode) + mso_schema_template_anp_epg: &epg_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: query + check_mode: true + register: cm_query_all_epgs + +- name: Query all EPGs (normal mode) + mso_schema_template_anp_epg: + <<: *epg_query + register: nm_query_all_epgs + +- name: Verify query_all_epgs + assert: + that: + - cm_query_all_epgs is not changed + - nm_query_all_epgs is not changed + - cm_query_all_epgs.current | length == nm_query_all_epgs.current | length == 2 + + +# QUERY AN EPG +- name: Query EPG 1 (check_mode) + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_1 + check_mode: true + register: cm_query_epg_1 + +- name: Query EPG 1 (normal mode) + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_1 + register: nm_query_epg_1 + +- name: Query EPG 3 (normal mode) + mso_schema_template_anp_epg: + <<: *epg_query + template: Template 2 + anp: ANP2 + epg: ansible_test_3 + register: nm_query_epg_3 + +- name: Query EPG 4 (normal mode) + mso_schema_template_anp_epg: + <<: *epg_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP3 + epg: ansible_test_4 + register: nm_query_epg_4 + +- name: Verify query_epg_x + assert: + that: + - cm_query_epg_1 is not changed + - nm_query_epg_1 is not changed + - nm_query_epg_3 is not changed + - nm_query_epg_4 is not changed + - nm_query_epg_1.current.subnets | length == 3 + - nm_query_epg_1.current.name == "ansible_test_1" + - nm_query_epg_1.current.vrfRef.templateName == nm_query_epg_1.current.bdRef.templateName == "Template1" + - nm_query_epg_1.current.vrfRef.vrfName == "VRF" + - nm_query_epg_1.current.bdRef.bdName == "ansible_test_1" + - nm_query_epg_1.current.uSegEpg == true + - nm_query_epg_1.current.intraEpg == 'enforced' + - nm_query_epg_1.current.mCastSource == true + - nm_query_epg_1.current.proxyArp == true + - nm_query_epg_1.current.preferredGroup == true + - nm_query_epg_1.current.subnets[0].description == "10.1.0.127/24" + - nm_query_epg_1.current.subnets[0].ip == "10.1.0.127/24" + - nm_query_epg_1.current.subnets[0].noDefaultGateway == false + - nm_query_epg_1.current.subnets[0].scope == "private" + - nm_query_epg_1.current.subnets[0].shared == false + - nm_query_epg_1.current.subnets[1].description == "New description for a subnet" + - nm_query_epg_1.current.subnets[1].ip == "172.17.0.1/24" + - nm_query_epg_1.current.subnets[1].noDefaultGateway == false + - nm_query_epg_1.current.subnets[1].scope == "private" + - nm_query_epg_1.current.subnets[1].shared == false + - nm_query_epg_1.current.subnets[2].description == "My description for a subnet" + - nm_query_epg_1.current.subnets[2].ip == "192.168.1.254/24" + - nm_query_epg_1.current.subnets[2].noDefaultGateway == false + - nm_query_epg_1.current.subnets[2].scope == "private" + - nm_query_epg_1.current.subnets[2].shared == false + - nm_query_epg_3.current.name == "ansible_test_3" + - nm_query_epg_4.current.name == "ansible_test_4" + - nm_query_epg_3.current.vrfRef.templateName == nm_query_epg_4.current.vrfRef.templateName == "Template1" + - nm_query_epg_3.current.vrfRef.vrfName == nm_query_epg_4.current.vrfRef.vrfName == "VRF" + - nm_query_epg_3.current.bdRef.templateName == nm_query_epg_4.current.bdRef.templateName == "Template1" + - nm_query_epg_3.current.bdRef.bdName == nm_query_epg_4.current.bdRef.bdName == "ansible_test_1" + - nm_query_epg_3.current.uSegEpg == true + - nm_query_epg_3.current.intraEpg == 'enforced' + - nm_query_epg_3.current.mCastSource == true + - nm_query_epg_3.current.proxyArp == true + - nm_query_epg_3.current.preferredGroup == true + - nm_query_epg_3.current.subnets[0].description == "10.0.0.128/24" + - nm_query_epg_3.current.subnets[0].ip == "10.0.0.128/24" + - nm_query_epg_3.current.subnets[0].noDefaultGateway == false + - nm_query_epg_3.current.subnets[0].scope == "private" + - nm_query_epg_3.current.subnets[0].shared == false + - nm_query_epg_3.current.subnets[1].description == "1234567890" + - nm_query_epg_3.current.subnets[1].ip == "10.0.1.254/24" + - nm_query_epg_3.current.subnets[1].noDefaultGateway == false + - nm_query_epg_3.current.subnets[1].scope == "private" + - nm_query_epg_3.current.subnets[1].shared == false + - nm_query_epg_3.current.subnets[2].description == "My description for a subnet" + - nm_query_epg_3.current.subnets[2].ip == "172.16.0.1/24" + - nm_query_epg_3.current.subnets[2].noDefaultGateway == false + - nm_query_epg_3.current.subnets[2].scope == "public" + - nm_query_epg_3.current.subnets[2].shared == true + - nm_query_epg_3.current.subnets[3].description == "My description for a subnet" + - nm_query_epg_3.current.subnets[3].ip == "192.168.0.254/24" + - nm_query_epg_3.current.subnets[3].noDefaultGateway == true + - nm_query_epg_3.current.subnets[3].scope == "private" + - nm_query_epg_3.current.subnets[3].shared == false + +# REMOVE EPG +- name: Remove EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + state: absent + check_mode: true + register: cm_remove_epg + +- name: Verify cm_remove_epg + assert: + that: + - cm_remove_epg is changed + - cm_remove_epg.current == {} + +- name: Remove EPG (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + state: absent + register: nm_remove_epg + +- name: Verify nm_remove_epg + assert: + that: + - nm_remove_epg is changed + - nm_remove_epg.current == {} + +- name: Remove EPG again (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + state: absent + check_mode: true + register: cm_remove_epg_again + +- name: Verify cm_remove_epg_again + assert: + that: + - cm_remove_epg_again is not changed + - cm_remove_epg_again.current == {} + +- name: Remove EPG again (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + state: absent + register: nm_remove_epg_again + +- name: Verify nm_remove_epg_again + assert: + that: + - nm_remove_epg_again is not changed + - nm_remove_epg_again.current == {} + +- name: Remove EPG (normal mode) + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: absent + ignore_errors: true + +# Add EPG when MSO version >= 3.3 +- name: Execute tasks only for MSO version >= 3.3 + when: + - version.current.version is version('3.3', '>=') + block: + - name: Add EPG (for version greater than 3.3) + mso_schema_template_anp_epg: &new_epg + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + description: 'Description of ANP EPG' + state: present + register: add_epg_desc + + - name: Verify Add description + assert: + that: + - add_epg_desc is changed + - add_epg_desc.current.description == "Description of ANP EPG" + + - name: Remove EPG (for version greater than 3.3) + mso_schema_template_anp_epg: + <<: *new_epg + state: absent + +- name: Execute tasks only for MSO version >= 4.0 + when: + - version.current.version is version('4.0', '>=') + block: + - name: Add EPG service type parameters (for version greater than 4.0) + mso_schema_template_anp_epg: + <<: *new_epg + epg_type: 'service' + deployment_type: 'third_party' + service_type: 'Azure-Storage' + access_type: 'private' + register: service_type_epg + + - name: Add EPG service type parameters (for version greater than 4.0) with error + mso_schema_template_anp_epg: + <<: *new_epg + epg_type: 'service' + deployment_type: 'third_party' + service_type: 'Azure-Storage' + access_type: 'public_and_private' + ignore_errors: true + register: service_type_epg_error + + - name: Verify service type error + assert: + that: + - service_type_epg_error.msg == "MSO Error 400{{':'}} EPG{{':'}} ansible_test_1 in Schema{{':'}} ansible_test , Template{{':'}} Template1 DeploymentType{{':'}} saas, AccessType {{':'}}publicAndPrivateType, Combination is not supported" + +# Add EPG when MSO version >= 3.3 and < 4.0 +- name: Execute tasks only for MSO version >= 3.3 and < 4.0 + when: + - version.current.version is version('3.3', '>=') + - version.current.version is version('4.0', '<') + block: + - name: Add EPG service type parameters (for version greater than 3.3 and less than 4.0) + mso_schema_template_anp_epg: + <<: *new_epg + epg_type: 'service' + deployment_type: 'third_party' + service_type: 'Azure-Storage' + access_type: 'public_and_private' + register: add_epg + + - name: Get Validation status for service type parameters + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: query_validate + + - name: Verify validation + assert: + that: + - query_validate is not changed + - query_validate.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} For Deployment type 'Third-party' access type 'PublicAndPrivate' is not supported exception while trying to update schema" + + - name: Add EPG service type parameters (for version greater than 3.3 and less than 4.0) + mso_schema_template_anp_epg: + <<: *new_epg + epg_type: 'service' + deployment_type: 'cloud_native' + service_type: 'Azure-Storage' + access_type: 'public' + register: add_epg + + - name: Get Validation status for service type parameters + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: query_validate + + - name: Verify validation + assert: + that: + - query_validate is not changed + - query_validate.current.result == "true" + + - name: Add new EPG service type parameters (for version greater than 3.3 and less than 4.0) + mso_schema_template_anp_epg: + <<: *new_epg + epg_type: 'service' + deployment_type: 'third_party' + service_type: 'Azure-Storage' + access_type: 'private' + register: add_epg + + - name: Get Validation status for service type parameters + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: query_validate + + - name: Verify validation + assert: + that: + - query_validate is not changed + - query_validate.current.result == "true" + +# Add QoS level to EPG +- name: Add EPG (for version greater than 3.1) + mso_schema_template_anp_epg: + <<: *epg_present + name: ansible_test_5 + qos_level: 'level2' + register: add_epg + when: version.current.version is version('3.1', '>=') + +- name: Verify Add contract for version greater than 3.1 + assert: + that: + - add_epg is changed + when: version.current.version is version('3.1', '>=') + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_query + epg: non_existing_epg + check_mode: true + ignore_errors: true + register: cm_query_non_epg + +- name: Query non-existing EPG (normal mode) + mso_schema_template_anp_epg: + <<: *epg_query + epg: non_existing_epg + ignore_errors: true + register: nm_query_non_epg + +- name: Verify query_non_epg + assert: + that: + - cm_query_non_epg is not changed + - nm_query_non_epg is not changed + - cm_query_non_epg == nm_query_non_epg + - cm_query_non_epg.msg == nm_query_non_epg.msg == "EPG 'non_existing_epg' not found" + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP (check_mode) + mso_schema_template_anp_epg: + <<: *epg_query + anp: non_existing_anp + check_mode: true + ignore_errors: true + register: cm_query_non_anp + +- name: Query non-existing ANP (normal mode) + mso_schema_template_anp_epg: + <<: *epg_query + anp: non_existing_anp + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - cm_query_non_anp is not changed + - nm_query_non_anp is not changed + - cm_query_non_anp == nm_query_non_anp + - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP" + +# USE A NON-EXISTING STATE +- name: Non-existing state for EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_2 + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for EPG (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_2 + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + schema: non-existing-schema + epg: ansible_test_2 + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for EPG (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_present + schema: non-existing-schema + epg: ansible_test_2 + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +- name: Non-existing BD schema for EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + epg: ansible_test_2 + bd: + name: ansible_test_1 + schema: non-existing-schema + template: Template 1 + check_mode: true + ignore_errors: true + register: cm_non_existing_bd_schema + +- name: Non-existing BD schema for EPG (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_present + epg: ansible_test_2 + bd: + name: ansible_test_1 + schema: non-existing-schema + template: Template 1 + ignore_errors: true + register: nm_non_existing_bd_schema + +- name: Verify non_existing_bd_schema + assert: + that: + - cm_non_existing_bd_schema is not changed + - nm_non_existing_bd_schema is not changed + - cm_non_existing_bd_schema == nm_non_existing_bd_schema + - cm_non_existing_bd_schema.msg == nm_non_existing_bd_schema.msg == "Referenced schema 'non-existing-schema' in bdref does not exist" + +- name: Non-existing VRF schema for EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + epg: ansible_test_2 + vrf: + name: VRF + schema: non-existing-schema + template: Template 1 + check_mode: true + ignore_errors: true + register: cm_non_existing_vrf_schema + +- name: Non-existing VRF schema for EPG (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_present + epg: ansible_test_2 + vrf: + name: VRF + schema: non-existing-schema + template: Template 1 + ignore_errors: true + register: nm_non_existing_vrf_schema + +- name: Verify non_existing_vrf_schema + assert: + that: + - cm_non_existing_vrf_schema is not changed + - nm_non_existing_vrf_schema is not changed + - cm_non_existing_vrf_schema == nm_non_existing_vrf_schema + - cm_non_existing_vrf_schema.msg == nm_non_existing_vrf_schema.msg == "Referenced schema 'non-existing-schema' in vrfref does not exist" + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for EPG (check_mode) + mso_schema_template_anp_epg: + <<: *epg_present + template: non-existing-template + epg: ansible_test_2 + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for EPG (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_present + template: non-existing-template + epg: ansible_test_2 + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# Checking if contract are removed after re-applying an EPG. (#13 | #62137) +- name: Reset EPG 3 in template of schema 1 to avoid cyclic circles (normal mode) + mso_schema_template_anp_epg: + <<: *epg_schema_1_template_2 + register: nm_add_epg_3 + +- name: Add Contracts to EPG 2 + mso_schema_template_anp_epg_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_2 + contract: + name: '{{ item.name }}' + template: '{{ item.template }}' + type: '{{ item.type }}' + state: present + loop: + - { name: Contract1, template: Template 1, type: consumer } + - { name: Contract1, template: Template 1, type: provider } + - { name: Contract2, template: Template 2, type: consumer } + - { name: Contract2, template: Template 2, type: provider } + +- name: Query EPG 2 + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_2 + register: nm_query_contract_epg + +- name: Verify that 4 contracts are in EPG 2 using nm_query_contract_epg + assert: + that: + - nm_query_contract_epg.current.contractRelationships | length == 4 + +- name: Add EPG 2 again (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_present + epg: ansible_test_2 + register: nm_add_epg_2_again + +- name: Verify that EPG 2 didn't change + assert: + that: + - nm_add_epg_2_again is not changed + +- name: Query EPG 2 + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_2 + register: nm_query_contract_epg + +- name: Verify that 4 contracts are in EPG 2 using nm_query_contract_epg + assert: + that: + - nm_query_contract_epg.current.contractRelationships | length == 4 + +# Checking if issue when querying EPG and VRF is not defined (#66) +- name: Add new test EPG 3 (normal mode) + mso_schema_template_anp_epg: &epg_present_2 + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_3 + bd: + name: ansible_test_1 + register: nm_add_epg_3 + +- name: Verify nm_add_epg_3 + assert: + that: + - nm_add_epg_3 is changed + - nm_add_epg_3.current.name == 'ansible_test_3' + - "'vrfRef' not in nm_add_epg_3.current" + +- name: Query test EPG 3 + mso_schema_template_anp_epg: + <<: *epg_present_2 + register: nm_query_epg_3 + +- name: Verify nm_query_epg_3 + assert: + that: + - nm_query_epg_3 is not changed + - nm_query_epg_3.current.name == 'ansible_test_3' + - "'vrfRef' not in nm_query_epg_3.current" + +# Checking if modifying an EPG with existing contracts throw an MSO error. (#82) +- name: Change EPG 2 to add VRF (normal_mode) + mso_schema_template_anp_epg: + <<: *epg_present + epg: ansible_test_2 + vrf: + name: VRF2 + bd: + name: ansible_test_2 + register: nm_change_epg_2_vrf + +- name: Verify that EPG 2 did change + assert: + that: + - nm_change_epg_2_vrf is changed + - nm_change_epg_2_vrf.current.vrfRef.templateName == "Template1" + - nm_change_epg_2_vrf.current.vrfRef.vrfName == "VRF2" + - nm_change_epg_2_vrf.current.bdRef.bdName == "ansible_test_2" + +- name: Query EPG 2 + mso_schema_template_anp_epg: + <<: *epg_query + epg: ansible_test_2 + register: nm_query_contract_epg_2 + +- name: Verify that 4 contracts are in EPG 2 using nm_query_contract_epg_2 + assert: + that: + - nm_query_contract_epg_2.current.contractRelationships | length == 4
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml new file mode 100644 index 000000000..0027cfda4 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml @@ -0,0 +1,620 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +# - name: Ensure site exist +# mso_site: &site_present +# host: '{{ mso_hostname }}' +# username: '{{ mso_username }}' +# password: '{{ mso_password }}' +# validate_certs: '{{ mso_validate_certs | default(false) }}' +# use_ssl: '{{ mso_use_ssl | default(true) }}' +# use_proxy: '{{ mso_use_proxy | default(true) }}' +# output_level: '{{ mso_output_level | default("info") }}' +# site: '{{ mso_site | default("ansible_test") }}' +# apic_username: '{{ apic_username }}' +# apic_password: '{{ apic_password }}' +# apic_site_id: '{{ apic_site_id | default(101) }}' +# urls: +# - https://{{ apic_hostname }} +# state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure ANP exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3' } + +- name: Ensure Filter 1 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1-Entry + state: present + +- name: Ensure Contract1 exist + mso_schema_template_contract_filter: &contract_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Filter 2 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + filter: Filter2 + entry: Filter2-Entry + state: present + +- name: Ensure Contract2 exist + mso_schema_template_contract_filter: &contract2_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + contract: Contract2 + filter: Filter2 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 2 + state: present + +- name: Ensure EPGs exist + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + epg: '{{ item.epg }}' + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_1' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3', epg: 'ansible_test_3' } + +# ADD Contract to EPG +- name: Add Contract1 to EPG (check_mode) + mso_schema_template_anp_epg_contract: &contract_epg_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + contract: + name: Contract1 + type: consumer + state: present + check_mode: true + register: cm_add_contract_rel + +- name: Verify cm_add_contract_rel + assert: + that: + - cm_add_contract_rel is changed + - cm_add_contract_rel.previous == {} + - cm_add_contract_rel.current.contractRef.templateName == "Template1" + - cm_add_contract_rel.current.contractRef.contractName == "Contract1" + - cm_add_contract_rel.current.relationshipType == "consumer" + +- name: Add Contract to EPG (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + register: nm_add_contract_rel + +- name: Verify nm_add_contract_rel + assert: + that: + - nm_add_contract_rel is changed + - nm_add_contract_rel.previous == {} + - nm_add_contract_rel.current.contractRef.templateName == "Template1" + - nm_add_contract_rel.current.contractRef.contractName == "Contract1" + - nm_add_contract_rel.current.relationshipType == "consumer" + - cm_add_contract_rel.current.contractRef.schemaId == nm_add_contract_rel.current.contractRef.schemaId + +- name: Add Contract to EPG again (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + check_mode: true + register: cm_add_contract_rel_again + +- name: Verify cm_add_contract_rel_again + assert: + that: + - cm_add_contract_rel_again is not changed + - cm_add_contract_rel_again.previous.contractRef.templateName == "Template1" + - cm_add_contract_rel_again.current.contractRef.templateName == "Template1" + - cm_add_contract_rel_again.previous.contractRef.contractName == "Contract1" + - cm_add_contract_rel_again.current.contractRef.contractName == "Contract1" + - cm_add_contract_rel_again.previous.relationshipType == "consumer" + - cm_add_contract_rel_again.current.relationshipType == "consumer" + - cm_add_contract_rel_again.previous.contractRef.schemaId == cm_add_contract_rel_again.current.contractRef.schemaId + + +- name: Add Contract to EPG again (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + register: nm_add_contract_rel_again + +- name: Verify nm_add_contract_rel_again + assert: + that: + - nm_add_contract_rel_again is not changed + - nm_add_contract_rel_again.previous.contractRef.templateName == "Template1" + - nm_add_contract_rel_again.current.contractRef.templateName == "Template1" + - nm_add_contract_rel_again.previous.contractRef.contractName == "Contract1" + - nm_add_contract_rel_again.current.contractRef.contractName == "Contract1" + - nm_add_contract_rel_again.previous.relationshipType == "consumer" + - nm_add_contract_rel_again.current.relationshipType == "consumer" + - nm_add_contract_rel_again.previous.contractRef.schemaId == nm_add_contract_rel_again.current.contractRef.schemaId + +- name: Add Contract1 to EPG - provider (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + contract: + name: Contract1 + type: provider + register: nm_add_contract1_rel_provider + +- name: Add Contract2 to EPG - consumer (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + contract: + name: Contract2 + template: Template 2 + type: consumer + register: nm_add_contract2_rel_consumer + +- name: Add Contract1 to EPG 3 - provider (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP + epg: ansible_test_3 + contract: + name: Contract1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + type: provider + register: nm_add_contract3_rel_provider + +- name: Verify nm_add_contract1_rel_provider, nm_add_contract2_rel_consumer and nm_add_contract3_rel_provider + assert: + that: + - nm_add_contract1_rel_provider is changed + - nm_add_contract2_rel_consumer is changed + - nm_add_contract3_rel_provider is changed + - nm_add_contract1_rel_provider.current.contractRef.contractName == nm_add_contract3_rel_provider.current.contractRef.contractName == "Contract1" + - nm_add_contract2_rel_consumer.current.contractRef.contractName == "Contract2" + - nm_add_contract1_rel_provider.current.contractRef.templateName == nm_add_contract3_rel_provider.current.contractRef.templateName == "Template1" + - nm_add_contract2_rel_consumer.current.contractRef.templateName == "Template2" + - nm_add_contract1_rel_provider.current.contractRef.schemaId == nm_add_contract2_rel_consumer.current.contractRef.schemaId == nm_add_contract3_rel_provider.current.contractRef.schemaId + - nm_add_contract2_rel_consumer.current.relationshipType == "consumer" + - nm_add_contract1_rel_provider.current.relationshipType == nm_add_contract3_rel_provider.current.relationshipType == "provider" + +# # QUERY ALL Contract to EPG +- name: Query all contract relationship for EPG (check_mode) + mso_schema_template_anp_epg_contract: &contract_epg_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + check_mode: true + register: cm_query_all_contract_rels + +- name: Query all contract relationship for EPG (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + register: nm_query_all_contract_rels + +- name: Verify query_all_contract_rels + assert: + that: + - cm_query_all_contract_rels is not changed + - nm_query_all_contract_rels is not changed + - cm_query_all_contract_rels.current | length == nm_query_all_contract_rels.current | length == 3 + + +# QUERY A Contract to EPG +- name: Query Contract1 relationship for EPG - consumer (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + type: consumer + check_mode: true + register: cm_query_contract1_consumer_rel + +- name: Query Contract1 relationship for EPG - consumer (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + type: consumer + register: nm_query_contract1_consumer_rel + +- name: Query Contract1 relationship for EPG - provider (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + type: provider + register: nm_query_contract1_provider_rel + +- name: Query Contract1 relationship for EPG - consumer (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract2 + template: Template 2 + type: consumer + register: nm_query_contract2_consumer_rel + +- name: Query Contract1 relationship for EPG - provider (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP + epg: ansible_test_3 + contract: + name: Contract1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + type: provider + register: nm_query_contract3_provider_rel + +- name: Verify query_contractX_YYYYY_rel + assert: + that: + - cm_query_contract1_consumer_rel is not changed + - nm_query_contract1_consumer_rel is not changed + - nm_query_contract1_provider_rel is not changed + - nm_query_contract2_consumer_rel is not changed + - nm_query_contract3_provider_rel is not changed + - cm_query_contract1_consumer_rel == nm_query_contract1_consumer_rel + - cm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_provider_rel.current.contractRef.contractName == nm_query_contract3_provider_rel.current.contractRef.contractName == "Contract1" + - nm_query_contract2_consumer_rel.current.contractRef.contractName == "Contract2" + - cm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_provider_rel.current.contractRef.templateName == nm_query_contract3_provider_rel.current.contractRef.templateName == "Template1" + - nm_query_contract2_consumer_rel.current.contractRef.templateName == "Template2" + - cm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_provider_rel.current.contractRef.schemaId == nm_query_contract2_consumer_rel.current.contractRef.schemaId == nm_query_contract3_provider_rel.current.contractRef.schemaId + - cm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract2_consumer_rel.current.relationshipType == "consumer" + - nm_query_contract1_provider_rel.current.relationshipType == nm_query_contract3_provider_rel.current.relationshipType == "provider" + + +# REMOVE Contract to EPG +- name: Remove Contract to EPG (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + state: absent + check_mode: true + register: cm_remove_contract_rel + +- name: Verify cm_remove_contract_rel + assert: + that: + - cm_remove_contract_rel is changed + - cm_remove_contract_rel.current == {} + +- name: Remove Contract to EPG (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + state: absent + register: nm_remove_contract_rel + +- name: Verify nm_remove_contract_rel + assert: + that: + - nm_remove_contract_rel is changed + - nm_remove_contract_rel.current == {} + +- name: Remove Contract to EPG again (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + state: absent + check_mode: true + register: cm_remove_contract_rel_again + +- name: Verify cm_remove_contract_rel_again + assert: + that: + - cm_remove_contract_rel_again is not changed + - cm_remove_contract_rel_again.current == {} + +- name: Remove Contract to EPG again (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_present + state: absent + register: nm_remove_contract_rel_again + +- name: Verify nm_remove_contract_rel_again + assert: + that: + - nm_remove_contract_rel_again is not changed + - nm_remove_contract_rel_again.current == {} + + +# QUERY NON-EXISTING Contract to EPG +- name: Query non-existing contract (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: non_existing_contract + type: provider + check_mode: true + ignore_errors: true + register: cm_query_non_contract + +- name: Query non-existing contract (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: non_existing_contract + type: provider + ignore_errors: true + register: nm_query_non_contract + +- name: Verify query_non_contract + assert: + that: + - cm_query_non_contract is not changed + - nm_query_non_contract is not changed + - cm_query_non_contract == nm_query_non_contract + - cm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found") + - nm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found") + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + epg: non_existing_epg + check_mode: true + ignore_errors: true + register: cm_query_non_epg + +- name: Query non-existing EPG (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + epg: non_existing_epg + ignore_errors: true + register: nm_query_non_epg + +- name: Verify query_non_epg + assert: + that: + - cm_query_non_epg is not changed + - nm_query_non_epg is not changed + - cm_query_non_epg == nm_query_non_epg + - cm_query_non_epg.msg == nm_query_non_epg.msg == "Provided epg 'non_existing_epg' does not exist. Existing epgs{{':'}} ansible_test_1" + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + anp: non_existing_anp + check_mode: true + ignore_errors: true + register: cm_query_non_anp + +- name: Query non-existing ANP (normal mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + anp: non_existing_anp + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - cm_query_non_anp is not changed + - nm_query_non_anp is not changed + - cm_query_non_anp == nm_query_non_anp + - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP" + +# USE A NON-EXISTING STATE +- name: Non-existing state for contract relationship (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for contract relationship (normal_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for contract relationship (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + schema: non-existing-schema + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for contract relationship (normal_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +- name: Non-existing contract schema for contract relationship (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + schema: non-existing-schema + template: Template 1 + type: provider + check_mode: true + ignore_errors: true + register: cm_non_existing_contract_schema + +- name: Non-existing contract schema for contract relationship (normal_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + schema: non-existing-schema + template: Template 1 + type: provider + ignore_errors: true + register: nm_non_existing_contract_schema + +- name: Verify non_existing_contract_schema + assert: + that: + - cm_non_existing_contract_schema is not changed + - nm_non_existing_contract_schema is not changed + - cm_non_existing_contract_schema == nm_non_existing_contract_schema + - cm_non_existing_contract_schema.msg == nm_non_existing_contract_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for contract relationship (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + template: non-existing-template + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for contract relationship (normal_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +- name: Non-existing contract template for contract relationship (check_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + template: non-existing-template + type: provider + check_mode: true + ignore_errors: true + register: cm_non_existing_contract_template + +- name: Non-existing contract template for contract relationship (normal_mode) + mso_schema_template_anp_epg_contract: + <<: *contract_epg_query + contract: + name: Contract1 + template: non-existing-template + type: provider + ignore_errors: true + register: nm_non_existing_contract_template + +- name: Verify non_existing_contract_template + assert: + that: + - cm_non_existing_contract_template is not changed + - nm_non_existing_contract_template is not changed + - cm_non_existing_contract_template == nm_non_existing_contract_template + - cm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found") + - nm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found")
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml new file mode 100644 index 000000000..013b96b71 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml @@ -0,0 +1,794 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +# - name: Ensure site exist +# mso_site: &site_present +# host: '{{ mso_hostname }}' +# username: '{{ mso_username }}' +# password: '{{ mso_password }}' +# validate_certs: '{{ mso_validate_certs | default(false) }}' +# use_ssl: '{{ mso_use_ssl | default(true) }}' +# use_proxy: '{{ mso_use_proxy | default(true) }}' +# output_level: '{{ mso_output_level | default("info") }}' +# site: '{{ mso_site | default("ansible_test") }}' +# apic_username: '{{ apic_username }}' +# apic_password: '{{ apic_password }}' +# apic_site_id: '{{ apic_site_id | default(101) }}' +# urls: +# - https://{{ apic_hostname }} +# state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure ANP exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3' } + +# ADD EPGs +- name: Ensure EPGs exist + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + epg: '{{ item.epg }}' + state: present + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_1' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3', epg: 'ansible_test_3' } + +# ADD Selector to EPG +- name: Add Selector to EPG (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: present + check_mode: true + register: cm_add_selector_1 + +- name: Verify cm_add_selector_1 + assert: + that: + - cm_add_selector_1 is changed + - cm_add_selector_1.previous == {} + - cm_add_selector_1.current.name == "selector_1" + - cm_add_selector_1.current.expressions == [] + +- name: Add Selector 1 to EPG with space in selector name (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector 1 + state: present + ignore_errors: true + register: nm_add_selector1_with_space_in_name + +- name: Verify nm_add_selector1_with_space_in_name + assert: + that: + - nm_add_selector1_with_space_in_name is not changed + - nm_add_selector1_with_space_in_name.msg == "There should not be any space in selector name." + +- name: Add Selector to EPG (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: present + register: nm_add_selector_1 + +- name: Verify nm_add_selector_1 + assert: + that: + - nm_add_selector_1 is changed + - nm_add_selector_1.previous == {} + - nm_add_selector_1.current.name == "selector_1" + - nm_add_selector_1.current.expressions == [] + +- name: Add Selector to EPG again (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: present + check_mode: true + register: cm_add_selector_1_again + +- name: Verify cm_add_selector_1_again + assert: + that: + - cm_add_selector_1_again is not changed + - cm_add_selector_1_again.previous.name == "selector_1" + - cm_add_selector_1_again.previous.expressions == [] + - cm_add_selector_1_again.current.name == "selector_1" + - cm_add_selector_1_again.current.expressions == [] + +- name: Add Selector to EPG again (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: present + register: nm_add_selector_1_again + +- name: Verify nm_add_selector_1_again + assert: + that: + - nm_add_selector_1_again is not changed + - nm_add_selector_1_again.previous.name == "selector_1" + - nm_add_selector_1_again.previous.expressions == [] + - nm_add_selector_1_again.current.name == "selector_1" + - nm_add_selector_1_again.current.expressions == [] + +- name: Add Selector 2 to EPG (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_1 + operator: in + value: test + state: present + check_mode: true + register: cm_add_selector_2 + +- name: Verify cm_add_selector_2 + assert: + that: + - cm_add_selector_2 is changed + - cm_add_selector_2.previous == {} + - cm_add_selector_2.current.name == "selector_2" + - cm_add_selector_2.current.expressions[0].key == "Custom:expression_1" + - cm_add_selector_2.current.expressions[0].operator == "in" + - cm_add_selector_2.current.expressions[0].value == "test" + +- name: Add Selector 2 to EPG (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_1 + operator: in + value: test + state: present + register: nm_add_selector_2 + +- name: Verify nm_add_selector_2 + assert: + that: + - nm_add_selector_2 is changed + - nm_add_selector_2.previous == {} + - nm_add_selector_2.current.name == "selector_2" + - nm_add_selector_2.current.expressions[0].key == "Custom:expression_1" + - nm_add_selector_2.current.expressions[0].operator == "in" + - nm_add_selector_2.current.expressions[0].value == "test" + +- name: Add Selector 2 to EPG with space in expression type (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression 1 + operator: in + value: test + state: present + ignore_errors: true + register: nm_add_selector2_with_space_in_expression_type + +- name: Verify nm_add_selector2_with_space_in_expression_type + assert: + that: + - nm_add_selector2_with_space_in_expression_type is not changed + - nm_add_selector2_with_space_in_expression_type.msg == "There should not be any space in 'type' attribute of expression 'expression 1'" + +- name: Change Selector 2 - keyExist(normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_5 + operator: has_key + value: test + state: present + ignore_errors: true + register: nm_change_selector_2_key_exist + +- name: Verify nm_change_selector_2_key_exist + assert: + that: + - nm_change_selector_2_key_exist is not changed + - nm_change_selector_2_key_exist.msg == "Attribute 'value' is not supported for operator 'has_key' in expression 'expression_5'" + +- name: Change Selector 2 - keyNotExist (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_6 + operator: does_not_have_key + value: test + state: present + ignore_errors: true + register: nm_change_selector_2_key_not_exist + +- name: Verify nm_change_selector_2_key_not_exist + assert: + that: + - nm_change_selector_2_key_not_exist is not changed + - nm_change_selector_2_key_not_exist.msg == "Attribute 'value' is not supported for operator 'does_not_have_key' in expression 'expression_6'" + +- name: Change Selector 2 - equals (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_6 + operator: equals + state: present + ignore_errors: true + register: nm_change_selector_2_equals + +- name: Verify nm_change_selector_2_equals + assert: + that: + - nm_change_selector_2_equals is not changed + - nm_change_selector_2_equals.msg == "Attribute 'value' needed for operator 'equals' in expression 'expression_6'" + +- name: Change Selector 2 expressions (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_1 + operator: in + value: test + - type: expression_2 + operator: not_in + value: test + - type: expression_3 + operator: equals + value: test + - type: expression_4 + operator: not_equals + value: test + - type: expression_5 + operator: has_key + value: + - type: expression_6 + operator: does_not_have_key + state: present + register: nm_change_selector_2 + +- name: Verify nm_change_selector_2 + assert: + that: + - nm_change_selector_2 is changed + - nm_change_selector_2.current.name == "selector_2" + - nm_change_selector_2.current.expressions | length == 6 + +- name: Change Selector 2 expressions again (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + expressions: + - type: expression_1 + operator: in + value: test + - type: expression_2 + operator: not_in + value: test + - type: expression_3 + operator: equals + value: test + - type: expression_4 + operator: not_equals + value: test + - type: expression_5 + operator: has_key + value: + - type: expression_6 + operator: does_not_have_key + state: present + register: nm_change_selector_2_again + +- name: Verify nm_change_selector_2_again + assert: + that: + - nm_change_selector_2_again is not changed + - nm_change_selector_2_again.current.name == "selector_2" + - nm_change_selector_2_again.current.expressions | length == 6 + +- name: Query all selectors (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + check_mode: true + register: cm_query_all_selectors + +- name: Verify cm_query_all_selectors + assert: + that: + - cm_query_all_selectors is not changed + - cm_query_all_selectors.current | length == 2 + - cm_query_all_selectors.current[0].name == "selector_1" + - cm_query_all_selectors.current[1].name == "selector_2" + - cm_query_all_selectors.current[0].expressions == [] + - cm_query_all_selectors.current[1].expressions | length == 6 + +- name: Query all selectors (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + state: query + register: nm_query_all_selectors + +- name: Verify nm_query_all_selectors + assert: + that: + - nm_query_all_selectors is not changed + - nm_query_all_selectors.current | length == 2 + - nm_query_all_selectors.current[0].name == "selector_1" + - nm_query_all_selectors.current[1].name == "selector_2" + - nm_query_all_selectors.current[0].expressions == [] + - nm_query_all_selectors.current[1].expressions | length == 6 + +- name: Query specific selector (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: query + check_mode: true + register: cm_query_selector1 + +- name: Verify cm_query_selector1 + assert: + that: + - cm_query_selector1 is not changed + - cm_query_selector1.current.name == "selector_1" + - cm_query_selector1.current.expressions == [] + +- name: Query specific selector (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: query + register: nm_query_selector1 + +- name: Verify nm_query_selector1 + assert: + that: + - nm_query_selector1 is not changed + - nm_query_selector1.current.name == "selector_1" + - nm_query_selector1.current.expressions == [] + +- name: Query specific selector2 (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + state: query + register: nm_query_selector2 + +- name: Verify nm_query_selector2 + assert: + that: + - nm_query_selector2 is not changed + - nm_query_selector2.current.name == "selector_2" + - nm_query_selector2.current.expressions | length == 6 + - nm_query_selector2.current.expressions[0].key == "Custom:expression_1" + - nm_query_selector2.current.expressions[0].operator == "in" + - nm_query_selector2.current.expressions[0].value == "test" + - nm_query_selector2.current.expressions[1].key == "Custom:expression_2" + - nm_query_selector2.current.expressions[1].operator == "notIn" + - nm_query_selector2.current.expressions[1].value == "test" + - nm_query_selector2.current.expressions[2].key == "Custom:expression_3" + - nm_query_selector2.current.expressions[2].operator == "equals" + - nm_query_selector2.current.expressions[2].value == "test" + - nm_query_selector2.current.expressions[3].key == "Custom:expression_4" + - nm_query_selector2.current.expressions[3].operator == "notEquals" + - nm_query_selector2.current.expressions[3].value == "test" + - nm_query_selector2.current.expressions[4].key == "Custom:expression_5" + - nm_query_selector2.current.expressions[4].operator == "keyExist" + - nm_query_selector2.current.expressions[4].value == "" + - nm_query_selector2.current.expressions[5].key == "Custom:expression_6" + - nm_query_selector2.current.expressions[5].operator == "keyNotExist" + - nm_query_selector2.current.expressions[5].value == "" + +# - name: Remove selector 1 (check_mode) +# mso_schema_template_anp_epg_selector: +# <<: *mso_info +# schema: '{{ mso_schema | default("ansible_test") }}' +# template: Template 1 +# anp: ANP +# epg: ansible_test_1 +# selector: selector 1 +# state: absent +# check_mode: true +# register: cm_remove_selector_1 + +# - name: Verify cm_remove_selector_1 +# assert: +# that: +# - cm_remove_selector_1 is changed +# - cm_remove_selector_1.current == {} + +# - name: Remove selector 1 (normal_mode) +# mso_schema_template_anp_epg_selector: +# <<: *mso_info +# schema: '{{ mso_schema | default("ansible_test") }}' +# template: Template 1 +# anp: ANP +# epg: ansible_test_1 +# selector: selector 1 +# state: absent +# register: nm_remove_selector_1 + +# - name: Verify nm_remove_selector_1 +# assert: +# that: +# - nm_remove_selector_1 is changed +# - nm_remove_selector_1.current == {} + +- name: Remove selector 2 (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_2 + state: absent + register: nm_remove_selector_2 + +- name: Verify nm_remove_selector_2 + assert: + that: + - nm_remove_selector_2 is changed + - nm_remove_selector_2.current == {} + +# QUERY NON-EXISTING Selector to EPG +- name: Query non-existing selector (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: non_existing_selector + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_selector + +- name: Query non-existing selector (normal mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: non_existing_selector + state: query + ignore_errors: true + register: nm_query_non_selector + +- name: Verify cm_query_non_selector and nm_query_non_selector + assert: + that: + - cm_query_non_selector is not changed + - nm_query_non_selector is not changed + - cm_query_non_selector == nm_query_non_selector + - cm_query_non_selector.msg == "Selector 'non_existing_selector' not found" + - nm_query_non_selector.msg == "Selector 'non_existing_selector' not found" + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: non_existing_epg + selector: selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_epg + +- name: Query non-existing EPG (normal mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: non_existing_epg + selector: selector_1 + state: query + ignore_errors: true + register: nm_query_non_epg + +- name: Verify query_non_epg + assert: + that: + - cm_query_non_epg is not changed + - nm_query_non_epg is not changed + - cm_query_non_epg == nm_query_non_epg + - cm_query_non_epg.msg == nm_query_non_epg.msg == "Provided epg 'non_existing_epg' does not exist. Existing epgs{{':'}} ansible_test_1" + +# QUERY NON-EXISTING ANP +- name: Query non-existing ANP (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: non_existing_anp + epg: ansible_test_1 + selector: selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_anp + +- name: Query non-existing ANP (normal mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: non_existing_anp + epg: ansible_test_1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_query_non_anp + +- name: Verify query_non_anp + assert: + that: + - cm_query_non_anp is not changed + - nm_query_non_anp is not changed + - cm_query_non_anp == nm_query_non_anp + - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP" + +# USE A NON-EXISTING STATE +- name: Non-existing state (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema (check_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml new file mode 100644 index 000000000..a10ed8e14 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml @@ -0,0 +1,1907 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Set version vars + set_fact: + mso_l3mcast: false + when: version.current.version is version('2.2.4', '=') + +- name: Ensure site exist + mso_site: &site_present + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove DHCP policies + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy1' + - 'ansible_test_dhcp_policy2' + - 'ansible_test_dhcp_policy3' + when: + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Remove DHCP option policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy_option1' + - 'ansible_test_dhcp_policy_option2' + - 'ansible_test_dhcp_policy_option3' + when: + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template1 + state: present + +- name: Ensure schema 1 with Template2 exists + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template2 + +- name: Ensure schema 2 with Template3 exists + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template3 + +- name: Ensure schema 2 with Template5 exists + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template5 + +- name: Ensure VRF exist + mso_schema_template_vrf: &vrf_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF + layer3_multicast: true + state: present + +- name: Ensure VRF2 exist + mso_schema_template_vrf: + <<: *vrf_present + vrf: VRF2 + +- name: Ensure VRF3 exist + mso_schema_template_vrf: + <<: *vrf_present + template: Template2 + vrf: VRF3 + +- name: Ensure VRF4 exist + mso_schema_template_vrf: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + vrf: VRF4 + +- name: Ensure VRF5 exists + mso_schema_template_vrf: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + vrf: VRF5 + +- name: Ensure ansible_test_1 BD does not exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + vrf: + name: VRF + state: absent + +- name: Ensure ansible_test_2 BD does not exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + bd: ansible_test_2 + vrf: + name: VRF + state: absent + +- name: Ensure ansible_test_3 BD does not exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + bd: ansible_test_3 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: absent + +- name: Ensure ansible_test_4 BD does not exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_4 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: absent + +- name: Ensure multiple DHCP policies exist + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + description: "My Test DHCP Policies" + tenant: ansible_test + state: present + loop: + - 'ansible_test_dhcp_policy1' + - 'ansible_test_dhcp_policy2' + - 'ansible_test_dhcp_policy3' + when: + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Ensure multiple DHCP option policies exist + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + description: "My Test DHCP Policy Options" + tenant: ansible_test + state: present + loop: + - 'ansible_test_dhcp_policy_option1' + - 'ansible_test_dhcp_policy_option2' + - 'ansible_test_dhcp_policy_option3' + when: + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +# ADD BD +- name: Add bd (check_mode) + mso_schema_template_bd: &bd_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + vrf: + name: VRF + state: present + check_mode: true + register: cm_add_bd + +- name: Verify cm_add_bd + assert: + that: + - cm_add_bd is changed + - cm_add_bd.previous == {} + - cm_add_bd.current.name == "ansible_test_1" + - cm_add_bd.current.vrfRef.templateName == "Template1" + - cm_add_bd.current.vrfRef.vrfName == "VRF" + +- name: Add bd (normal mode) + mso_schema_template_bd: + <<: *bd_present + register: nm_add_bd + +- name: Verify nm_add_bd + assert: + that: + - nm_add_bd is changed + - nm_add_bd.previous == {} + - nm_add_bd.current.name == "ansible_test_1" + - nm_add_bd.current.vrfRef.templateName == "Template1" + - nm_add_bd.current.vrfRef.vrfName == "VRF" + - cm_add_bd.current.vrfRef.schemaId == nm_add_bd.current.vrfRef.schemaId + +- name: Add bd again (check_mode) + mso_schema_template_bd: + <<: *bd_present + check_mode: true + register: cm_add_bd_again + +- name: Verify cm_add_bd_again + assert: + that: + - cm_add_bd_again is not changed + - cm_add_bd_again.previous.name == "ansible_test_1" + - cm_add_bd_again.current.name == "ansible_test_1" + - cm_add_bd_again.previous.vrfRef.templateName == "Template1" + - cm_add_bd_again.current.vrfRef.templateName == "Template1" + - cm_add_bd_again.previous.vrfRef.vrfName == "VRF" + - cm_add_bd_again.current.vrfRef.vrfName == "VRF" + - cm_add_bd_again.previous.vrfRef.schemaId == cm_add_bd_again.current.vrfRef.schemaId + +- name: Add bd again (normal mode) + mso_schema_template_bd: + <<: *bd_present + register: nm_add_bd_again + +- name: Verify nm_add_bd_again + assert: + that: + - nm_add_bd_again is not changed + - nm_add_bd_again.previous.name == "ansible_test_1" + - nm_add_bd_again.current.name == "ansible_test_1" + - nm_add_bd_again.previous.vrfRef.templateName == "Template1" + - nm_add_bd_again.current.vrfRef.templateName == "Template1" + - nm_add_bd_again.previous.vrfRef.vrfName == "VRF" + - nm_add_bd_again.current.vrfRef.vrfName == "VRF" + - nm_add_bd_again.previous.vrfRef.schemaId == nm_add_bd_again.current.vrfRef.schemaId + +- name: Add bd 2 (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + bd: ansible_test_2 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: true + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + - subnet: 172.16.0.1/24 + description: "My description for a subnet" + scope: public + shared: true + no_default_gateway: false + querier: true + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + register: nm_add_bd_2 + +- name: Add bd 3 (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + bd: ansible_test_3 + vrf: + name: VRF4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + register: nm_add_bd_3 + +- name: Add bd 4 (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_4 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + register: nm_add_bd_4 + +- name: Verify nm_add_bd_2 and nm_add_bd_3 + assert: + that: + - nm_add_bd_2 is changed + - nm_add_bd_3 is changed + - nm_add_bd_2.current.name == "ansible_test_2" + - nm_add_bd_3.current.name == "ansible_test_3" + - nm_add_bd_2.current.vrfRef.templateName == "Template2" + - nm_add_bd_3.current.vrfRef.templateName == "Template3" + - nm_add_bd_2.current.vrfRef.vrfName == "VRF3" + - nm_add_bd_3.current.vrfRef.vrfName == "VRF4" + - nm_add_bd_2.current.vrfRef.schemaId == nm_add_bd.current.vrfRef.schemaId + - nm_add_bd_2.current.intersiteBumTrafficAllow == true + - nm_add_bd_2.current.optimizeWanBandwidth == true + - nm_add_bd_2.current.l2Stretch == true + - nm_add_bd_2.current.l2UnknownUnicast == "flood" + - nm_add_bd_2.current.l3MCast == true + - nm_add_bd_2.current.subnets[0].description == "10.0.0.128/24" + - nm_add_bd_2.current.subnets[0].ip == "10.0.0.128/24" + - nm_add_bd_2.current.subnets[0].noDefaultGateway == false + - nm_add_bd_2.current.subnets[0].scope == "private" + - nm_add_bd_2.current.subnets[0].shared == false + - nm_add_bd_2.current.subnets[0].querier == false + - nm_add_bd_2.current.subnets[1].description == "1234567890" + - nm_add_bd_2.current.subnets[1].ip == "10.0.1.254/24" + - nm_add_bd_2.current.subnets[1].noDefaultGateway == false + - nm_add_bd_2.current.subnets[1].scope == "private" + - nm_add_bd_2.current.subnets[1].shared == false + - nm_add_bd_2.current.subnets[1].querier == false + - nm_add_bd_2.current.subnets[2].description == "My description for a subnet" + - nm_add_bd_2.current.subnets[2].ip == "172.16.0.1/24" + - nm_add_bd_2.current.subnets[2].noDefaultGateway == false + - nm_add_bd_2.current.subnets[2].scope == "public" + - nm_add_bd_2.current.subnets[2].shared == true + - nm_add_bd_2.current.subnets[2].querier == true + - nm_add_bd_2.current.subnets[3].description == "My description for a subnet" + - nm_add_bd_2.current.subnets[3].ip == "192.168.0.254/24" + - nm_add_bd_2.current.subnets[3].noDefaultGateway == true + - nm_add_bd_2.current.subnets[3].scope == "private" + - nm_add_bd_2.current.subnets[3].shared == false + - nm_add_bd_2.current.subnets[3].querier == false + +- name: Add bd 5 (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: true + virtual_mac_address: 00:00:5E:00:01:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + register: nm_add_bd_5 + +- name: Verify nm_add_bd_5 for a version that's before 3.1 + assert: + that: + - nm_add_bd_5 is changed + - nm_add_bd_5.current.name == "ansible_test_5" + - nm_add_bd_5.current.vrfRef.templateName == "Template5" + - nm_add_bd_5.current.vrfRef.vrfName == "VRF5" + - nm_add_bd_5.current.intersiteBumTrafficAllow == true + - nm_add_bd_5.current.optimizeWanBandwidth == true + - nm_add_bd_5.current.l2Stretch == true + - nm_add_bd_5.current.l2UnknownUnicast == "flood" + - nm_add_bd_5.current.l3MCast == false + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_add_bd_5 for a version that's 3.1 + assert: + that: + - nm_add_bd_5 is changed + - nm_add_bd_5.current.name == "ansible_test_5" + - nm_add_bd_5.current.vrfRef.templateName == "Template5" + - nm_add_bd_5.current.vrfRef.vrfName == "VRF5" + - nm_add_bd_5.current.intersiteBumTrafficAllow == true + - nm_add_bd_5.current.optimizeWanBandwidth == true + - nm_add_bd_5.current.l2Stretch == true + - nm_add_bd_5.current.l2UnknownUnicast == "flood" + - nm_add_bd_5.current.l3MCast == false + - nm_add_bd_5.current.unkMcastAct == "flood" + - nm_add_bd_5.current.v6unkMcastAct == "flood" + - nm_add_bd_5.current.vmac == "00:00:5E:00:01:3C" + - nm_add_bd_5.current.multiDstPktAct == "drop" + - nm_add_bd_5.current.arpFlood == true + when: version.current.version is version('3.1.1g', '>=') + +- name: Add bd 5 again (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: true + virtual_mac_address: 00:00:5E:00:01:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + register: nm_add_again_bd_5 + +- name: Verify nm_add_again_bd_5 for a version that's before 3.1 + assert: + that: + - nm_add_again_bd_5 is not changed + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_add_again_bd_5 for a version that's between 3.1 and 4.0 + assert: + that: + - nm_add_again_bd_5 is not changed + when: + - version.current.version is version('3.1.1g', '>=') + +- name: Add bd 5 with different values for new options (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: optimized_flooding + multi_destination_flooding: flood_in_bd + ipv6_unknown_multicast_flooding: optimized_flooding + arp_flooding: true + virtual_mac_address: 00:00:5E:00:02:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + register: nm_bd_5_options + +- name: Verify nm_bd_5_options for a version that's before 3.1 + assert: + that: + - nm_bd_5_options is not changed + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_bd_5_options for a version that's 3.1 + assert: + that: + - nm_bd_5_options is changed + - nm_bd_5_options.current.unkMcastAct == "opt-flood" + - nm_bd_5_options.current.v6unkMcastAct == "opt-flood" + - nm_bd_5_options.current.multiDstPktAct == "bd-flood" + - nm_bd_5_options.current.vmac == "00:00:5E:00:02:3C" + when: version.current.version is version('3.1.1g', '>=') + +- name: Change bd 5_1 (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: proxy + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: true + virtual_mac_address: 00:00:5E:00:01:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + register: nm_change_bd_5_1 + +- name: Verify nm_change_bd_5_1 for a version that's before 3.1 + assert: + that: + - nm_change_bd_5_1 is changed + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_change_bd_5_1 for a version that's 3.1 + assert: + that: + - nm_change_bd_5_1 is changed + - nm_change_bd_5_1.current.arpFlood == true + when: version.current.version is version('3.1.1g', '>=') + +- name: Change bd 5_2 (normal mode) + mso_schema_template_bd: &change_bd5_2 + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + ignore_errors: true + register: nm_change_bd_5_2 + +- name: Verify nm_change_bd_5_2 for a version that's before 3.1 + assert: + that: + - nm_change_bd_5_2 is changed + - nm_change_bd_5_2.current.l2UnknownUnicast == "flood" + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_change_bd_5_2 for a version that's after 3.1 + assert: + that: + - nm_change_bd_5_2 is changed + - nm_change_bd_5_2.current.arpFlood == true + when: version.current.version is version('3.1.1g', '>=') + +- name: Change bd 5_3 (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: false + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + ignore_errors: true + register: nm_change_bd_5_3 + +- name: Verify nm_change_bd_5_3 for a version that's before 3.1 + assert: + that: + - nm_change_bd_5_3 is changed + - nm_change_bd_5_3.msg is match ("MSO Error 143{{':'}} Invalid Field{{':'}} BD 'ansible_test_5' l2UnknownUnicast cannot be flood when intersiteBumTrafficAllow is off") + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_change_bd_5_3 for a version that's after 3.1 and before 3.3 + assert: + that: + - nm_change_bd_5_3 is changed + # Inconsistency shown in returned error messages for v3.1.1 + # Below variants of the message output have occurred randomly, thus commenting out the specific error messages below. + # Would need further investigation in to the cause, but due to old version decided to skip this investigation and do skip the message skip. + # - "'l2UnknownUnicast cannot be flood when intersiteBumTrafficAllow is off exception while trying to update schema' in nm_change_bd_5_3.msg" + # - nm_change_bd_5_3.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} BD 'ansible_test_5' {{':'}} ARP Flooding has to be disabled if L2 Stretch enabled and BUM traffic disabled exception while trying to update schema") + # - nm_change_bd_5_3.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Template 'Template5', BD 'ansible_test_5' {{':'}} ARP Flooding has to be disabled if L2 Stretch enabled and BUM traffic disabled exception while trying to update schema") + when: + - version.current.version is version('3.3', '<') + - version.current.version is version('3.1.1g', '>=') + +- name: Verify nm_change_bd_5_3 for a version that's after 4.0 + assert: + that: + - nm_change_bd_5_3 is changed + - nm_change_bd_5_3.msg is match ("MSO Error 400{{':'}} BD{{':'}} ansible_test_5 in Schema{{':'}} ansible_test_2 , Template{{':'}} Template5 BD ansible_test_5 l2UnknownUnicast cannot be flood when intersiteBumTrafficAllow is off") + when: + - version.current.version is version('4.0', '>=') + +- name: Get Validation status + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + state: query + ignore_errors: true + register: query_validate + when: + - version.current.version is version('3.3', '>=') + - version.current.version is version('4.0', '<') # mso_schema_validate not needed after 4.0 because validation is done upon request + +- name: Verify query_validate for a version that's after 3.3 + assert: + that: + - query_validate is not changed + - query_validate.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Template 'Template5', BD 'ansible_test_5' {{':'}} ARP Flooding has to be disabled if L2 Stretch enabled and BUM traffic disabled exception while trying to update schema") + when: + - version.current.version is version('3.3', '>=') + - version.current.version is version('3.7', '<') + +# Reverting back to bd 5_2 +- name: Change bd 5_3 (normal mode) + mso_schema_template_bd: + <<: *change_bd5_2 + register: nm_change_bd_5_3_again + when: query_validate is failed + +- name: Get Validation status + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + state: query + register: query_validate + when: + - version.current.version is version('3.3', '>=') + - version.current.version is version('4.0', '<') # mso_schema_validate not needed after 4.0 because validation is done upon request + +- name: Change bd 5 for query (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: true + virtual_mac_address: 00:00:5E:00:01:3C + vrf: + name: VRF5 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + +# CHANGE BD +- name: Change bd (check_mode) + mso_schema_template_bd: + <<: *bd_present + vrf: + name: VRF2 + check_mode: true + register: cm_change_bd + +- name: Verify cm_change_bd + assert: + that: + - cm_change_bd is changed + - cm_change_bd.current.name == 'ansible_test_1' + - cm_change_bd.current.vrfRef.vrfName == 'VRF2' + - cm_change_bd.current.vrfRef.templateName == "Template1" + - cm_change_bd.current.vrfRef.schemaId == cm_change_bd.previous.vrfRef.schemaId + +- name: Change bd (normal mode) + mso_schema_template_bd: + <<: *bd_present + vrf: + name: VRF2 + output_level: debug + register: nm_change_bd + +- name: Verify nm_change_bd + assert: + that: + - nm_change_bd is changed + - nm_change_bd.current.name == 'ansible_test_1' + - nm_change_bd.current.vrfRef.vrfName == 'VRF2' + - nm_change_bd.current.vrfRef.templateName == "Template1" + - nm_change_bd.current.vrfRef.schemaId == nm_change_bd.previous.vrfRef.schemaId + +- name: Change bd again (check_mode) + mso_schema_template_bd: + <<: *bd_present + vrf: + name: VRF2 + check_mode: true + register: cm_change_bd_again + +- name: Verify cm_change_bd_again + assert: + that: + - cm_change_bd_again is not changed + - cm_change_bd_again.current.name == 'ansible_test_1' + - cm_change_bd_again.current.vrfRef.vrfName == 'VRF2' + - cm_change_bd_again.current.vrfRef.templateName == "Template1" + - cm_change_bd_again.current.vrfRef.schemaId == cm_change_bd_again.previous.vrfRef.schemaId + +- name: Change bd again (normal mode) + mso_schema_template_bd: + <<: *bd_present + vrf: + name: VRF2 + register: nm_change_bd_again + +- name: Verify nm_change_bd_again + assert: + that: + - nm_change_bd_again is not changed + - nm_change_bd_again.current.name == 'ansible_test_1' + - nm_change_bd_again.current.vrfRef.vrfName == 'VRF2' + - nm_change_bd_again.current.vrfRef.templateName == "Template1" + - nm_change_bd_again.current.vrfRef.schemaId == nm_change_bd_again.previous.vrfRef.schemaId + +- name: Change bd to VRF3 in other template (normal mode) + mso_schema_template_bd: + <<: *bd_present + vrf: + name: VRF3 + template: Template2 + register: nm_change_bd_vrf3 + +- name: Change bd to VRF4 in other schema (normal mode) + mso_schema_template_bd: + <<: *bd_present + vrf: + name: VRF4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + register: nm_change_bd_vrf4 + +- name: Verify nm_change_bd_vrf3 and nm_change_bd_vrf4 + assert: + that: + - nm_change_bd_vrf3 is changed + - nm_change_bd_vrf3.current.name == nm_change_bd_vrf4.current.name == 'ansible_test_1' + - nm_change_bd_vrf3.current.vrfRef.vrfName == 'VRF3' + - nm_change_bd_vrf3.current.vrfRef.templateName == "Template2" + - nm_change_bd_vrf4.current.vrfRef.vrfName == 'VRF4' + - nm_change_bd_vrf4.current.vrfRef.templateName == "Template3" + +- name: Change bd 1 settings(normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: '{{ mso_l3mcast | default(true) }}' + subnets: + - subnet: 10.1.0.128/24 + - subnet: 10.1.1.254/24 + description: 1234567890 + - subnet: 172.17.0.1/24 + description: "My description for a subnet" + scope: public + shared: true + no_default_gateway: false + querier: true + - ip: 192.168.1.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + register: nm_change_bd_1_settings + +- name: Change bd 1 subnets (normal mode) + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: '{{ mso_l3mcast | default(true) }}' + subnets: + - subnet: 10.1.0.127/24 + - subnet: 172.17.0.1/24 + description: "New description for a subnet" + scope: private + shared: false + no_default_gateway: false + querier: false + - ip: 192.168.1.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: false + querier: true + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + register: nm_change_bd_1_subnets + +- name: Verify nm_change_bd_1_subnets + assert: + that: + - nm_change_bd_1_settings is changed + - nm_change_bd_1_settings.current.name == "ansible_test_1" + - nm_change_bd_1_settings.current.vrfRef.templateName == "Template2" + - nm_change_bd_1_settings.current.vrfRef.vrfName == "VRF3" + - nm_change_bd_1_settings.current.intersiteBumTrafficAllow == true + - nm_change_bd_1_settings.current.optimizeWanBandwidth == true + - nm_change_bd_1_settings.current.l2Stretch == true + - nm_change_bd_1_settings.current.l2UnknownUnicast == "flood" + - nm_change_bd_1_settings.current.subnets[0].description == "10.1.0.128/24" + - nm_change_bd_1_settings.current.subnets[0].ip == "10.1.0.128/24" + - nm_change_bd_1_settings.current.subnets[0].noDefaultGateway == false + - nm_change_bd_1_settings.current.subnets[0].scope == "private" + - nm_change_bd_1_settings.current.subnets[0].shared == false + - nm_change_bd_1_settings.current.subnets[0].querier == false + - nm_change_bd_1_settings.current.subnets[1].description == "1234567890" + - nm_change_bd_1_settings.current.subnets[1].ip == "10.1.1.254/24" + - nm_change_bd_1_settings.current.subnets[1].noDefaultGateway == false + - nm_change_bd_1_settings.current.subnets[1].scope == "private" + - nm_change_bd_1_settings.current.subnets[1].shared == false + - nm_change_bd_1_settings.current.subnets[1].querier == false + - nm_change_bd_1_settings.current.subnets[2].description == "My description for a subnet" + - nm_change_bd_1_settings.current.subnets[2].ip == "172.17.0.1/24" + - nm_change_bd_1_settings.current.subnets[2].noDefaultGateway == false + - nm_change_bd_1_settings.current.subnets[2].scope == "public" + - nm_change_bd_1_settings.current.subnets[2].shared == true + - nm_change_bd_1_settings.current.subnets[2].querier == true + - nm_change_bd_1_settings.current.subnets[3].description == "My description for a subnet" + - nm_change_bd_1_settings.current.subnets[3].ip == "192.168.1.254/24" + - nm_change_bd_1_settings.current.subnets[3].noDefaultGateway == true + - nm_change_bd_1_settings.current.subnets[3].scope == "private" + - nm_change_bd_1_settings.current.subnets[3].shared == false + - nm_change_bd_1_settings.current.subnets[3].querier == false + - nm_change_bd_1_settings is changed + - nm_change_bd_1_subnets.current.subnets | length == 3 + - nm_change_bd_1_subnets.current.name == "ansible_test_1" + - nm_change_bd_1_subnets.current.vrfRef.templateName == "Template2" + - nm_change_bd_1_subnets.current.vrfRef.vrfName == "VRF3" + - nm_change_bd_1_subnets.current.intersiteBumTrafficAllow == true + - nm_change_bd_1_subnets.current.optimizeWanBandwidth == true + - nm_change_bd_1_subnets.current.l2Stretch == true + - nm_change_bd_1_subnets.current.l2UnknownUnicast == "flood" + - nm_change_bd_1_subnets.current.subnets[0].description == "10.1.0.127/24" + - nm_change_bd_1_subnets.current.subnets[0].ip == "10.1.0.127/24" + - nm_change_bd_1_subnets.current.subnets[0].noDefaultGateway == false + - nm_change_bd_1_subnets.current.subnets[0].scope == "private" + - nm_change_bd_1_subnets.current.subnets[0].shared == false + - nm_change_bd_1_subnets.current.subnets[0].querier == false + - nm_change_bd_1_subnets.current.subnets[1].description == "New description for a subnet" + - nm_change_bd_1_subnets.current.subnets[1].ip == "172.17.0.1/24" + - nm_change_bd_1_subnets.current.subnets[1].noDefaultGateway == false + - nm_change_bd_1_subnets.current.subnets[1].scope == "private" + - nm_change_bd_1_subnets.current.subnets[1].shared == false + - nm_change_bd_1_subnets.current.subnets[1].querier == false + - nm_change_bd_1_subnets.current.subnets[2].description == "My description for a subnet" + - nm_change_bd_1_subnets.current.subnets[2].ip == "192.168.1.254/24" + - nm_change_bd_1_subnets.current.subnets[2].noDefaultGateway == false + - nm_change_bd_1_subnets.current.subnets[2].scope == "private" + - nm_change_bd_1_subnets.current.subnets[2].shared == false + - nm_change_bd_1_subnets.current.subnets[2].querier == true + +- name: Verify l3MCast nm_change_bd_1_subnets (version == 2.2.4) + assert: + that: + - nm_change_bd_1_settings.current.l3MCast == false + - nm_change_bd_1_subnets.current.l3MCast == false + when: version.current.version is version('2.2.4', '=') + +- name: Verify l3MCast nm_change_bd_1_subnets (version != 2.2.4) + assert: + that: + - nm_change_bd_1_settings.current.l3MCast == true + - nm_change_bd_1_subnets.current.l3MCast == true + when: version.current.version is version('2.2.4', '!=') + +- name: Add bd with multiple dhcp policies for mso version > 3.1.1g + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_multiple_dhcp + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + dhcp_policies: + - name: ansible_test_dhcp_policy1 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + - name: ansible_test_dhcp_policy2 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + - name: ansible_test_dhcp_policy3 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option2 + version: 1 + state: present + register: nm_bd_dhcp_policies + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Verify addition of DHCP policies for mso version > 3.1.1g + assert: + that: + - nm_bd_dhcp_policies is changed + - nm_bd_dhcp_policies.current.dhcpLabels | length == 3 + - nm_bd_dhcp_policies.current.dhcpLabels[0].name == 'ansible_test_dhcp_policy1' + - nm_bd_dhcp_policies.current.dhcpLabels[1].name == 'ansible_test_dhcp_policy2' + - nm_bd_dhcp_policies.current.dhcpLabels[2].name == 'ansible_test_dhcp_policy3' + - nm_bd_dhcp_policies.current.dhcpLabels[0].version == 1 + - nm_bd_dhcp_policies.current.dhcpLabels[1].version == 1 + - nm_bd_dhcp_policies.current.dhcpLabels[2].version == 1 + - nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - nm_bd_dhcp_policies.current.dhcpLabels[2].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option2' + - nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.version == 1 + - nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.version == 1 + - nm_bd_dhcp_policies.current.dhcpLabels[2].dhcpOptionLabel.version == 1 + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Change bd with multiple dhcp policies for mso version > 3.1.1g + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_multiple_dhcp + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + dhcp_policies: + - name: ansible_test_dhcp_policy1 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + - name: ansible_test_dhcp_policy2 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + state: present + register: change_nm_bd_dhcp_policies + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Verify change in DHCP policies for mso version > 3.1.1g + assert: + that: + - change_nm_bd_dhcp_policies is changed + - change_nm_bd_dhcp_policies.current.dhcpLabels | length == 2 + - change_nm_bd_dhcp_policies.current.dhcpLabels[0].name == 'ansible_test_dhcp_policy1' + - change_nm_bd_dhcp_policies.current.dhcpLabels[1].name == 'ansible_test_dhcp_policy2' + - change_nm_bd_dhcp_policies.current.dhcpLabels[0].version == 1 + - change_nm_bd_dhcp_policies.current.dhcpLabels[1].version == 1 + - change_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - change_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - change_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.version == 1 + - change_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.version == 1 + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +# Add BD with new options for mso version > 3.1.1g +- name: Add bd with new options available in mso versions > 3.1.1g + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_new_options + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + unicast_routing: true + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + virtual: true + primary: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_bd_new_subnet_options + when: version.current.version is version('3.1.1g', '>') + +- name: Verify subnets in nm_bd_new_subnet_options + assert: + that: + - nm_bd_new_subnet_options is changed + - nm_bd_new_subnet_options.current.name == 'ansible_test_new_options' + - nm_bd_new_subnet_options.current.vrfRef.templateName == "Template1" + - nm_bd_new_subnet_options.current.vrfRef.vrfName == "VRF" + - nm_bd_new_subnet_options.current.intersiteBumTrafficAllow == true + - nm_bd_new_subnet_options.current.optimizeWanBandwidth == false + - nm_bd_new_subnet_options.current.l2Stretch == true + - nm_bd_new_subnet_options.current.l2UnknownUnicast == "flood" + - nm_bd_new_subnet_options.current.unkMcastAct == "flood" + - nm_bd_new_subnet_options.current.multiDstPktAct == "drop" + - nm_bd_new_subnet_options.current.l3MCast == false + - nm_bd_new_subnet_options.current.arpFlood == true + - nm_bd_new_subnet_options.current.v6unkMcastAct == "flood" + - nm_bd_new_subnet_options.current.vmac == "00:00:5E:00:01:3C" + - nm_bd_new_subnet_options.current.unicastRouting == true + - nm_bd_new_subnet_options.current.subnets[1].primary == false + - nm_bd_new_subnet_options.current.subnets[0].virtual == false + - nm_bd_new_subnet_options.current.subnets[2].primary == true + - nm_bd_new_subnet_options.current.subnets[2].virtual == true + when: version.current.version is version('3.1.1g', '>') + +# Change BD with new options for mso version > 3.1.1g +- name: Try Changing bd with another subnet to primary IP + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_new_options + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: drop + ipv6_unknown_multicast_flooding: flood + arp_flooding: false + virtual_mac_address: 00:00:5E:00:01:3C + unicast_routing: true + subnets: + - subnet: 10.0.0.128/24 + - subnet: 10.0.1.254/24 + description: 1234567890 + primary: true + - ip: 192.168.0.254/24 + description: "My description for a subnet" + scope: private + shared: false + no_default_gateway: true + virtual: true + primary: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_bd_subnet_second_primary + ignore_errors: true + when: version.current.version is version('3.1.1g', '>') + +- name: Verify subnets in nm_bd_subnet_second_primary + assert: + that: + - nm_bd_subnet_second_primary.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Only one preferred subnet per address family is allowed under BD ansible_test_new_options of Template Template1 exception while trying to update schema") + when: + - version.current.version is version('3.1.1g', '>') + - version.current.version is version('3.3', '<') + +- name: Add bd with new option flood in encap available in mso versions > 3.1.1g with l2 set to true + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_flood_encap + intersite_bum_traffic: true + optimize_wan_bandwidth: false + layer2_stretch: true + layer2_unknown_unicast: flood + layer3_multicast: false + unknown_multicast_flooding: flood + multi_destination_flooding: encap-flood + ipv6_unknown_multicast_flooding: flood + unicast_routing: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + ignore_errors: true + register: nm_bd_new_encap_flood_non + when: version.current.version is version('3.1.1g', '>') + +- name: Verify nm_bd_new_encap_flood_non + assert: + that: + - nm_bd_new_encap_flood_non.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Template 'Template1', BD 'ansible_test_flood_encap' {{':'}} Multi destination flood in encapsulation is only supported when l2Stretch is disabled exception while trying to update schema") + when: + - version.current.version is version('3.1.1g', '>') + - version.current.version is version('3.3', '<') + +- name: Add bd with new option flood in encap available in mso versions > 3.1.1g l2 set to false + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_flood_encap + layer2_stretch: false + layer2_unknown_unicast: flood + unknown_multicast_flooding: flood + multi_destination_flooding: encap-flood + ipv6_unknown_multicast_flooding: flood + unicast_routing: true + intersite_bum_traffic: false + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_bd_new_encap_flood + when: version.current.version is version('3.1.1g', '>') + +- name: Verify nm_bd_new_encap_flood + assert: + that: + - nm_bd_new_encap_flood.current.multiDstPktAct == "encap-flood" + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd with new option description in mso versions >= 3.3 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_description + description: ansible_test_bd + layer2_stretch: true + layer2_unknown_unicast: proxy + unknown_multicast_flooding: flood + ipv6_unknown_multicast_flooding: flood + unicast_routing: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_bd_desc + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_bd_desc + assert: + that: + - nm_bd_desc.current.description == "ansible_test_bd" + when: version.current.version is version('3.3', '>=') + +- name: Add bd with change in description in mso versions >= 3.3 + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_description + description: ansible_test_bd_again + layer2_stretch: true + layer2_unknown_unicast: proxy + unknown_multicast_flooding: flood + ipv6_unknown_multicast_flooding: flood + unicast_routing: true + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + register: nm_bd_desc_2 + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_bd_desc_2 + assert: + that: + - nm_bd_desc_2.current.description == "ansible_test_bd_again" + when: version.current.version is version('3.3', '>=') + +- name: Ensure bd ansible_test_unicast_false is removed >= 3.1.1g + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_unicast_false + state: absent + when: version.current.version is version('3.1.1g', '>') + +- name: Ensure bd ansible_test_unicast_true is removed >= 3.1.1g + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_unicast_true + state: absent + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd with change in unicast routing false in mso versions >= 3.1.1g (check mode) + mso_schema_template_bd: &unicast_routing_false_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_unicast_false + unicast_routing: false + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + check_mode: true + register: cm_ansible_test_unicast_false + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd with change in unicast routing false in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_false_present + register: nm_ansible_test_unicast_false + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd again with unicast routing false in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_false_present + register: nm_ansible_test_unicast_false_again + when: version.current.version is version('3.1.1g', '>') + +- name: Change bd with unicast routing to true in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_false_present + unicast_routing: true + register: nm_ansible_test_unicast_false_to_true + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd with change in unicast routing true in mso versions >= 3.1.1g (check mode) + mso_schema_template_bd: &unicast_routing_true_present + <<: *unicast_routing_false_present + bd: ansible_test_unicast_true + unicast_routing: true + check_mode: true + register: cm_ansible_test_unicast_true + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd with change in unicast routing true in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_true_present + register: nm_ansible_test_unicast_true + when: version.current.version is version('3.1.1g', '>') + +- name: Add bd again with unicast routing true in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_true_present + register: nm_ansible_test_unicast_true_again + when: version.current.version is version('3.1.1g', '>') + +- name: Change bd with unicast routing to false in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_true_present + unicast_routing: false + register: nm_ansible_test_unicast_true_to_false + when: version.current.version is version('3.1.1g', '>') + +- name: Verify unicast routing + assert: + that: + - cm_ansible_test_unicast_false is changed + - cm_ansible_test_unicast_false.current.unicastRouting == false + - nm_ansible_test_unicast_false is changed + - nm_ansible_test_unicast_false.current.unicastRouting == false + - nm_ansible_test_unicast_false_again is not changed + - nm_ansible_test_unicast_false_again.previous.unicastRouting == false + - nm_ansible_test_unicast_false_again.current.unicastRouting == false + - nm_ansible_test_unicast_false_to_true is changed + - nm_ansible_test_unicast_false_to_true.current.unicastRouting == true + - cm_ansible_test_unicast_true is changed + - cm_ansible_test_unicast_true.current.unicastRouting == true + - nm_ansible_test_unicast_true is changed + - nm_ansible_test_unicast_true.current.unicastRouting == true + - nm_ansible_test_unicast_true_again is not changed + - nm_ansible_test_unicast_true_again.previous.unicastRouting == true + - nm_ansible_test_unicast_true_again.current.unicastRouting == true + - nm_ansible_test_unicast_true_to_false is changed + - nm_ansible_test_unicast_true_to_false.current.unicastRouting == false + when: version.current.version is version('3.1.1g', '>') + +- name: Remove bd with change in unicast routing false in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_false_present + state: absent + register: ansible_test_unicast_false + when: version.current.version is version('3.1.1g', '>') + +- name: Remove bd with change in unicast routing true in mso versions >= 3.1.1g + mso_schema_template_bd: + <<: *unicast_routing_true_present + state: absent + register: ansible_test_unicast_true + when: version.current.version is version('3.1.1g', '>') + +# FIXME: Add missing DHCP Policy changes and checks (missing DHCP Policy module to make sure it is there.) + +# QUERY ALL BD +- name: Query all bd (check_mode) + mso_schema_template_bd: &bd_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: query + check_mode: true + register: cm_query_all_bds + +- name: Query all bd (normal mode) + mso_schema_template_bd: + <<: *bd_query + register: nm_query_all_bds + +- name: Verify query_all_bds for version < 3.1.1g + assert: + that: + - cm_query_all_bds is not changed + - nm_query_all_bds is not changed + - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 2 + when: version.current.version is version('3.1.1g', '<') + +- name: Verify query_all_bds for version > 3.1.1g and version < 3.3 + assert: + that: + - cm_query_all_bds is not changed + - nm_query_all_bds is not changed + - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 5 + when: + - version.current.version is version('3.1.1g', '>') + - version.current.version is version('3.3', '<') + +- name: Verify query_all_bds for version >= 3.3 and < 4.0 + assert: + that: + - cm_query_all_bds is not changed + - nm_query_all_bds is not changed + - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 6 + when: + - version.current.version is version('3.3', '>=') + - version.current.version is version('3.7', '<') + +- name: Verify query_all_bds for version >= 4.0 + assert: + that: + - cm_query_all_bds is not changed + - nm_query_all_bds is not changed + - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 5 + when: version.current.version is version('4.0', '>=') + +# QUERY A BD +- name: Query bd 1 + mso_schema_template_bd: + <<: *bd_query + bd: ansible_test_1 + check_mode: true + register: cm_query_bd + +- name: Query bd 1 + mso_schema_template_bd: + <<: *bd_query + bd: ansible_test_1 + register: nm_query_bd + +- name: Query bd 2 + mso_schema_template_bd: + <<: *bd_query + template: Template2 + bd: ansible_test_2 + register: nm_query_bd_2 + +- name: Query bd 3 + mso_schema_template_bd: + <<: *bd_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template3 + bd: ansible_test_3 + register: nm_query_bd_3 + +- name: Query bd 5 + mso_schema_template_bd: + <<: *bd_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + register: nm_query_bd_5 + +- name: Verify query_bd + assert: + that: + - cm_query_bd is not changed + - nm_query_bd is not changed + - cm_query_bd.current.name == nm_query_bd.current.name == "ansible_test_1" + - cm_query_bd == nm_query_bd + - nm_query_bd_2.current.name == "ansible_test_2" + - nm_query_bd_3.current.name == "ansible_test_3" + - nm_query_bd_2.current.intersiteBumTrafficAllow == true + - nm_query_bd_2.current.optimizeWanBandwidth == true + - nm_query_bd_2.current.l2Stretch == true + - nm_query_bd_2.current.l2UnknownUnicast == "flood" + - nm_query_bd_2.current.l3MCast == true + - nm_query_bd_2.current.subnets[0].description == "10.0.0.128/24" + - nm_query_bd_2.current.subnets[0].ip == "10.0.0.128/24" + - nm_query_bd_2.current.subnets[0].noDefaultGateway == false + - nm_query_bd_2.current.subnets[0].scope == "private" + - nm_query_bd_2.current.subnets[0].shared == false + - nm_query_bd_2.current.subnets[1].description == "1234567890" + - nm_query_bd_2.current.subnets[1].ip == "10.0.1.254/24" + - nm_query_bd_2.current.subnets[1].noDefaultGateway == false + - nm_query_bd_2.current.subnets[1].scope == "private" + - nm_query_bd_2.current.subnets[1].shared == false + - nm_query_bd_2.current.subnets[2].description == "My description for a subnet" + - nm_query_bd_2.current.subnets[2].ip == "172.16.0.1/24" + - nm_query_bd_2.current.subnets[2].noDefaultGateway == false + - nm_query_bd_2.current.subnets[2].scope == "public" + - nm_query_bd_2.current.subnets[2].shared == true + - nm_query_bd_2.current.subnets[3].description == "My description for a subnet" + - nm_query_bd_2.current.subnets[3].ip == "192.168.0.254/24" + - nm_query_bd_2.current.subnets[3].noDefaultGateway == true + - nm_query_bd_2.current.subnets[3].scope == "private" + - nm_query_bd_2.current.subnets[3].shared == false + +- name: Verify nm_query_bd_5 for a version that's before 3.1 + assert: + that: + - nm_query_bd_5 is not changed + - nm_query_bd_5.current.name == "ansible_test_5" + - nm_query_bd_5.current.intersiteBumTrafficAllow == true + - nm_query_bd_5.current.optimizeWanBandwidth == true + - nm_query_bd_5.current.l2Stretch == true + - nm_query_bd_5.current.l2UnknownUnicast == "flood" + - nm_query_bd_5.current.l3MCast == false + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_query_bd_5 for a version that's 3.1 + assert: + that: + - nm_query_bd_5 is not changed + - nm_query_bd_5.current.name == "ansible_test_5" + - nm_query_bd_5.current.intersiteBumTrafficAllow == true + - nm_query_bd_5.current.optimizeWanBandwidth == true + - nm_query_bd_5.current.l2Stretch == true + - nm_query_bd_5.current.l2UnknownUnicast == "flood" + - nm_query_bd_5.current.l3MCast == false + - nm_query_bd_5.current.unkMcastAct == "flood" + - nm_query_bd_5.current.v6unkMcastAct == "flood" + - nm_query_bd_5.current.vmac == "00:00:5E:00:01:3C" + - nm_query_bd_5.current.multiDstPktAct == "drop" + - nm_query_bd_5.current.arpFlood == true + when: version.current.version is version('3.1.1g', '>=') + +- name: Query bd with multiple dhcp policies for mso version > 3.1.1g + mso_schema_template_bd: + <<: *bd_query + bd: ansible_test_multiple_dhcp + state: query + register: query_nm_bd_dhcp_policies + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Verify query of DHCP policies for mso version > 3.1.1g + assert: + that: + - query_nm_bd_dhcp_policies is not changed + - query_nm_bd_dhcp_policies.current.name == 'ansible_test_multiple_dhcp' + - query_nm_bd_dhcp_policies.current.dhcpLabels | length == 2 + - query_nm_bd_dhcp_policies.current.dhcpLabels[0].name == 'ansible_test_dhcp_policy1' + - query_nm_bd_dhcp_policies.current.dhcpLabels[1].name == 'ansible_test_dhcp_policy2' + - query_nm_bd_dhcp_policies.current.dhcpLabels[0].version == 1 + - query_nm_bd_dhcp_policies.current.dhcpLabels[1].version == 1 + - query_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - query_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - query_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.version == 1 + - query_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.version == 1 + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +# Query BD with new options for mso version > 3.1.1g +- name: Query bd with new option flood in encap available in mso versions > 3.1.1g with l2 set to false + mso_schema_template_bd: + <<: *bd_query + bd: ansible_test_flood_encap + state: query + register: query_bd_new_encap_flood + when: version.current.version is version('3.1.1g', '>') + +- name: Verify query_bd_new_encap_flood + assert: + that: + - query_bd_new_encap_flood is not changed + - query_bd_new_encap_flood.current.name == 'ansible_test_flood_encap' + - query_bd_new_encap_flood.current.intersiteBumTrafficAllow == false + - query_bd_new_encap_flood.current.optimizeWanBandwidth == false + - query_bd_new_encap_flood.current.l2Stretch == false + - query_bd_new_encap_flood.current.l2UnknownUnicast == "flood" + - query_bd_new_encap_flood.current.unkMcastAct == "flood" + - query_bd_new_encap_flood.current.multiDstPktAct == "encap-flood" + - query_bd_new_encap_flood.current.arpFlood == true + - query_bd_new_encap_flood.current.v6unkMcastAct == "flood" + - query_bd_new_encap_flood.current.unicastRouting == true + when: version.current.version is version('3.1.1g', '>') + +- name: Verify query_bd_new_encap_flood + assert: + that: + - query_bd_new_encap_flood.current.l3MCast == false + when: version.current.version is version('3.2', '>=') + +# REMOVE BD +- name: Remove bd (check_mode) + mso_schema_template_bd: &bd_absent + <<: *bd_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: absent + check_mode: true + register: cm_remove_bd + +- name: Verify cm_remove_bd + assert: + that: + - cm_remove_bd is changed + - cm_remove_bd.current == {} + +- name: Remove bd (normal mode) + mso_schema_template_bd: + <<: *bd_absent + register: nm_remove_bd + +- name: Verify nm_remove_bd + assert: + that: + - nm_remove_bd is changed + - nm_remove_bd.current == {} + +- name: Remove bd again (check_mode) + mso_schema_template_bd: + <<: *bd_absent + check_mode: true + register: cm_remove_bd_again + +- name: Verify cm_remove_bd_again + assert: + that: + - cm_remove_bd_again is not changed + - cm_remove_bd_again.current == {} + +- name: Remove bd again (normal mode) + mso_schema_template_bd: + <<: *bd_absent + register: nm_remove_bd_again + +- name: Verify nm_remove_bd_again + assert: + that: + - nm_remove_bd_again is not changed + - nm_remove_bd_again.current == {} + +- name: Remove bd 5 (normal mode) + mso_schema_template_bd: + <<: *bd_absent + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template5 + bd: ansible_test_5 + register: nm_remove_bd_5 + +- name: Verify nm_remove_bd_5 + assert: + that: + - nm_remove_bd_5 is changed + - nm_remove_bd_5.current == {} + +- name: Remove bd ansible_test_multiple_dhcp (normal mode) + mso_schema_template_bd: + <<: *bd_absent + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_multiple_dhcp + register: nm_remove_ansible_test_multiple_dhcp + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Verify nm_remove_ansible_test_multiple_dhcp + assert: + that: + - nm_remove_ansible_test_multiple_dhcp is changed + - nm_remove_ansible_test_multiple_dhcp.current == {} + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +# QUERY NON-EXISTING BD +- name: Query non-existing bd (check_mode) + mso_schema_template_bd: + <<: *bd_query + bd: ansible_test_1 + check_mode: true + ignore_errors: true + register: cm_query_non_bd + +- name: Query non-existing bd (normal mode) + mso_schema_template_bd: + <<: *bd_query + bd: ansible_test_1 + ignore_errors: true + register: nm_query_non_bd + +- name: Verify query_non_bd + assert: + that: + - cm_query_non_bd is not changed + - nm_query_non_bd is not changed + - cm_query_non_bd == nm_query_non_bd + - cm_query_non_bd.msg == nm_query_non_bd.msg == "BD 'ansible_test_1' not found" + + +# USE A NON-EXISTING STATE +- name: Non-existing state for bd (check_mode) + mso_schema_template_bd: + <<: *bd_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for bd (normal_mode) + mso_schema_template_bd: + <<: *bd_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for bd (check_mode) + mso_schema_template_bd: + <<: *bd_query + schema: non-existing-schema + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for bd (normal_mode) + mso_schema_template_bd: + <<: *bd_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for bd (check_mode) + mso_schema_template_bd: + <<: *bd_query + template: non-existing-template + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for bd (normal_mode) + mso_schema_template_bd: + <<: *bd_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# CLEAN UP DHCP Policies +- name: Ensure DHCP policies are removed + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy1' + - 'ansible_test_dhcp_policy2' + - 'ansible_test_dhcp_policy3' + when: + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +- name: Ensure DHCP option policies are removed + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy_option1' + - 'ansible_test_dhcp_policy_option2' + - 'ansible_test_dhcp_policy_option3' + when: + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + +# REMOVE Schemas for next CI Run +- name: Remove schemas for next ci test + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}'
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml new file mode 100644 index 000000000..b6b2d7a80 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml @@ -0,0 +1,454 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks on MSO version > 3.1.1g + when: + - version.current.version is version('3.1.1g', '>') + # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created + - version.current.version is version('4.0', '<') + block: + - name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + + - name: Remove DHCP policies + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy1' + - 'ansible_test_dhcp_policy2' + - 'ansible_test_dhcp_policy3' + + - name: Remove DHCP option policies + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy_option1' + - 'ansible_test_dhcp_policy_option2' + + - name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + + - name: Ensure schema 1 with Template1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template1 + state: present + + - name: Ensure VRF exists + mso_schema_template_vrf: &vrf_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF + layer3_multicast: true + state: present + + - name: Ensure multiple DHCP policies exist + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + description: "My Test DHCP Policies" + tenant: ansible_test + state: present + loop: + - 'ansible_test_dhcp_policy1' + - 'ansible_test_dhcp_policy2' + - 'ansible_test_dhcp_policy3' + + - name: Ensure multiple DHCP option policies exist + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + description: "My Test DHCP Policy Options" + tenant: ansible_test + state: present + loop: + - 'ansible_test_dhcp_policy_option1' + - 'ansible_test_dhcp_policy_option2' + + # ADD BD + - name: Add bd + mso_schema_template_bd: &bd_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: proxy + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: present + + - name: Add bd2 + mso_schema_template_bd: + <<: *bd_present + bd: ansible_test_2 + state: present + + # Add dhcp policies + - name: Add DHCP policy in check mode + mso_schema_template_bd_dhcp_policy: &dhcp_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + name: ansible_test_dhcp_policy1 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + register: cm_add_dhcp + check_mode: true + + - name: Add DHCP policy in normal mode + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_present + register: nm_add_dhcp + + - name: Add DHCP policy again in normal mode + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_present + register: nm_add_dhcp_again + + - name: Add another DHCP policy in normal mode + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_present + name: ansible_test_dhcp_policy2 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option2 + version: 1 + register: nm_add_dhcp2 + + + - name: Add dhcp for query all (normal mode) + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_present + name: ansible_test_dhcp_policy3 + version: 1 + register: nm_add_dhcp3 + + - name: Verify cm_add_dhcp, nm_add_dhcp, nm_add_dhcp2 and nm_add_dhcp3 + assert: + that: + - cm_add_dhcp is changed + - nm_add_dhcp is changed + - nm_add_dhcp_again is not changed + - nm_add_dhcp.current.name == 'ansible_test_dhcp_policy1' + - nm_add_dhcp.current.version == 1 + - nm_add_dhcp.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + - nm_add_dhcp.current.dhcpOptionLabel.version == 1 + - nm_add_dhcp2.current.name == 'ansible_test_dhcp_policy2' + - nm_add_dhcp2.current.version == 1 + - nm_add_dhcp2.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option2' + - nm_add_dhcp2.current.dhcpOptionLabel.version == 1 + - nm_add_dhcp3.current.name == 'ansible_test_dhcp_policy3' + - nm_add_dhcp3.current.version == 1 + + # CHANGE dhcp policies + - name: Change dhcp policy (normal mode) + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + name: ansible_test_dhcp_policy1 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option2 + version: 1 + register: nm_change_dhcp + + - name: Verify nm_change_dhcp + assert: + that: + - nm_change_dhcp is changed + - nm_change_dhcp.current.name == 'ansible_test_dhcp_policy1' + - nm_change_dhcp.current.version == 1 + - nm_change_dhcp.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option2' + + - name: Change dhcp policy again (normal mode) + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + name: ansible_test_dhcp_policy1 + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + register: nm_change_dhcp_again + + + - name: Verify nm_change_dhcp + assert: + that: + - nm_change_dhcp_again is changed + - nm_change_dhcp_again.current.name == 'ansible_test_dhcp_policy1' + - nm_change_dhcp_again.current.version == 1 + - nm_change_dhcp_again.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + + # QUERY ALL dhcp policies + - name: Query all dhcp (check_mode) + mso_schema_template_bd_dhcp_policy: &dhcp_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: query + check_mode: true + register: cm_query_all_dhcp + + + - name: Query all dhcp (normal mode) + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + register: nm_query_all_dhcp + + + - name: Verify query_all_dhcp + assert: + that: + - cm_query_all_dhcp is not changed + - nm_query_all_dhcp is not changed + - cm_query_all_dhcp.current | length == nm_query_all_dhcp.current | length == 3 + + # QUERY a DHCP policy + - name: Query single dhcp + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + name: ansible_test_dhcp_policy1 + register: nm_query_dhcp + + - name: Verify nm_query_dhcp + assert: + that: + - nm_query_dhcp is not changed + - nm_query_dhcp.current.name == 'ansible_test_dhcp_policy1' + - nm_query_dhcp.current.version == 1 + - nm_query_dhcp.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1' + + # QUERY a non associated DHCP policy + - name: Query non associated dhcp + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + bd: ansible_test_2 + name: ansible_test_dhcp_policy1 + ignore_errors: true + register: non_dhcp + + - name: Verify non_dhcp + assert: + that: + - non_dhcp.msg is match ("DHCP policy not associated with the bd") + + # REMOVE DHCP policy + - name: Remove dhcp policy + mso_schema_template_bd_dhcp_policy: &dhcp_absent + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + name: ansible_test_dhcp_policy1 + state: absent + register: nm_remove_dhcp + + - name: Verify nm_remove_dhcp + assert: + that: + - nm_remove_dhcp is changed + - nm_remove_dhcp.current == {} + + - name: Remove dhcp again (check_mode) + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_absent + register: nm_remove_dhcp_again + + + - name: Verify nm_remove_dhcp_again + assert: + that: + - nm_remove_dhcp_again is not changed + - nm_remove_dhcp_again.current == {} + + # QUERY NON-EXISTING DHCP policy + - name: Query non-existing dhcp policy + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + name: non_policy + version: 1 + dhcp_option_policy: + name: ansible_test_dhcp_policy_option1 + version: 1 + ignore_errors: true + register: nm_query_non_dhcp + + - name: Verify nm_query_non_dhcp + assert: + that: + - nm_query_non_dhcp is not changed + - nm_query_non_dhcp.msg is match ("DHCP policy 'non_policy' does not exist") + + # QUERY NON-EXISTING DHCP policy option + - name: Query non-existing dhcp policy option + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + name: ansible_test_dhcp_policy1 + version: 1 + dhcp_option_policy: + name: non_option + version: 1 + ignore_errors: true + register: nm_query_non_dhcp_option + + - name: Verify nm_query_non_dhcp + assert: + that: + - nm_query_non_dhcp_option is not changed + - nm_query_non_dhcp_option.msg is match ("DHCP option policy 'non_option' does not exist") + + # USE A NON-EXISTING STATE + - name: Non-existing state for dhcp + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + + - name: Verify non_existing_state + assert: + that: + - nm_non_existing_state is not changed + - nm_non_existing_state.msg is match ("value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state") + + # USE A NON-EXISTING SCHEMA + - name: Non-existing schema for dhcp + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + + - name: Verify non_existing_schema + assert: + that: + - nm_non_existing_schema is not changed + - nm_non_existing_schema.msg is match ("Provided schema 'non-existing-schema' does not exist.") + + # USE A NON-EXISTING TEMPLATE + - name: Non-existing template for dhcp + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + + - name: Verify non_existing_template + assert: + that: + - nm_non_existing_template is not changed + - nm_non_existing_template.msg is match ("Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1") + + # USE A NON-EXISTING BD + - name: Non-existing bd for dhcp + mso_schema_template_bd_dhcp_policy: + <<: *dhcp_query + bd: non-existing-bd + ignore_errors: true + register: nm_non_existing_bd + + - name: Verify non_existing_bd + assert: + that: + - nm_non_existing_bd is not changed + - nm_non_existing_bd.msg is match ("Provided BD 'non-existing-bd' does not exist. Existing BDs{{':'}} ansible_test_1") + + # REMOVE Schemas for next CI Run + - name: Remove schemas for next ci test + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + + # CLEAN UP DHCP Policies + - name: Ensure DHCP policies are removed + mso_dhcp_relay_policy: + <<: *mso_info + dhcp_relay_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy1' + - 'ansible_test_dhcp_policy2' + - 'ansible_test_dhcp_policy3' + + - name: Ensure DHCP option policies are removed + mso_dhcp_option_policy: + <<: *mso_info + dhcp_option_policy: '{{ item }}' + tenant: ansible_test + state: absent + loop: + - 'ansible_test_dhcp_policy_option1' + - 'ansible_test_dhcp_policy_option2'
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml new file mode 100644 index 000000000..75a3b4747 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml @@ -0,0 +1,630 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Set version vars + set_fact: + mso_l3mcast: false + when: version.current.version is version('2.2.4', '=') + +- name: Ensure site exist + mso_site: &site_present + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template1 + state: present + +- name: Ensure schema 2 with Template2 exists + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template2 + state: present + +- name: Ensure VRF exists + mso_schema_template_vrf: &vrf_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF + layer3_multicast: true + state: present + +- name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + vrf: VRF2 + +# ADD BD +- name: Add bd + mso_schema_template_bd: &bd_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: proxy + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + state: present + +- name: Add bd 2 + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + bd: ansible_test_2 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: proxy + vrf: + name: VRF2 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + +- name: Add bd + mso_schema_template_bd: + <<: *bd_present + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_3 + intersite_bum_traffic: true + optimize_wan_bandwidth: true + layer2_stretch: true + layer2_unknown_unicast: proxy + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + dhcp_policy: + name: ansible_test + version: 1 + dhcp_option_policy: + name: ansible_test_option + version: 1 + state: present + +# Add subnet +- name: Add subnet in check mode + mso_schema_template_bd_subnet: &subnet_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 172.16.0.1/24 + description: "My description for a subnet" + scope: public + shared: true + no_default_gateway: false + querier: true + state: present + register: cm_add_subnet + check_mode: true + +- name: Add subnet (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_present + register: nm_add_subnet + +- name: Add subnet again (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_present + register: nm_add_subnet_again + +- name: Add subnet for query all (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_present + subnet: 2.16.0.1/24 + +- name: Verify cm_add_subnet and nm_add_subnet + assert: + that: + - cm_add_subnet is changed + - nm_add_subnet is changed + - nm_add_subnet_again is not changed + - cm_add_subnet.current.description == "My description for a subnet" + - cm_add_subnet.current.ip == "172.16.0.1/24" + - cm_add_subnet.current.noDefaultGateway == false + - cm_add_subnet.current.scope == "public" + - cm_add_subnet.current.shared == true + - cm_add_subnet.current.querier == true + - nm_add_subnet.current.description == "My description for a subnet" + - nm_add_subnet.current.ip == "172.16.0.1/24" + - nm_add_subnet.current.noDefaultGateway == false + - nm_add_subnet.current.scope == "public" + - nm_add_subnet.current.shared == true + - nm_add_subnet.current.querier == true + +- name: Add subnet 2 (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + bd: ansible_test_2 + subnet: 10.1.1.1/24 + description: "My description for a subnet with virtual ip" + scope: public + shared: true + no_default_gateway: false + querier: true + is_virtual_ip: true + register: nm_add_subnet_2 + +- name: Verify nm_bd_2 for a version that's < 3.1 + assert: + that: + - nm_add_subnet_2.current.ip == "10.1.1.1/24" + - nm_add_subnet_2.current.noDefaultGateway == false + - nm_add_subnet_2.current.scope == "public" + - nm_add_subnet_2.current.shared == true + - nm_add_subnet_2.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_bd_2 for a version that's >= 3.1 + assert: + that: + - nm_add_subnet_2.current.ip == "10.1.1.1/24" + - nm_add_subnet_2.current.noDefaultGateway == false + - nm_add_subnet_2.current.scope == "public" + - nm_add_subnet_2.current.shared == true + - nm_add_subnet_2.current.querier == true + - nm_add_subnet_2.current.virtual == true + when: version.current.version is version('3.1.1g', '>=') + +# CHANGE Subnet +- name: Change subnet 2 (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + bd: ansible_test_2 + subnet: 10.1.1.1/24 + description: "My description for a subnet with virtual ip" + scope: public + shared: true + no_default_gateway: false + querier: true + is_virtual_ip: false + register: nm_change_subnet2 + +- name: Verify nm_change_subnet2 for a version < 3.1 + assert: + that: + - nm_change_subnet2 is not changed + - nm_change_subnet2.current.ip == "10.1.1.1/24" + - nm_change_subnet2.current.noDefaultGateway == false + - nm_change_subnet2.current.scope == "public" + - nm_change_subnet2.current.shared == true + - nm_change_subnet2.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_change_subnet2 for a version >= 3.1 + assert: + that: + - nm_change_subnet2 is changed + - nm_change_subnet2.current.ip == "10.1.1.1/24" + - nm_change_subnet2.current.noDefaultGateway == false + - nm_change_subnet2.current.scope == "public" + - nm_change_subnet2.current.shared == true + - nm_change_subnet2.current.querier == true + - nm_change_subnet2.current.virtual == false + when: version.current.version is version('3.1.1g', '>=') + +- name: Change subnet2 again (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + bd: ansible_test_2 + subnet: 10.1.1.1/24 + description: "My description for a subnet with virtual ip" + scope: public + shared: true + no_default_gateway: false + querier: true + is_virtual_ip: false + register: nm_change_subnet2_again + +- name: Verify nm_change_subnet2_again for a version that's < 3.1 + assert: + that: + - nm_change_subnet2_again is not changed + - nm_change_subnet2_again.current.ip == "10.1.1.1/24" + - nm_change_subnet2_again.current.noDefaultGateway == false + - nm_change_subnet2_again.current.scope == "public" + - nm_change_subnet2_again.current.shared == true + - nm_change_subnet2_again.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify cm_change_subnet2 for a version that's >= 3.1 + assert: + that: + - nm_change_subnet2_again is not changed + - nm_change_subnet2_again.current.ip == "10.1.1.1/24" + - nm_change_subnet2_again.current.noDefaultGateway == false + - nm_change_subnet2_again.current.scope == "public" + - nm_change_subnet2_again.current.shared == true + - nm_change_subnet2_again.current.querier == true + - nm_change_subnet2_again.current.virtual == false + when: version.current.version is version('3.1.1g', '>=') + +# Primary parameter +- name: Add subnet 3 with primary and querier parameters (normal mode) + mso_schema_template_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.1.5/24 + description: "My description for a subnet with virtual ip" + scope: public + shared: true + no_default_gateway: false + querier: true + primary: true + is_virtual_ip: true + state: present + register: nm_add_subnet_3 + +- name: Verify nm_add_subnet_3 for a version that's < 3.1 + assert: + that: + - nm_add_subnet_3.current.ip == "10.1.1.5/24" + - nm_add_subnet_3.current.noDefaultGateway == false + - nm_add_subnet_3.current.scope == "public" + - nm_add_subnet_3.current.shared == true + - nm_add_subnet_3.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_add_subnet_3 for a version that's >= 3.1 + assert: + that: + - nm_add_subnet_3.current.ip == "10.1.1.5/24" + - nm_add_subnet_3.current.noDefaultGateway == false + - nm_add_subnet_3.current.scope == "public" + - nm_add_subnet_3.current.shared == true + - nm_add_subnet_3.current.querier == true + - nm_add_subnet_3.current.virtual == true + - nm_add_subnet_3.current.primary == true + when: version.current.version is version('3.1.1g', '>=') + +# CHANGE Subnet +- name: Change subnet 3 (normal mode) + mso_schema_template_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.1.5/24 + description: "My description for a subnet with virtual ip" + scope: public + shared: true + no_default_gateway: false + querier: true + is_virtual_ip: false + primary: true + state: present + register: nm_change_subnet3 + +- name: Verify nm_change_subnet3 for a version < 3.1 + assert: + that: + - nm_change_subnet3 is not changed + - nm_change_subnet3.current.ip == "10.1.1.5/24" + - nm_change_subnet3.current.noDefaultGateway == false + - nm_change_subnet3.current.scope == "public" + - nm_change_subnet3.current.shared == true + - nm_change_subnet3.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_change_subnet2 for a version >= 3.1 + assert: + that: + - nm_change_subnet3 is changed + - nm_change_subnet3.current.ip == "10.1.1.5/24" + - nm_change_subnet3.current.noDefaultGateway == false + - nm_change_subnet3.current.scope == "public" + - nm_change_subnet3.current.shared == true + - nm_change_subnet3.current.querier == true + - nm_change_subnet3.current.virtual == false + - nm_change_subnet3.current.primary == true + when: version.current.version is version('3.1.1g', '>=') + +- name: Change subnet3 again (normal mode) + mso_schema_template_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 10.1.1.5/24 + description: "My description for a subnet with virtual ip" + scope: public + shared: true + no_default_gateway: false + querier: true + is_virtual_ip: false + primary: true + state: present + register: nm_change_subnet3_again + +- name: Verify nm_change_subnet2_again for a version that's < 3.1 + assert: + that: + - nm_change_subnet3_again is not changed + - nm_change_subnet3_again.current.ip == "10.1.1.5/24" + - nm_change_subnet3_again.current.noDefaultGateway == false + - nm_change_subnet3_again.current.scope == "public" + - nm_change_subnet3_again.current.shared == true + - nm_change_subnet3_again.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify cm_change_subnet2 for a version that's >= 3.1 + assert: + that: + - nm_change_subnet3_again is not changed + - nm_change_subnet3_again.current.ip == "10.1.1.5/24" + - nm_change_subnet3_again.current.noDefaultGateway == false + - nm_change_subnet3_again.current.scope == "public" + - nm_change_subnet3_again.current.shared == true + - nm_change_subnet3_again.current.querier == true + - nm_change_subnet3_again.current.virtual == false + - nm_change_subnet3_again.current.primary == true + when: version.current.version is version('3.1.1g', '>=') + +# QUERY ALL Subnets +- name: Query all subnet (check_mode) + mso_schema_template_bd_subnet: &subnet_query + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + state: query + check_mode: true + register: cm_query_all_subnet + +- name: Query all subnet (normal mode) + mso_schema_template_bd_subnet: + <<: *subnet_query + register: nm_query_all_subnet + +- name: Verify query_all_subnet + assert: + that: + - cm_query_all_subnet is not changed + - nm_query_all_subnet is not changed + - cm_query_all_subnet.current | length == nm_query_all_subnet.current | length == 3 + +# QUERY A subnet +- name: Query subnet2 + mso_schema_template_bd_subnet: + <<: *subnet_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template2 + bd: ansible_test_2 + subnet: 10.1.1.1/24 + register: nm_query_subnet2 + +- name: Verify nm_query_subnet2 for a version that's < 3.1 + assert: + that: + - nm_query_subnet2 is not changed + - nm_query_subnet2.current.ip == "10.1.1.1/24" + - nm_query_subnet2.current.noDefaultGateway == false + - nm_query_subnet2.current.scope == "public" + - nm_query_subnet2.current.shared == true + - nm_query_subnet2.current.querier == true + when: version.current.version is version('3.1.1g', '<') + +- name: Verify nm_query_subnet2 for a version that's >= 3.1 + assert: + that: + - nm_query_subnet2 is not changed + - nm_query_subnet2.current.ip == "10.1.1.1/24" + - nm_query_subnet2.current.noDefaultGateway == false + - nm_query_subnet2.current.scope == "public" + - nm_query_subnet2.current.shared == true + - nm_query_subnet2.current.querier == true + - nm_query_subnet2.current.virtual == false + when: version.current.version is version('3.1.1g', '>=') + +# REMOVE Subnet +- name: Remove subnet + mso_schema_template_bd_subnet: &subnet_absent + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_1 + subnet: 172.16.0.1/24 + state: absent + register: nm_remove_subnet + +- name: Verify nm_remove_subnet + assert: + that: + - nm_remove_subnet is changed + - nm_remove_subnet.current == {} + +- name: Remove subnet again (check_mode) + mso_schema_template_bd_subnet: + <<: *subnet_absent + register: nm_remove_subnet_again + +- name: Verify nm_remove_subnet_again + assert: + that: + - nm_remove_subnet_again is not changed + - nm_remove_subnet_again.current == {} + +# QUERY NON-EXISTING Subnet +- name: Query non-existing subnet + mso_schema_template_bd_subnet: + <<: *subnet_query + bd: ansible_test_1 + subnet: 172.16.0.3/24 + ignore_errors: true + register: nm_query_non_subnet + +- name: Verify nm_query_non_subnet + assert: + that: + - nm_query_non_subnet is not changed + - nm_query_non_subnet.msg is match ("Subnet IP '172.16.0.3/24' not found") + +# USE A NON-EXISTING STATE +- name: Non-existing state for subnet + mso_schema_template_bd_subnet: + <<: *subnet_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - nm_non_existing_state is not changed + - nm_non_existing_state.msg is match ("value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state") + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for subnet + mso_schema_template_bd_subnet: + <<: *subnet_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - nm_non_existing_schema is not changed + - nm_non_existing_schema.msg is match ("Provided schema 'non-existing-schema' does not exist.") + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for subnet + mso_schema_template_bd_subnet: + <<: *subnet_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - nm_non_existing_template is not changed + - nm_non_existing_template.msg is match ("Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1") + +# USE NON-EXISTING OPTIONS +- name: Add subnet with no description + mso_schema_template_bd_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: ansible_test_3 + subnet: 172.16.0.5/24 + state: present + register: nm_add_subnet_no_desc + +- name: Verify nm_add_subnet_no_desc + assert: + that: + - nm_add_subnet_no_desc.current.description == "172.16.0.5/24" + +# USE A NON-EXISTING BD +- name: Non-existing bd for subnet + mso_schema_template_bd_subnet: + <<: *subnet_query + bd: non-existing-bd + ignore_errors: true + register: nm_non_existing_bd + +- name: Verify non_existing_bd + assert: + that: + - nm_non_existing_bd is not changed + - nm_non_existing_bd.msg is match ("Provided BD 'non-existing-bd' does not exist. Existing BDs{{':'}} ansible_test_1") + +- name: Remove schemas for next ci test + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}'
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml new file mode 100644 index 000000000..0039a9b02 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml @@ -0,0 +1,293 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + ignore_errors: true + loop: + - Schema1 + - Schema2 + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure user is defined under common tenant + mso_tenant: + <<: *mso_info + tenant: common + users: + - '{{ mso_username }}' + state: present + when: version.current.version is version('3.2', '<') + +- name: Create Schema1 with Template 1, and Template 2 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: Schema1 + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template1 + - Template2 + +- name: Create Schema2 with Template 3 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: Schema2 + tenant: ansible_test + template: Template3 + state: present + +- name: Ensure ANP exist + cisco.mso.mso_schema_template_anp: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + state: present + loop: + - { schema: 'Schema1', template: 'Template1' } + - { schema: 'Schema1', template: 'Template2' } + - { schema: 'Schema2', template: 'Template3' } + +- name: Ensure EPGs exist + cisco.mso.mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ item.schema }}' + template: '{{ item.template }}' + anp: ANP + epg: '{{ item.epg }}' + state: present + loop: + - { schema: 'Schema1', template: 'Template1', epg: 'ansible_test_1' } + - { schema: 'Schema1', template: 'Template2', epg: 'ansible_test_2' } + - { schema: 'Schema2', template: 'Template3', epg: 'ansible_test_3' } + +- name: Add Selector to EPG (normal_mode) + mso_schema_template_anp_epg_selector: + <<: *mso_info + schema: 'Schema1' + template: Template1 + anp: ANP + epg: ansible_test_1 + selector: selector_1 + state: present + +- name: Clone template in the same schema (check_mode) + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: Schema1 + destination_tenant: ansible_test + source_template_name: Template1 + destination_template_name: Template1_clone + destination_template_display_name: Template1_clone + state: clone + check_mode: true + register: cm_add_template + +- name: Clone template in the same schema (normal mode) + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: Schema1 + destination_tenant: ansible_test + source_template_name: Template1 + destination_template_name: Template1_clone + destination_template_display_name: Template1_clone + state: clone + register: add_template + +- name: Clone template in the same schema without destination_schema being specified + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + source_template_name: Template1 + destination_template_name: Template1_clone_nodestschema + state: clone + register: add_template_nodestschema + +- name: Clone template to different schema + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: Schema2 + destination_tenant: ansible_test + source_template_name: Template2 + destination_template_name: Cloned_template_1 + destination_template_display_name: Cloned_template_1 + state: clone + register: add_template_schema + +- name: Clone template to different schema but keep template name + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: Schema2 + source_template_name: Template2 + state: clone + register: add_template_schema_2 + +- name: Clone template in the same schema but different tenant attached + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: Schema1 + destination_tenant: common + source_template_name: Template1_clone + destination_template_name: Template1_clone_2 + state: clone + register: add_template_tenant + +- name: Verify add_templates + assert: + that: + - cm_add_template is not changed + - add_template is changed + - (add_template.current.templates | selectattr('displayName', 'contains', 'Template1_clone')|first).name == 'Template1_clone' + - add_template_nodestschema is changed + - (add_template_nodestschema.current.templates | selectattr('displayName', 'contains', 'Template1_clone_nodestschema')|first).name == 'Template1_clone_nodestschema' + - add_template_schema is changed + - (add_template_schema.current.templates | selectattr('displayName', 'contains', 'Cloned_template_1')|first).name == 'Cloned_template_1' + - add_template_schema_2 is changed + - (add_template_schema_2.current.templates | selectattr('displayName', 'contains', 'Template2')|first).name == 'Template2' + - add_template_tenant is changed + - (add_template_tenant.current.templates | selectattr('displayName', 'contains', 'Template1_clone_2')|first).name == 'Template1_clone_2' + +# Checking for other cases +- name: Clone non existing template + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema2 + destination_schema: Schema2 + destination_tenant: common + source_template_name: non_existing_template + destination_template_name: Cloned_template_2 + destination_template_display_name: Cloned_template_2 + state: clone + ignore_errors: true + register: non_existing_template + +- name: Verify non_existing_template + assert: + that: + - non_existing_template is not changed + - non_existing_template.msg == "Source template with the name 'non_existing_template' does not exist." + +- name: Clone non existing source schema + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: non_existing_schema + destination_schema: Schema2 + source_template_name: Template2 + destination_template_name: NewTemplate2 + state: clone + ignore_errors: true + register: non_existing_source_schema + +- name: Verify non_existing_source_schema + assert: + that: + - non_existing_source_schema is not changed + - non_existing_source_schema.msg == "Schema with the name 'non_existing_schema' does not exist." + +- name: Clone non existing destination schema + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: non_existing_schema + destination_tenant: common + source_template_name: Template2 + destination_template_name: Template_clone + destination_template_display_name: Template_clone + state: clone + ignore_errors: true + register: non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - non_existing_schema is not changed + - non_existing_schema.msg == "Schema with the name 'non_existing_schema' does not exist." + +- name: Clone to same schema with same template name + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + source_template_name: Template1 + state: clone + ignore_errors: true + register: wrong_template_name + +- name: Verify wrong_template_name + assert: + that: + - wrong_template_name is not changed + - wrong_template_name.msg == "Source and destination templates in the same schema cannot have same names." + +- name: Clone schema to schema with existing template with same name + cisco.mso.mso_schema_template_clone: + <<: *mso_info + source_schema: Schema1 + destination_schema: Schema2 + source_template_name: Template2 + state: clone + ignore_errors: true + register: template_already_exist + +- name: Verify template_already_exist + assert: + that: + - template_already_exist is not changed + - template_already_exist.msg == "Template with the name 'Template2' already exists. Please use another name." + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + register: rm_schema + loop: + - Schema2 + - Schema1 + +- name: Verify rm_schema + assert: + that: + - rm_schema is changed
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml new file mode 100644 index 000000000..08f6d5cf5 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml @@ -0,0 +1,1088 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Akini Ross (@akinross) <akinross@cisco.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schema 2 + mso_schema: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + state: absent + +- name: Remove schema 1 + mso_schema: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: absent + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *schema_present + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure Filter1 exist + cisco.mso.mso_schema_template_filter_entry: &filter_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + #add filter entry + entry: Filter1Entry + state: present + register: add_filter + +- name: Ensure Filter2 exist + mso_schema_template_filter_entry: + <<: *filter_present + template: Template 2 + filter: Filter2 + entry: Filter2Entry + state: present + +- name: Ensure Filter3 exist + mso_schema_template_filter_entry: + <<: *filter_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + filter: Filter3 + entry: Filter3Entry + state: present + +- name: Ensure Filter4 exist + mso_schema_template_filter_entry: + <<: *filter_present + filter: Filter4 + entry: Filter4Entry + state: present + +- name: Ensure Filter5 exist + mso_schema_template_filter_entry: + <<: *filter_present + filter: Filter5 + entry: Filter5Entry + state: present + +- name: Ensure Filter-6 exist + mso_schema_template_filter_entry: + <<: *filter_present + filter: Filter-6 + entry: Filter-6Entry + state: present + +- name: Ensure Contract_1 contract does not exist + mso_schema_template_contract_filter: &contract_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: absent + +- name: Ensure Contract_2 contract does not exist + mso_schema_template_contract_filter: + <<: *contract_present + template: Template 2 + contract: Contract2 + state: absent + +- name: Ensure Contract_3 contract does not exist + mso_schema_template_contract_filter: + <<: *contract_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + contract: Contract3 + state: absent + +- name: Ensure Contract_4 contract does not exist + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + state: absent + +- name: Ensure Contract_5 contract does not exist + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract5 + state: absent + +- name: Ensure Contract_6 contract does not exist + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract-6 + state: absent + +# ADD CONTRACT +- name: Add contract (check_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + filter: Filter1 + state: present + check_mode: true + register: cm_add_contract + +- name: Verify cm_add_contract + assert: + that: + - cm_add_contract is changed + - cm_add_contract.previous == {} + - cm_add_contract.current.filterRef.filterName == "Filter1" + - cm_add_contract.current.filterRef.templateName == "Template1" + +- name: Add contract (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + filter: Filter1 + state: present + register: nm_add_contract + +- name: Verify nm_add_contract + assert: + that: + - nm_add_contract is changed + - nm_add_contract.previous == {} + - nm_add_contract.current.filterRef.filterName == "Filter1" + - nm_add_contract.current.filterRef.templateName == "Template1" + - cm_add_contract.current.filterRef.schemaId == nm_add_contract.current.filterRef.schemaId + +- name: Add contract again (check_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + filter: Filter1 + state: present + check_mode: true + register: cm_add_contract_again + +- name: Verify cm_add_contract_again + assert: + that: + - cm_add_contract_again is not changed + - cm_add_contract_again.current.filterRef.filterName == "Filter1" + - cm_add_contract_again.current.filterRef.templateName == "Template1" + - cm_add_contract_again.previous.filterRef.filterName == "Filter1" + - cm_add_contract_again.previous.filterRef.templateName == "Template1" + - cm_add_contract_again.previous.filterRef.schemaId == cm_add_contract_again.current.filterRef.schemaId + +- name: Add contract again (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + filter: Filter1 + state: present + register: nm_add_contract_again + +- name: Verify nm_add_contract_again + assert: + that: + - nm_add_contract_again is not changed + - nm_add_contract_again.current.filterRef.filterName == "Filter1" + - nm_add_contract_again.current.filterRef.templateName == "Template1" + - nm_add_contract_again.current.filterRef.templateName == "Template1" + - nm_add_contract_again.previous.filterRef.filterName == "Filter1" + - nm_add_contract_again.previous.filterRef.templateName == "Template1" + - nm_add_contract_again.previous.filterRef.schemaId == nm_add_contract_again.current.filterRef.schemaId + +- name: Add Contract2 (check_mode) + mso_schema_template_contract_filter: + <<: *contract_present + template: Template 2 + contract: Contract2 + filter: Filter1 + filter_template: Template 1 + state: present + check_mode: true + register: cm_add_contract_2 + +- name: Verify cm_add_contract_2 + assert: + that: + - cm_add_contract_2 is changed + - cm_add_contract_2.current.filterRef.filterName == "Filter1" + - cm_add_contract_2.current.filterRef.templateName == "Template1" + +- name: Add Contract2 (nomal mode) + mso_schema_template_contract_filter: + <<: *contract_present + template: Template 2 + contract: Contract2 + filter: Filter1 + filter_template: Template 1 + state: present + register: nm_add_contract_2 + +- name: Verify nm_add_contract_2 + assert: + that: + - nm_add_contract_2 is changed + - nm_add_contract_2.current.filterRef.filterName == "Filter1" + - nm_add_contract_2.current.filterRef.templateName == "Template1" + - cm_add_contract_2.current.filterRef.schemaId == nm_add_contract_2.current.filterRef.schemaId + +- name: Add Contract3 (nomal mode) + mso_schema_template_contract_filter: + <<: *contract_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + contract: Contract3 + filter: Filter1 + filter_template: Template 1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + state: present + register: nm_add_contract_3 + +- name: Verify nm_add_contract_3 + assert: + that: + - nm_add_contract_3 is changed + - nm_add_contract_3.current.filterRef.filterName == "Filter1" + - nm_add_contract_3.current.filterRef.templateName == "Template1" + +- name: Add Contract4 (nomal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + contract_display_name: displayContract4 + state: present + register: nm_add_contract_4 + +- name: Verify nm_add_contract_4 + assert: + that: + - nm_add_contract_4 is changed + - nm_add_contract_4.current.filterRef.filterName == "Filter1" + - nm_add_contract_4.current.filterRef.templateName == "Template1" + - nm_add_contract_3.current.filterRef.schemaId == nm_add_contract_4.current.filterRef.schemaId == nm_add_contract_2.current.filterRef.schemaId == nm_add_contract.current.filterRef.schemaId + +# create CONTRACT FILTER with diff options +- name: Add Contract filter to both-way(check_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + contract_filter_type: both-way + filter: Filter4 + filter_type: both-way + state: present + check_mode: true + register: cm_add_contract_filter_both_way + +- name: Add Contract filter to both-way(normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + contract_filter_type: both-way + filter: Filter4 + filter_type: both-way + state: present + register: nm_add_contract_filter_both_way + +- name: Verify cm_change_contract_filter_both_way + assert: + that: + - cm_add_contract_filter_both_way is changed + - nm_add_contract_filter_both_way is changed + - cm_add_contract_filter_both_way.previous == {} + - nm_add_contract_filter_both_way.previous == {} + - cm_add_contract_filter_both_way.current.filterRef.filterName == "Filter4" + - cm_add_contract_filter_both_way.current.filterRef.templateName == "Template1" + - nm_add_contract_filter_both_way.current.filterRef.filterName == "Filter4" + - nm_add_contract_filter_both_way.current.filterRef.templateName == "Template1" + - cm_add_contract_filter_both_way.current.filterRef.schemaId == nm_add_contract_filter_both_way.current.filterRef.schemaId + - cm_add_contract_filter_both_way.current.contractFilterType == "bothWay" + - cm_add_contract_filter_both_way.current.contractScope == "context" + - cm_add_contract_filter_both_way.current.displayName == "Contract1" + - cm_add_contract_filter_both_way.current.filterType == "both-way" + +- name: Change Contract type one_way Filter type consumer-to-provider(normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract5 + contract_filter_type: one-way + filter: Filter5 + filter_type: consumer-to-provider + state: present + register: nm_one_way_and_consumer_to_provider + +- name: Verify nm_one_way_and_consumer_to_provider + assert: + that: + - nm_one_way_and_consumer_to_provider is changed + - nm_one_way_and_consumer_to_provider.previous == {} + - nm_one_way_and_consumer_to_provider.current.contractFilterType == "oneWay" + - nm_one_way_and_consumer_to_provider.current.contractScope == "context" + - nm_one_way_and_consumer_to_provider.current.displayName == "Contract5" + - nm_one_way_and_consumer_to_provider.current.filterType == "consumer-to-provider" + +- name: Change Contract type one_way Filter type provider-to-consumer(normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract-6 + contract_filter_type: one-way + filter: Filter-6 + filter_type: provider-to-consumer + state: present + register: nm_one_way_and_provider_to_consumer + +- name: Verify nm create contract filter with different type + assert: + that: + - nm_one_way_and_provider_to_consumer is changed + - nm_one_way_and_provider_to_consumer.current.contractFilterType == "oneWay" + - nm_one_way_and_provider_to_consumer.current.contractScope == "context" + - nm_one_way_and_provider_to_consumer.current.displayName == "Contract-6" + - nm_one_way_and_provider_to_consumer.current.filterType == "provider-to-consumer" + +# change contract display name +- name: change contract display name + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + contract_display_name: newDisplayContract4 + state: present + register: nm_change_display_name + +- name: Verify nm_change_display_name + assert: + that: + - nm_change_display_name is changed + - nm_change_display_name.current.displayName == "newDisplayContract4" + - nm_change_display_name.previous.displayName == "displayContract4" + +# change contract filter_directives to log +- name: change contract filter_directives to log(check_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + filter_directives: log + state: present + check_mode: true + register: cm_change_filter_directives_log + +- name: change contract filter_directives to log(normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + filter_directives: log + state: present + register: nm_change_filter_directives_log + +- name: Verify change_contract_filter_directives to log + assert: + that: + - cm_change_filter_directives_log is changed + - nm_change_filter_directives_log is changed + - cm_change_filter_directives_log.previous.directives[0] == "none" + - nm_change_filter_directives_log.previous.directives[0] == "none" + - cm_change_filter_directives_log.current.directives[0] == "log" + - nm_change_filter_directives_log.current.directives[0] == "log" + +- name: change contract filter_directives to log and none(normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + filter_directives: ['log', 'none'] + state: present + register: nm_change_filter_directives_log_and_none + +- name: Verify nm_change_filter_directives_log_and_none + assert: + that: + - nm_change_filter_directives_log_and_none is changed + - nm_change_filter_directives_log_and_none.previous.directives[0] == "log" + - nm_change_filter_directives_log_and_none.current.directives == ['log', 'none'] + +# change contract filter_directives to policy_compression +- name: change contract filter_directives to policy_compression (check_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + filter_directives: policy_compression + state: present + check_mode: true + register: cm_change_filter_directives_pc + +- name: change contract filter_directives to policy_compression (normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + filter_directives: policy_compression + state: present + register: nm_change_filter_directives_pc + +- name: Verify change_contract_filter_directives to pc + assert: + that: + - cm_change_filter_directives_pc is changed + - nm_change_filter_directives_pc is changed + - cm_change_filter_directives_pc.previous.directives == ['log', 'none'] + - nm_change_filter_directives_pc.previous.directives == ['log', 'none'] + - cm_change_filter_directives_pc.current.directives[0] == "no_stats" + - nm_change_filter_directives_pc.current.directives[0] == "no_stats" + +- name: change contract filter_directives to log, none, policy compression (normal_mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract4 + filter: Filter1 + filter_directives: ['log', 'none', 'policy_compression'] + state: present + register: nm_change_filter_directives_log_and_none_pc + +- name: Verify nm_change_filter_directives_log_and_none_pc + assert: + that: + - nm_change_filter_directives_log_and_none_pc is changed + - nm_change_filter_directives_log_and_none_pc.previous.directives[0] == "no_stats" + - nm_change_filter_directives_log_and_none_pc.current.directives == ["log", "none", "no_stats"] + +- name: Change Contract1 scope to global (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + contract_scope: global + state: present + register: nm_change_contract_scope_global + +- name: Verify nm_change_contract_scope_global + assert: + that: + - nm_change_contract_scope_global is changed + - nm_change_contract_scope_global.current.contractScope == "global" + - nm_change_contract_scope_global.previous.contractScope == "context" + +- name: Change Contract1 scope to tenant(normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + contract_scope: tenant + state: present + register: nm_change_contract_scope_tenant + +- name: Verify nm_change_contract_scope_tenant + assert: + that: + - nm_change_contract_scope_tenant is changed + - nm_change_contract_scope_tenant.previous.contractScope == "global" + - nm_change_contract_scope_tenant.current.contractScope == "tenant" + +- name: Change Contract1 scope application_profile(normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + contract_scope: application-profile + state: present + register: nm_change_contract_scope_application_profile + +- name: Verify nm_change_contract_scope_application_profile + assert: + that: + - nm_change_contract_scope_application_profile is changed + - nm_change_contract_scope_application_profile.previous.contractScope == "tenant" + - nm_change_contract_scope_application_profile.current.contractScope == "application-profile" + +- name: Change Contract1 scope to vrf(normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + contract_scope: vrf + state: present + register: nm_change_contract_scope_vrf + +- name: Verify nm_change_contract_scope_vrf + assert: + that: + - nm_change_contract_scope_vrf is changed + - nm_change_contract_scope_vrf.current.contractScope == "context" + - nm_change_contract_scope_vrf.previous.contractScope == "application-profile" + +- name: Change Contract1 scope to default(normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + state: present + register: nm_change_contract_scope_default + +- name: Verify nm_change_contract_scope_default + assert: + that: + - nm_change_contract_scope_default is not changed + - nm_change_contract_scope_default.current.contractScope == "context" + - nm_change_contract_scope_default.previous.contractScope == "context" + +- name: Change Contract1 description (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + description: changed description + state: present + register: nm_change_contract_description + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_change_contract_description + assert: + that: + - nm_change_contract_description is changed + - nm_change_contract_description.current.description == "changed description" + - nm_change_contract_description.previous.description == "" + when: version.current.version is version('3.3', '>=') + +- name: Change Contract1 description empty (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + description: "" + state: present + register: nm_change_contract_description_empty + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_change_contract_description_empty + assert: + that: + - nm_change_contract_description_empty is changed + - nm_change_contract_description_empty.current.description == "" + - nm_change_contract_description_empty.previous.description == "changed description" + when: version.current.version is version('3.3', '>=') + +- name: Change Contract1 qos_level (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + qos_level: level1 + state: present + register: nm_change_contract_qos + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_change_contract_qos_level + assert: + that: + - nm_change_contract_qos is changed + - nm_change_contract_qos.current.prio == "level1" + when: version.current.version is version('3.3', '>=') + +- name: Change Contract1 qos_level unspecified (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + qos_level: unspecified + state: present + register: nm_change_contract_qos_unspecified + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_change_contract_qos_level_unspecified + assert: + that: + - nm_change_contract_qos_unspecified is changed + - nm_change_contract_qos_unspecified.current.prio == "unspecified" + - nm_change_contract_qos_unspecified.previous.prio == "level1" + when: version.current.version is version('3.3', '>=') + +- name: Ensure contract filter_type set to both-way (normal mode) + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + filter: Filter1 + filter_type: both-way + state: present + register: nm_contract_filter_both_way + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_contract_filter_both_way + assert: + that: + - nm_contract_filter_both_way.current.contractFilterType == "bothWay" + - nm_contract_filter_both_way.current.filterType == "both-way" + when: version.current.version is version('3.3', '>=') + +- name: Change contract filter_type set to one-way with consumer-to-provider (normal mode) + # Test to check that filter type cannot be changed from two-way to one-way type + # changed behaviour due to error handling is now handled in code + mso_schema_template_contract_filter: + <<: *contract_present + contract: Contract1 + filter: Filter1 + filter_type: consumer-to-provider + state: present + register: nm_contract_filter_consumer_to_provider + ignore_errors: true + when: version.current.version is version('3.3', '>=') + +- name: Verify nm_contract_filter_consumer_to_provider + assert: + that: + - nm_contract_filter_consumer_to_provider.msg == "Current filter type 'bothWay' for contract 'Contract1' is not allowed to change to 'oneWay'." + when: version.current.version is version('3.3', '>=') + +# QUERY ALL CONTRACT +- name: Query Contract1 filters (check_mode) + mso_schema_template_contract_filter: &Contract_query + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + state: query + check_mode: true + register: cm_contract1_query_result + +- name: Query Contract1 filters (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + register: nm_contract1_query_result + +- name: Verify query_contract_1 + assert: + that: + - cm_contract1_query_result is not changed + - nm_contract1_query_result is not changed + - cm_contract1_query_result.current | length == nm_contract1_query_result.current | length == 2 + +- name: Query Contract2 filters (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + contract: Contract2 + state: query + register: nm_contract2_query_result + +- name: Query Contract3 filters (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + contract: Contract3 + register: nm_contract3_query_result + +- name: Query Contract4 filters (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract4 + register: nm_contract4_query_result + +- name: Query Contract5 filters (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract5 + contract_filter_type: one-way + filter_type: consumer-to-provider + register: nm_contract5_query_result + +- name: Query Contract-6 filters (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract-6 + contract_filter_type: one-way + filter_type: provider-to-consumer + register: nm_contract6_query_result + +- name: Verify query_contract + assert: + that: + - nm_contract2_query_result is not changed + - nm_contract3_query_result is not changed + - nm_contract4_query_result is not changed + - nm_contract2_query_result.current | length == nm_contract3_query_result.current | length == nm_contract4_query_result.current | length == 1 + - nm_contract5_query_result is not changed + - nm_contract6_query_result is not changed + - nm_contract5_query_result.current | length == 1 + - nm_contract6_query_result.current | length == 1 + +# QUERY A SPECIFIC CONTRACT FILTER +- name: Query Contract1 Filter1 (check_mode) + mso_schema_template_contract_filter: &Contract_filter_query + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + state: query + check_mode: true + register: cm_contract1_filter1_query_result + +- name: Query Contract1 Filter4 (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_filter_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter4 + state: query + register: nm_contract1_filter4_query_result + +- name: Query Contract2 Filter1 (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_filter_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + contract: Contract2 + filter_template: Template 1 + filter: Filter1 + state: query + register: nm_contract2_filter1_query_result + +- name: Query Contract3 Filter1 (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_filter_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + contract: Contract3 + filter: Filter1 + filter_template: Template 1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + state: query + register: nm_contract3_filter1_query_result + +- name: Query Contract4 Filter1 (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_filter_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract4 + filter: Filter1 + state: query + register: nm_contract4_filter1_query_result + +- name: Query Contract5 Filter5 (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_filter_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract5 + filter: Filter5 + contract_filter_type: one-way + filter_type: consumer-to-provider + state: query + register: nm_contract5_filter5_query_result + +- name: Query Contract-6 Filter-6 (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_filter_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract-6 + filter: Filter-6 + contract_filter_type: one-way + filter_type: provider-to-consumer + state: query + register: nm_contract6_filter6_query_result + +- name: Verify contract1_filter1_query_result + assert: + that: + - cm_contract1_filter1_query_result is not changed + - nm_contract1_filter4_query_result is not changed + - nm_contract2_filter1_query_result is not changed + - nm_contract3_filter1_query_result is not changed + - nm_contract4_filter1_query_result is not changed + - nm_contract5_filter5_query_result is not changed + - nm_contract6_filter6_query_result is not changed + +# REMOVE CONTRACT Filter +- name: Remove contract1 filter1 (check_mode) + mso_schema_template_contract_filter: &contract1_filter1_absent + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + state: absent + check_mode: true + register: cm_remove_contract1_filter1 + +- name: Verify cm_remove_contract1_filter1 + assert: + that: + - cm_remove_contract1_filter1 is changed + - cm_remove_contract1_filter1.current == {} + - cm_remove_contract1_filter1.previous.filterRef.filterName == "Filter1" + - cm_remove_contract1_filter1.previous.filterRef.templateName == "Template1" + +- name: Remove contract1 filter1 (normal_mode) + mso_schema_template_contract_filter: + <<: *contract1_filter1_absent + register: nm_remove_contract1_filter1 + +- name: Verify nm_remove_contract1_filter1 + assert: + that: + - nm_remove_contract1_filter1 is changed + - nm_remove_contract1_filter1.current == {} + - nm_remove_contract1_filter1.previous.filterRef.filterName == "Filter1" + - nm_remove_contract1_filter1.previous.filterRef.templateName == "Template1" + +- name: Remove contract1 filter1 again (check_mode) + mso_schema_template_contract_filter: + <<: *contract1_filter1_absent + check_mode: true + register: cm_remove_contract1_filter1_again + +- name: Verify cm_remove_contract1_filter1_again + assert: + that: + - cm_remove_contract1_filter1_again is not changed + - cm_remove_contract1_filter1_again.current == {} + - cm_remove_contract1_filter1_again.previous == {} + +- name: Remove contract1 filter1 again (normal_mode) + mso_schema_template_contract_filter: + <<: *contract1_filter1_absent + register: nm_remove_contract1_filter1_again + +- name: Verify nm_remove_contract1_filter1_again + assert: + that: + - nm_remove_contract1_filter1_again is not changed + - nm_remove_contract1_filter1_again.current == {} + - nm_remove_contract1_filter1_again.previous == {} + +- name: Remove contract1 filter4 (normal_mode) + mso_schema_template_contract_filter: + <<: *contract1_filter1_absent + filter: Filter4 + register: nm_remove_contract1_filter4 + +- name: Verify nm_remove_contract1_filter4 + assert: + that: + - nm_remove_contract1_filter4 is changed + - nm_remove_contract1_filter4.current == {} + - nm_remove_contract1_filter4.previous.filterRef.filterName == "Filter4" + - nm_remove_contract1_filter4.previous.filterRef.templateName == "Template1" + +- name: Remove contract1 filter4 again (normal_mode) + mso_schema_template_contract_filter: + <<: *contract1_filter1_absent + filter: Filter4 + register: nm_remove_contract1_filter4_again + +- name: Verify nm_remove_contract1_filter4_again + assert: + that: + - nm_remove_contract1_filter4_again is not changed + - nm_remove_contract1_filter4_again.previous == nm_remove_contract1_filter4_again.current == {} + +# QUERY NON-EXISTING FILTER +- name: Query non-existing filter (check_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract4 + filter: non-existing-filter + check_mode: true + ignore_errors: true + register: cm_query_non_filter + +- name: Query non-existing filter (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract4 + filter: non-existing-filter + ignore_errors: true + register: nm_query_non_filter + +- name: Verify query_non_filter + assert: + that: + - cm_query_non_filter is not changed + - nm_query_non_filter is not changed + +- name: Add contract (for version greater than 3.3) + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + description: "This is contract description" + filter: Filter1 + qos_level: level1 + action: deny + priority: 'lowest_priority' + state: present + register: add_contract + when: version.current.version is version('3.3', '>=') + +- name: Verify Add contract for version greater than 3.3 + assert: + that: + - add_contract is changed + - add_contract.current.action == "deny" + - add_contract.current.priorityOverride == "level1" + when: version.current.version is version('3.3', '>=') + +# # QUERY NON-EXISTING CONTRACT +- name: Query non-existing contract (check_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: non-existing-contract + check_mode: true + ignore_errors: true + register: cm_query_non_contract + +- name: Query non-existing contract (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: non-existing-contract + ignore_errors: true + register: nm_query_non_contract + +- name: Verify query_non_contract + assert: + that: + - cm_query_non_contract is not changed + - nm_query_non_contract is not changed + - nm_query_non_contract == cm_query_non_contract + - cm_query_non_contract.msg == nm_query_non_contract.msg == "Provided contract 'non-existing-contract' does not exist. Existing contracts{{':'}} Contract4, Contract5, Contract-6" + when: version.current.version is version('3.3', '<') + +- name: Verify query_non_contract when version greater than 3.3 + assert: + that: + - cm_query_non_contract.msg == nm_query_non_contract.msg == "Provided contract 'non-existing-contract' does not exist. Existing contracts{{':'}} Contract4, Contract5, Contract-6, Contract1" + when: version.current.version is version('3.3', '>=') + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for contrct (check_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + template: Template 1 + schema: non-existing-schema + check_mode: true + ignore_errors: true + register: cm_query_non_schema + +- name: Non-existing schema for contrct (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + template: Template 1 + schema: non-existing-schema + ignore_errors: true + register: nm_query_non_schema + + +- name: Verify non_existing_schema + assert: + that: + - cm_query_non_schema is not changed + - nm_query_non_schema is not changed + - cm_query_non_schema == nm_query_non_schema + - cm_query_non_schema.msg == nm_query_non_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for contract (check_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + check_mode: true + ignore_errors: true + register: cm_query_non_template + +- name: Non-existing template for contract (normal_mode) + mso_schema_template_contract_filter: + <<: *Contract_query + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + ignore_errors: true + register: nm_query_non_template + +- name: Verify non_existing_template + assert: + that: + - cm_query_non_template is not changed + - nm_query_non_template is not changed + - cm_query_non_template == nm_query_non_template + - cm_query_non_template.msg == nm_query_non_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml new file mode 100644 index 000000000..8bc3ce625 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml @@ -0,0 +1,744 @@ +# Test code for the MSO modules +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove schema 2 + mso_schema: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + state: absent + +- name: Remove schema 1 + mso_schema: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: absent + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *schema_present + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure Filter1 exist + cisco.mso.mso_schema_template_filter_entry: &filter_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1Entry + state: present + +- name: Ensure Filter2 exist + cisco.mso.mso_schema_template_filter_entry: + <<: *filter_present + filter: Filter2 + entry: Filter2Entry + state: present + +- name: Ensure Filter3 exist + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + filter: Filter3 + entry: Filter3Entry + state: present + +- name: Ensure Contract_1 contract exist + mso_schema_template_contract_filter: &contract_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + state: present + +- name: Ensure Contract_2 contract exist + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + contract: Contract2 + filter: Filter3 + state: present + +- name: Ensure SG_1 service graph exist + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + service_graph: SG1 + display_name: sg1 + service_nodes: + - type: firewall + - type: load-balancer + state: present + +- name: Ensure SG_2 service graph exist + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + service_graph: SG2 + display_name: sg2 + service_nodes: + - type: firewall + - type: load-balancer + state: present + +- name: Ensure VRF_1 vrf exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + layer3_multicast: true + state: present + +- name: Ensure BD_1 bd exist + mso_schema_template_bd: &bd_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: BD1 + vrf: + name: VRF1 + state: present + +- name: Ensure BD_2 bd exist + mso_schema_template_bd: + <<: *bd_present + bd: BD2 + state: present + +- name: Ensure BD_3 bd exist + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + bd: BD3 + vrf: + name: VRF1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + +- name: Ensure AP1 in Template 1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + state: present + +- name: Ensure AP1 in Template 1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: AP2 + state: present + +- name: Ensure EPG1 in AP1 in Template 1 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + bd: + name: BD1 + state: present + +- name: Ensure EPG2 in AP1 in Template 1 exists + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: AP2 + epg: EPG2 + bd: + name: BD3 + state: present + +- name: Add Contract1 to EPG1 provider + mso_schema_template_anp_epg_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + contract: + name: Contract1 + type: provider + state: present + +- name: Add Contract1 to EPG1 consumer + mso_schema_template_anp_epg_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: AP1 + epg: EPG1 + contract: + name: Contract1 + type: consumer + state: present + +- name: Add Contract2 to EPG2 provider + mso_schema_template_anp_epg_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: AP2 + epg: EPG2 + contract: + name: Contract2 + type: provider + state: present + +- name: Add Contract2 to EPG2 consumer + mso_schema_template_anp_epg_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: AP2 + epg: EPG2 + contract: + name: Contract2 + type: consumer + state: present + +# TESTS + +- name: Add service graph 1 to Contract1 (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD1 + provider: BD2 + - consumer: BD1 + provider: BD2 + state: present + check_mode: true + register: cm_add_sg1_to_c1 + +- name: Add service graph 1 to Contract1 + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD1 + provider: BD2 + - consumer: BD1 + provider: BD2 + state: present + register: nm_add_sg1_to_c1 + +- name: Verify service graph 1 is added to Contract1 + assert: + that: + - cm_add_sg1_to_c1 is changed + - cm_add_sg1_to_c1.previous == {} + - cm_add_sg1_to_c1.current.serviceGraphRef.serviceGraphName == "SG1" + - cm_add_sg1_to_c1.current.serviceGraphRef.templateName == "Template1" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - cm_add_sg1_to_c1.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + - nm_add_sg1_to_c1 is changed + - nm_add_sg1_to_c1.previous == {} + - nm_add_sg1_to_c1.current.serviceGraphRef.serviceGraphName == "SG1" + - nm_add_sg1_to_c1.current.serviceGraphRef.templateName == "Template1" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - nm_add_sg1_to_c1.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + +- name: Add service graph 1 to Contract1 again (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD1 + provider: BD2 + - consumer: BD1 + provider: BD2 + state: present + check_mode: true + register: cm_add_sg1_to_c1_again + +- name: Add service graph 1 to Contract1 again + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD1 + provider: BD2 + - consumer: BD1 + provider: BD2 + state: present + register: nm_add_sg1_to_c1_again + +- name: Verify service graph 1 is added to Contract1 again + assert: + that: + - cm_add_sg1_to_c1_again is not changed + - cm_add_sg1_to_c1_again.previous.serviceGraphRef.serviceGraphName == "SG1" + - cm_add_sg1_to_c1_again.previous.serviceGraphRef.templateName == "Template1" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + - nm_add_sg1_to_c1_again is not changed + - nm_add_sg1_to_c1_again.previous.serviceGraphRef.serviceGraphName == "SG1" + - nm_add_sg1_to_c1_again.previous.serviceGraphRef.templateName == "Template1" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + +- name: Change service graph 1 node to Contract1 (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD2 + provider: BD1 + - consumer: BD2 + provider: BD1 + state: present + check_mode: true + register: cm_change_sg1_to_c1 + +- name: Change service graph 1 node to Contract1 + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD2 + provider: BD1 + - consumer: BD2 + provider: BD1 + state: present + register: nm_change_sg1_to_c1 + +- name: Verify service graph 1 is added to Contract1 again + assert: + that: + - cm_change_sg1_to_c1 is changed + - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2" + - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1" + - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2" + - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_change_sg1_to_c1 is changed + - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2" + - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1" + - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2" + - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general" + +- name: Query service graph 1 to Contract1 (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + state: query + register: cm_query_sg1_to_c1 + +- name: Query service graph 1 to Contract1 + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + state: query + register: nm_query_sg1_to_c1 + +- name: Verify queried service graph 1 + assert: + that: + - cm_query_sg1_to_c1 is not changed + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_query_sg1_to_c1 is not changed + - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1" + - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + +- name: Remove service graph 1 from Contract1 (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + state: absent + check_mode: true + register: cm_remove_sg1_from_c1 + +- name: Remove service graph 1 from Contract1 + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + state: absent + register: nm_remove_sg1_from_c1 + +- name: Verify service graph 1 is removed from Contract1 + assert: + that: + - cm_remove_sg1_from_c1 is changed + - cm_remove_sg1_from_c1.current == {} + - cm_remove_sg1_from_c1.previous.serviceGraphRef.serviceGraphName == "SG1" + - cm_remove_sg1_from_c1.previous.serviceGraphRef.templateName == "Template1" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + - nm_remove_sg1_from_c1 is changed + - nm_remove_sg1_from_c1.current == {} + - nm_remove_sg1_from_c1.previous.serviceGraphRef.serviceGraphName == "SG1" + - nm_remove_sg1_from_c1.previous.serviceGraphRef.templateName == "Template1" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + +- name: Add service graph 2 to Contract2 with BD in other schema (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + contract: Contract2 + service_graph: SG2 + service_nodes: + - consumer: BD1 + consumer_template: Template 1 + consumer_schema: '{{ mso_schema | default("ansible_test") }}' + provider: BD3 + - consumer: BD3 + provider: BD2 + provider_template: Template 1 + provider_schema: '{{ mso_schema | default("ansible_test") }}' + state: present + check_mode: true + register: cm_add_sg2_to_c2 + +- name: Add service graph 2 to Contract2 with BD in other schema + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + contract: Contract2 + service_graph: SG2 + service_nodes: + - consumer: BD1 + consumer_template: Template 1 + consumer_schema: '{{ mso_schema | default("ansible_test") }}' + provider: BD3 + - consumer: BD3 + provider: BD2 + provider_template: Template 1 + provider_schema: '{{ mso_schema | default("ansible_test") }}' + state: present + register: nm_add_sg2_to_c2 + +- name: Verify service graph 2 is added to Contract2 + assert: + that: + - cm_add_sg2_to_c2 is changed + - cm_add_sg2_to_c2.previous == {} + - cm_add_sg2_to_c2.current.serviceGraphRef.serviceGraphName == "SG2" + - cm_add_sg2_to_c2.current.serviceGraphRef.templateName == "Template1" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG2" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - cm_add_sg2_to_c2.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + - nm_add_sg2_to_c2 is changed + - nm_add_sg2_to_c2.previous == {} + - nm_add_sg2_to_c2.current.serviceGraphRef.serviceGraphName == "SG2" + - nm_add_sg2_to_c2.current.serviceGraphRef.templateName == "Template1" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG2" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall" + - nm_add_sg2_to_c2.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer" + +- name: Query service graph 2 to Contract2 (check_mode) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + contract: Contract2 + service_graph: SG2 + state: query + register: cm_query_sg2_to_c2 + +- name: Query service graph 2 to Contract2 + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + contract: Contract2 + service_graph: SG2 + state: query + register: nm_query_sg2_to_c2 + +- name: Verify queried service graph 1 + assert: + that: + - cm_query_sg2_to_c2 is not changed + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.schemaId != cm_query_sg2_to_c2.current.serviceGraphRef.schemaId + - cm_query_sg2_to_c2 is not changed + - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general" + - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3" + - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1" + - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general" + - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.schemaId != nm_query_sg2_to_c2.current.serviceGraphRef.schemaId + +# NOT EXISTING INPUT + +- name: Not existing template provided for absent + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template False + contract: Contract1 + service_graph: SG1 + state: absent + ignore_errors: true + register: not_existing_template_input_absent + +- name: Not existing contract provided for absent + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract False + service_graph: SG1 + state: absent + ignore_errors: true + register: not_existing_contract_input_absent + +- name: Not existing service graph provided for absent + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG False + state: absent + register: not_existing_service_graph_input_absent + +- name: Not existing service graph provided for query + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG False + state: query + register: not_existing_service_graph_input_query + +- name: Not existing template provided for present + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_graph_template: Template False + service_nodes: + - consumer: BD2 + provider: BD1 + - consumer: BD2 + provider: BD1 + state: present + ignore_errors: true + register: not_existing_template_input_present + +- name: Not existing service graph provided for present + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG False + service_graph_template: Template 2 + service_nodes: + - consumer: BD2 + provider: BD1 + - consumer: BD2 + provider: BD1 + state: present + ignore_errors: true + register: not_existing_service_graph_input_present + +- name: Verify non_existing_input + assert: + that: + - not_existing_template_input_absent is not changed + - not_existing_template_input_absent.msg.startswith("Provided template") + - not_existing_contract_input_absent is not changed + - not_existing_contract_input_absent.msg.startswith("Provided contract") + - not_existing_service_graph_input_absent is not changed + - not_existing_service_graph_input_query is not changed + - not_existing_template_input_present is not changed + - not_existing_template_input_present.msg.startswith("Provided template") + - not_existing_service_graph_input_present is not changed + - not_existing_service_graph_input_present.msg.startswith("Provided service graph") + +# False input + +- name: False service graph node amount provided ( less than 2 as provided in SG1 config ) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD1 + provider: BD2 + state: present + ignore_errors: true + register: nm_false_sg1_to_c1_less_than_2 + +- name: False service graph node amount provided ( more than 2 as provided in SG1 config ) + mso_schema_template_contract_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + service_graph: SG1 + service_nodes: + - consumer: BD1 + provider: BD2 + - consumer: BD1 + provider: BD2 + - consumer: BD1 + provider: BD2 + state: present + ignore_errors: true + register: nm_false_sg1_to_c1_more_than_2 + +- name: Verify false_sg1_to_c1 + assert: + that: + - nm_false_sg1_to_c1_less_than_2 is not changed + - nm_false_sg1_to_c1_less_than_2.msg.startswith("Not enough service nodes defined") + - nm_false_sg1_to_c1_more_than_2 is not changed + - nm_false_sg1_to_c1_more_than_2.msg.startswith("Too many service nodes defined") diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml new file mode 100644 index 000000000..58f043256 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml @@ -0,0 +1,237 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +# mso_schema_template_deploy is deprecated in MSO/NDO v4.0+, different api endpoint thus different module +- name: Execute tasks only for MSO version < 4.0 + when: version.current.version is version('4.0', '<') + block: + - name: Ensure site exist + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + + - name: Undeploy template + cisco.mso.mso_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + - Template 5 + - Template5 + + - name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + + - name: Ensure tenant ansible_test exists + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + + - name: Ensure schema 1 with Template 1, and Template 2, Template 3 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + + - name: Add physical site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + + - name: Deploy templates (check_mode) + cisco.mso.mso_schema_template_deploy: &schema_deploy + <<: *mso_info + schema: ansible_test + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + check_mode: true + register: cm_deploy_template + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + + - name: Verify cm_deploy_template + ansible.builtin.assert: + that: + - item is not changed + loop: "{{ cm_deploy_template.results }}" + + - name: Deploy templates (normal_mode) + cisco.mso.mso_schema_template_deploy: + <<: *schema_deploy + schema: ansible_test + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + register: nm_deploy_template + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + + - name: Verify nm_deploy_template + ansible.builtin.assert: + that: + - item is not changed + - item.msg == "Successfully deployed" + loop: "{{ nm_deploy_template.results }}" + + - name: Get deployment status + cisco.mso.mso_schema_template_deploy: + <<: *schema_deploy + schema: ansible_test + template: "{{ item }}" + state: status + register: query_deploy_status + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + + - name: Verify query_deploy_status + ansible.builtin.assert: + that: + - item is not changed + - item.status.0.status.siteStatus == "Succeeded" + loop: "{{ query_deploy_status.results }}" + + - name: Undeploy templates + cisco.mso.mso_schema_template_deploy: + <<: *schema_deploy + schema: ansible_test + template: '{{ item }}' + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + register: undeploy_template + loop: + - Template 1 + - Template 2 + - Template 3 + - Template 4 + - Template_5 + + - name: Verify undeploy_template + ansible.builtin.assert: + that: + - item is not changed + - item.msg == "Successfully Un-deployed" + loop: "{{ undeploy_template.results }}" + when: version.current.version is version('3.1', '>=') + + - name: Verify undeploy_template + ansible.builtin.assert: + that: + - item is not changed + - item.msg == "Successfully deployed" + loop: "{{ undeploy_template.results }}" + when: version.current.version is version('3.1', '<') + + # Validate schema when MSO version >= 3.3 + - name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + - name: Add VRF1 with validation error + cisco.mso.mso_schema_template_vrf: &fail_validation + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF1 + layer3_multicast: true + vzany: true + state: present + + - name: Deploy template with validation error + cisco.mso.mso_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 2 + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + register: failed_validaton_deploy + ignore_errors: true + + - name: Verify validation errors before deploy and redploy + ansible.builtin.assert: + that: + - failed_validaton_deploy.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF1 exception while trying to update schema" + + - name: Remove VRF1 with validation error + cisco.mso.mso_schema_template_vrf: + <<: *fail_validation + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml new file mode 100644 index 000000000..fce5cf8e8 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml @@ -0,0 +1,627 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3[0].txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exist + mso_site: &site_present + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure aws site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Undeploy templates if deployed to clean the environment before ndo 4.0 + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + loop: + - Template 1 + - Template 2 + when: version.current.version is version('4.0', '<') + +#- name: Undeploy templates if deployed to clean the environment after ndo 4.0 +# <TBD>: +# <<: *mso_info +# schema: '{{ mso_schema | default("ansible_test") }}' +# template: "{{ item }}" +# site: '{{ mso_site | default("ansible_test") }}' +# state: undeploy +# ignore_errors: true +# loop: +# - Template 1 +# - Template 2 +# when: version.current.version is version('4.0', '>=') + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1, Template 2 + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Add a new site to a schema with Template 1, Template 2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Ensure VRF1 exists on Template1 + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + +- name: Ensure VRF2 exists on Template2 + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF2 + state: present + +- name: Ensure ANP exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + +- name: Ensure ANP2 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + anp: ANP2 + state: present + +- name: Check deployment status of Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: status1_temp1 + +- name: Check deployment status of Template 2 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + state: query + register: status1_temp2 + +- name: Verify status after adding VRFs and ANPs + assert: + that: + - status1_temp1.current[0].anps[0].state == 'created' + - status1_temp1.current[0].vrfs[0].state == 'created' + - status1_temp2.current[0].anps[0].state == 'created' + - status1_temp2.current[0].vrfs[0].state == 'created' + +- name: Check deployment status by querying site + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: status_site + +- name: Check deployment status by querying site and Template1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: status_site_temp1 + +- name: Check deployment status by querying site and Template2 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: status_site_temp2 + +- name: Verify status after querying site before deployment + assert: + that: + - status_site.current | length == 2 + - status_site_temp1.current.anps[0].state == 'created' + - status_site_temp1.current.vrfs[0].state == 'created' + - status_site_temp2.current.anps[0].state == 'created' + - status_site_temp2.current.vrfs[0].state == 'created' + + +- name: Ensure ansible_test_1 BD exists + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + vrf: + name: VRF1 + template: Template 1 + state: present + +- name: Ensure ansible_test_2 BD exists + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + bd: ansible_test_2 + vrf: + name: VRF2 + template: Template 2 + state: present + +- name: Add EPG to Template 1 + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + bd: + name: ansible_test_1 + vrf: + name: VRF1 + state: present + +- name: Add EPG to Template 2 + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + anp: ANP2 + epg: ansible_test_2 + bd: + name: ansible_test_2 + vrf: + name: VRF2 + state: present + +- name: Check deployment status of Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: status2_temp1 + +- name: Check deployment status of Template 2 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + state: query + register: status2_temp2 + +- name: Verify status after adding BDs and EPGs + assert: + that: + - status2_temp1.current[0].bds[0].state == 'created' + - status2_temp1.current[0].anps[0].epgs[0].state == 'created' + - status2_temp2.current[0].bds[0].state == 'created' + - status2_temp2.current[0].anps[0].epgs[0].state == 'created' + +- name: Add VRF3 exists to Template1 + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF3 + state: present + +- name: Add ansible_test_3 BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_3 + vrf: + name: VRF3 + template: Template 1 + state: present + +- name: Check deployment status of Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: status3_temp1 + +- name: Verify status after adding new BD and VRF and changing EPG1 + assert: + that: + - status3_temp1.current[0].bds[0].state == 'created' + - status3_temp1.current[0].vrfs[0].state == 'created' + +# mso_schema_template_deploy is deprecated in MSO/NDO v4.0+, different api endpoint thus different module +# when new module created, remove block and do execution for each mso_schema_template_deploy tasks +- name: Execute tasks only for MSO version < 4.0 + when: version.current.version is version('4.0', '<') + block: + - name: Deploy templates + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + register: cm_deploy_template + loop: + - Template 1 + - Template 2 + + - name: Change EPG + mso_schema_template_anp_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + epg: ansible_test_1 + bd: + name: ansible_test_3 + vrf: + name: VRF3 + state: present + + - name: Check deployment status of Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: status_change_temp1 + + - name: Verify status after changing EPG + assert: + that: + - status_change_temp1.current[0].anps[0].epgs[0].state == 'modified' + + - name: Delete ansible_test_1 BD + mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + bd: ansible_test_1 + vrf: + name: VRF1 + template: Template 1 + state: absent + + - name: Delete VRF1 + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: absent + + - name: Check deployment status of Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: status4_temp1 + + - name: Verify status after deleting VRF1 + assert: + that: + - status4_temp1.current[0].bds[0].state == 'deleted' + - status4_temp1.current[0].vrfs[0].state == 'deleted' + + - name: Try deploy and check results + block: + - name: Deploy templates Template 1 + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + + - name: Check deployment status of Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: status5_temp1 + + - name: Increment the retry count + set_fact: + retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}" + + rescue: + - fail: + msg: Status5_temp1 correct value retrieved continuing + when: + - status5_temp1.current[0] is defined + - status5_temp1.current[0].anps == [] + + - fail: + msg: Maximum retries of deploy and check group for status5_temp1 reached + when: retry_count | int == 10 + + - debug: + msg: "Deploy and check group for status5_temp1 failed, let's give it another shot" + + - name: Reset the retry count + set_fact: + retry_count: + + - name: Try deploy and check results + block: + - name: Deploy templates Template 2 + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + + - name: Check deployment status of Template 2 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + state: query + register: status5_temp2 + + - name: Increment the retry count + set_fact: + retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}" + + rescue: + - fail: + msg: status5_temp2 correct value retrieved continuing + when: + - status5_temp2.current[0] is defined + - status5_temp2.current[0].anps == [] + + - fail: + msg: Maximum retries of deploy and check group for status5_temp2 reached + when: retry_count | int == 10 + + - debug: + msg: "Deploy and check group for status5_temp2 failed, let's give it another shot" + + - name: Verify status after deploying Templates to site + assert: + that: + - status5_temp1.current[0].anps == [] + - status5_temp1.current[0].bds == [] + - status5_temp1.current[0].vrfs == [] + - status5_temp2.current[0].anps == [] + - status5_temp2.current[0].bds == [] + - status5_temp2.current[0].vrfs == [] + + - name: Check status of all templates + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + register: all_templates + + - name: Verify all + assert: + that: + - all_templates.current.policyStates | length == 2 + + - name: Check deployment status by querying site + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: status2_site + + - name: Reset the retry count + set_fact: + retry_count: + + - name: Try deploy and check results for a site and Template1 + block: + - name: Deploy templates Template1 + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + + - name: Check deployment status by querying site and Template1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: status2_site_temp1 + + - name: Increment the retry count + set_fact: + retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}" + + rescue: + - fail: + msg: status2_site_temp1 correct value retrieved continuing + when: + - status2_site_temp1.current is defined + - status2_site_temp1.current.anps == [] + + - fail: + msg: Maximum retries of deploy and check group for status2_site_temp1 reached + when: retry_count | int == 10 + + - debug: + msg: "Deploy and check group for status2_site_temp1 failed, let's give it another shot" + + - name: Try deploy and check results for a site and Template2 + block: + - name: Deploy templates Template2 + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + + - name: Check deployment status by querying site and Template2 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template2 + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: status2_site_temp2 + + - name: Increment the retry count + set_fact: + retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}" + + rescue: + - fail: + msg: status2_site_temp2 correct value retrieved continuing + when: + - status2_site_temp2.current is defined + - status2_site_temp2.current.anps == [] + + - fail: + msg: Maximum retries of deploy and check group for status2_site_temp2 reached + when: retry_count | int == 10 + + - debug: + msg: "Deploy and check group for status2_site_temp2 failed, let's give it another shot" + + - name: Verify status after querying site post deployment + assert: + that: + - status2_site.current | length == 2 + - status2_site_temp1.current.anps == [] + - status2_site_temp1.current.bds == [] + - status2_site_temp1.current.vrfs == [] + - status2_site_temp2.current.anps == [] + - status2_site_temp2.current.bds == [] + - status2_site_temp2.current.vrfs == [] + + - name: Check deployment status by querying site and non associated Template 1 + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + site: 'aws_{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: status_site_temp3 + + - name: Verify status after querying site with non associated Template 1 + assert: + that: + - status_site_temp3.msg == "Provided Template 'Template1' not associated with Site 'aws_ansible_test'." + + - name: Check Non-existing schema + mso_schema_template_deploy_status: + <<: *mso_info + schema: non-existing-schema + state: query + ignore_errors: true + register: non_schema + + - name: Verify non_existing_schema + assert: + that: + - non_schema.msg == "Schema 'non-existing-schema' not found." + + - name: Check deployment status of non-existing-template + mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + state: query + ignore_errors: true + register: non_temp + + - name: Verify non_existing_template + assert: + that: + - non_temp.msg == "Template 'non-existing-template' not found."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml new file mode 100644 index 000000000..e76869456 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml @@ -0,0 +1,1158 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# - name: Ensure site exist +# mso_site: &site_present +# host: '{{ mso_hostname }}' +# username: '{{ mso_username }}' +# password: '{{ mso_password }}' +# validate_certs: '{{ mso_validate_certs | default(false) }}' +# use_ssl: '{{ mso_use_ssl | default(true) }}' +# use_proxy: '{{ mso_use_proxy | default(true) }}' +# output_level: '{{ mso_output_level | default("info") }}' +# site: '{{ mso_site | default("ansible_test") }}' +# apic_username: '{{ apic_username }}' +# apic_password: '{{ apic_password }}' +# apic_site_id: '{{ apic_site_id | default(101) }}' +# urls: +# - https://{{ apic_hostname }} +# state: present + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Undeploy templates if deployed from previous test case + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + loop: + - Template 1 + - Template 2 + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure Filter 1 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1-Entry + state: present + +- name: Ensure Contract1 exist + mso_schema_template_contract_filter: &contract_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Filter 2 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + filter: Filter2 + entry: Filter2-Entry + state: present + +- name: Ensure Contract2 exist + mso_schema_template_contract_filter: &contract2_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + contract: Contract2 + filter: Filter2 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 2 + state: present + +- name: Ensure VRF exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + #layer3_multicast: true + state: present + +- name: Ensure VRF2 exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: present + +- name: Ensure VRF3 exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF3 + state: present + +- name: Ensure VRF4 exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF4 + state: present + +- name: Ensure L3out exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: L3out + vrf: + name: VRF + state: present + +- name: Ensure L3out2 exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: L3out2 + vrf: + name: VRF2 + state: present + +- name: Ensure L3out3 exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + l3out: L3out3 + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + state: present + +- name: Ensure L3out4 exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + l3out: L3out4 + vrf: + name: VRF4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + state: present + +- name: Ensure ANP exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP1 + state: present + +- name: Ensure ANP2 exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP2 + state: present + +- name: Ensure ANP3 exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP3 + state: present + +- name: Ensure ANP4 exist + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + anp: ANP4 + state: present + +- name: Ensure ansible_test_1 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: absent + +- name: Ensure ansible_test_2 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_2 + state: absent + +- name: Ensure ansible_test_3 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + external_epg: ansible_test_3 + state: absent + +- name: Ensure ansible_test_4 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + state: absent + +- name: Ensure ansible_test_6 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_6 + state: absent + +- name: Ensure ansible_test_7 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_7 + state: absent + +# ADD external EPG +- name: Add external EPG (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF + state: present + check_mode: true + register: cm_add_epg + +- name: Verify cm_add_epg + assert: + that: + - cm_add_epg is changed + - cm_add_epg.previous == {} + - cm_add_epg.current.name == "ansible_test_1" + - cm_add_epg.current.vrfRef.templateName == "Template1" + - cm_add_epg.current.vrfRef.vrfName == "VRF" + +- name: Add external EPG (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF + state: present + register: nm_add_epg + +- name: Verify nm_add_epg + assert: + that: + - nm_add_epg is changed + - nm_add_epg.previous == {} + - nm_add_epg.current.name == "ansible_test_1" + - nm_add_epg.current.vrfRef.templateName == "Template1" + - nm_add_epg.current.vrfRef.vrfName == "VRF" + - cm_add_epg.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId + +- name: Add external EPG again (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + check_mode: true + register: cm_add_epg_again + +- name: Verify cm_add_epg_again + assert: + that: + - cm_add_epg_again is not changed + - cm_add_epg_again.previous.name == "ansible_test_1" + - cm_add_epg_again.current.name == "ansible_test_1" + - cm_add_epg_again.previous.vrfRef.templateName == "Template1" + - cm_add_epg_again.current.vrfRef.templateName == "Template1" + - cm_add_epg_again.previous.vrfRef.vrfName == "VRF" + - cm_add_epg_again.current.vrfRef.vrfName == "VRF" + - cm_add_epg_again.previous.vrfRef.schemaId == cm_add_epg_again.current.vrfRef.schemaId + + +- name: Add epg again (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_again + +- name: Verify nm_add_epg_again + assert: + that: + - nm_add_epg_again is not changed + - nm_add_epg_again.previous.name == "ansible_test_1" + - nm_add_epg_again.current.name == "ansible_test_1" + - nm_add_epg_again.previous.vrfRef.templateName == "Template1" + - nm_add_epg_again.current.vrfRef.templateName == "Template1" + - nm_add_epg_again.previous.vrfRef.vrfName == "VRF" + - nm_add_epg_again.current.vrfRef.vrfName == "VRF" + - nm_add_epg_again.previous.vrfRef.schemaId == nm_add_epg_again.current.vrfRef.schemaId + +- name: Add external EPG 2(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_2 + vrf: + name: VRF3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + l3out: + name: L3out3 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + state: present + register: nm_add_epg_2 + +- name: Add external EPG 3 (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + external_epg: ansible_test_3 + vrf: + name: VRF4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + l3out: + name: L3out4 + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + state: present + register: nm_add_epg_3 + +- name: Add external EPG 4 (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + vrf: + name: VRF + state: present + register: nm_add_epg_4 + +- name: Verify nm_add_epg_2 and nm_add_epg_3 + assert: + that: + - nm_add_epg_2 is changed + - nm_add_epg_3 is changed + - nm_add_epg_2.current.name == "ansible_test_2" + - nm_add_epg_3.current.name == "ansible_test_3" + - nm_add_epg_2.current.vrfRef.templateName == "Template2" + - nm_add_epg_3.current.vrfRef.templateName == "Template3" + - nm_add_epg_2.current.vrfRef.vrfName == "VRF3" + - nm_add_epg_3.current.vrfRef.vrfName == "VRF4" + - nm_add_epg_2.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId + - nm_add_epg_3.current.vrfRef.schemaId != nm_add_epg.current.vrfRef.schemaId + +- name: Add external EPG 5 (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_5 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: + name: ANP1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_5 + +- name: Verify nm_add_epg_5 + assert: + that: + - nm_add_epg_5 is changed + - nm_add_epg_5.current.name == "ansible_test_5" + +- name: Add external EPG 5 again with L3Out (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_5 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: + name: L3out + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: + name: ANP1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_5_again + +- name: Verify nm_add_epg_5_again + assert: + that: + - nm_add_epg_5_again is changed + - nm_add_epg_5_again.current.name == "ansible_test_5" + +- name: Add external EPG 6 with external epg type cloud (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_6 + type: cloud + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: + name: ANP1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_6 + +- name: Verify nm_add_epg_6 + assert: + that: + - nm_add_epg_6 is changed + - nm_add_epg_6.current.name == "ansible_test_6" + - nm_add_epg_6.current.vrfRef.templateName == "Template1" + - nm_add_epg_6.current.vrfRef.vrfName == "VRF" + - nm_add_epg_6.current.anpRef.anpName == "ANP1" + +- name: Add external EPG 6 with external epg type cloud again(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_6 + type: cloud + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: + name: ANP1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_6_again + +- name: Verify nm_add_epg_6_again + assert: + that: + - nm_add_epg_6_again is not changed + - nm_add_epg_6_again.current.name == "ansible_test_6" + +- name: Add external EPG 6 with external epg type cloud with modification(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_6 + type: cloud + vrf: + name: VRF2 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: + name: ANP1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_6_again_2 + +- name: Verify nm_add_epg_6_again + assert: + that: + - nm_add_epg_6_again_2 is changed + - nm_add_epg_6_again_2.current.name == "ansible_test_6" + - nm_add_epg_6_again_2.current.vrfRef.vrfName == "VRF2" + - nm_add_epg_6_again_2.current.anpRef.anpName == "ANP1" + +- name: Add external EPG 7 with external epg type on-premise explicitly mentioned again(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_7 + type: on-premise + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: + name: L3out + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_7 + +- name: Verify nm_add_epg_7 + assert: + that: + - nm_add_epg_7 is changed + - nm_add_epg_7.current.name == "ansible_test_7" + - nm_add_epg_7.current.vrfRef.templateName == "Template1" + - nm_add_epg_7.current.vrfRef.vrfName == "VRF" + +- name: Add external EPG 7 with external epg type not mentioned again(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_7 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: + name: L3out + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_7_again + +- name: Verify nm_add_epg_7_again + assert: + that: + - nm_add_epg_7_again is not changed + - nm_add_epg_7_again.current.name == "ansible_test_7" + +# CHANGE external EPG +- name: Change epg from different template (check_mode) + mso_schema_template_external_epg: &change_epg_template + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_2 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + check_mode: true + register: cm_change_epg + +- name: Verify cm_change_epg from different template to own template + assert: + that: + - cm_change_epg is changed + - cm_change_epg.current.name == 'ansible_test_2' + - cm_change_epg.current.vrfRef.vrfName == 'VRF' + - cm_change_epg.current.vrfRef.templateName == "Template1" + - cm_change_epg.current.vrfRef.schemaId == cm_change_epg.previous.vrfRef.schemaId + +- name: Change epg from different template to own template (normal_mode) + mso_schema_template_external_epg: + <<: *change_epg_template + state: present + register: nm_change_epg + +- name: Verify nm_change_epg from different template to own template + assert: + that: + - nm_change_epg is changed + - nm_change_epg.current.name == 'ansible_test_2' + - nm_change_epg.current.vrfRef.vrfName == 'VRF' + - nm_change_epg.current.vrfRef.templateName == "Template1" + - nm_change_epg.current.vrfRef.schemaId == nm_change_epg.previous.vrfRef.schemaId + +- name: Change epg again from different template (check_mode) + mso_schema_template_external_epg: + <<: *change_epg_template + check_mode: true + register: cm_change_epg_again + +- name: Verify cm_change_epg_again + assert: + that: + - cm_change_epg_again is not changed + - cm_change_epg_again.current.name == 'ansible_test_2' + - cm_change_epg_again.current.vrfRef.vrfName == 'VRF' + - cm_change_epg_again.current.vrfRef.templateName == "Template1" + - cm_change_epg_again.current.vrfRef.schemaId == cm_change_epg_again.previous.vrfRef.schemaId + +- name: Change epg again from different template (normal_mode) + mso_schema_template_external_epg: + <<: *change_epg_template + state: present + register: nm_change_epg_again + +- name: Verify nm_change_epg_again from different template to own template + assert: + that: + - nm_change_epg_again is not changed + - nm_change_epg_again.current.name == 'ansible_test_2' + - nm_change_epg_again.current.vrfRef.vrfName == 'VRF' + - nm_change_epg_again.current.vrfRef.templateName == "Template1" + - nm_change_epg_again.current.vrfRef.schemaId == nm_change_epg_again.previous.vrfRef.schemaId + +- name: Change VRF from different schema (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + external_epg: ansible_test_3 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_change_epg_vrf4 + +- name: Verify nm_change_epg_vrf4 and nm_change_epg_vrf2 + assert: + that: + - nm_change_epg_vrf4 is changed + - nm_change_epg_vrf4.current.name == 'ansible_test_3' + - nm_change_epg_vrf4.current.vrfRef.vrfName == 'VRF' + - nm_change_epg_vrf4.current.vrfRef.templateName == "Template1" + - nm_change_epg_vrf4.current.vrfRef.schemaId != nm_change_epg_vrf4.previous.vrfRef.schemaId + +- name: Change epg 1 l3out(normal mode) + mso_schema_template_external_epg: &change_l3out + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF2 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: + name: L3out2 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_change_epg_1_l3out + +- name: Change epg 1 settings(normal mode) + mso_schema_template_external_epg: + <<: *change_l3out + vrf: + name: VRF + l3out: + name: L3out + state: present + register: nm_change_epg_1_settings + +- name: Verify nm_change_epg_1_settings and nm_change_epg_1_l3out + assert: + that: + - nm_change_epg_1_settings is changed + - nm_change_epg_1_settings.previous.vrfRef.vrfName == 'VRF2' + - nm_change_epg_1_settings.previous.vrfRef.templateName == 'Template1' + - nm_change_epg_1_settings.current.vrfRef.vrfName == 'VRF' + - nm_change_epg_1_settings.current.vrfRef.templateName == 'Template1' + - nm_change_epg_1_settings.current.l3outRef.l3outName == 'L3out' + - nm_change_epg_1_settings.current.l3outRef.templateName == 'Template1' + - nm_change_epg_1_settings.previous.l3outRef.schemaId == nm_change_epg_1_settings.current.l3outRef.schemaId + - nm_change_epg_1_l3out is changed + - nm_change_epg_1_l3out.previous.vrfRef.vrfName == 'VRF' + - nm_change_epg_1_l3out.previous.vrfRef.templateName == 'Template1' + - nm_change_epg_1_l3out.current.vrfRef.vrfName == 'VRF2' + - nm_change_epg_1_l3out.current.vrfRef.templateName == 'Template1' + - nm_change_epg_1_l3out.current.l3outRef.l3outName == 'L3out2' + - nm_change_epg_1_l3out.current.l3outRef.templateName == 'Template1' + +- name: Change epg 4 preferredGroup(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + vrf: + name: VRF + preferred_group: true + state: present + register: nm_change_epg_4_preferred_group + +- name: Change epg 4 preferredGroup again(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + vrf: + name: VRF + preferred_group: false + state: present + register: nm_change_epg_4_preferred_group_again + +- name: Verify nm_change_epg_4_preferred_group and nm_change_epg_4_preferred_group_again + assert: + that: + - nm_change_epg_4_preferred_group is changed + - nm_change_epg_4_preferred_group_again is changed + - nm_change_epg_4_preferred_group.current.preferredGroup == true + - nm_change_epg_4_preferred_group_again.current.preferredGroup == false + +# QUERY ALL EPG +- name: Query all EPG (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + check_mode: true + register: cm_query_all_epgs + +- name: Query all EPG (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: nm_query_all_epgs + +- name: Verify query_all_epgs + assert: + that: + - cm_query_all_epgs is not changed + - nm_query_all_epgs is not changed + - cm_query_all_epgs.current | length == nm_query_all_epgs.current | length == 2 + +# QUERY AN EPG +- name: Query epg 1(check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + check_mode: true + register: cm_query_epg_1 + +- name: Query epg 1(normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + register: nm_query_epg_1 + +- name: Verify cm_query_epg_1 and nm_query_epg_1 + assert: + that: + - cm_query_epg_1 is not changed + - nm_query_epg_1 is not changed + - cm_query_epg_1.current.l3outRef.l3outName == 'L3out' == nm_query_epg_1.current.l3outRef.l3outName + - cm_query_epg_1.current.l3outRef.templateName == nm_query_epg_1.current.l3outRef.templateName == 'Template1' + - cm_query_epg_1.current.l3outRef.schemaId == nm_query_epg_1.current.l3outRef.schemaId + - cm_query_epg_1.current.vrfRef.vrfName == nm_query_epg_1.current.vrfRef.vrfName == 'VRF' + - cm_query_epg_1.current.vrfRef.templateName == nm_query_epg_1.current.vrfRef.templateName == 'Template1' + - cm_query_epg_1.current.vrfRef.schemaId == nm_query_epg_1.current.vrfRef.schemaId + - nm_query_epg_1.current.l3outRef.schemaId == nm_query_epg_1.current.vrfRef.schemaId + +- name: Query epg 5(normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_5 + state: query + register: nm_query_epg_5 + +- name: Verify nm_query_epg_5 + assert: + that: + - nm_query_epg_5.current.l3outRef.l3outName == 'L3out' + +- name: Query epg 6(normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: ansible_test_6 + state: query + register: nm_query_epg_6 + +- name: Verify nm_query_epg_5 + assert: + that: + - nm_add_epg_6.current.anpRef.anpName == "ANP1" + +# REMOVE EPG +- name: Remove EPG 4 (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + state: absent + check_mode: true + register: cm_remove_epg_4 + +- name: Verify cm_remove_epg_4 + assert: + that: + - cm_remove_epg_4 is changed + - cm_remove_epg_4.current == {} + +- name: Remove EPG 4 (normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + state: absent + register: nm_remove_epg_4 + +- name: Verify nm_remove_epg_4 + assert: + that: + - nm_remove_epg_4 is changed + - nm_remove_epg_4.current == {} + +- name: Remove EPG 4 again (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + state: absent + register: nm_remove_epg_4_again + +- name: Verify nm_remove_epg_4_again + assert: + that: + - nm_remove_epg_4_again is not changed + - nm_remove_epg_4_again.previous == nm_remove_epg_4_again.current == {} + +- name: Add external EPG 4 description for version greater than 3.3 + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_4 + description: "Description of an external EPG 4" + vrf: + name: VRF + state: present + register: add_epg_4 + when: version.current.version is version('3.3', '>=') + +- name: Verify add_epg_4 for version greater than 3.3 + assert: + that: + - add_epg_4 is changed + - add_epg_4.current.name == "ansible_test_4" + - add_epg_4.current.description == "Description of an external EPG 4" + - add_epg_4.current.vrfRef.templateName == "Template1" + - add_epg_4.current.vrfRef.vrfName == "VRF" + - add_epg_4.current.vrfRef.schemaId == add_epg_4.current.vrfRef.schemaId + when: version.current.version is version('3.3', '>=') + + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: non-existing-epg + state: query + ignore_errors: true + check_mode: true + register: cm_query_non_existing_epg + +- name: Query non-existing EPG (normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: non-existing-epg + state: query + ignore_errors: true + register: nm_query_non_existing_epg + +- name: Verify cm_query_non_existing_epg and nm_query_non_existing_epg + assert: + that: + - cm_query_non_existing_epg is not changed + - nm_query_non_existing_epg is not changed + - cm_query_non_existing_epg == nm_query_non_existing_epg + - cm_query_non_existing_epg.msg == nm_query_non_existing_epg.msg == "External EPG 'non-existing-epg' not found" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for epg (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for epg (normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for epg (check_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for epg (normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# Checking if contract are removed after re-applying an EPG. (#13 | #62137) +- name: Remove EPG 2/5/6/7 to avoid circle errors (normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + external_epg: '{{ item }}' + state: absent + loop: + - ansible_test_2 + - ansible_test_5 + - ansible_test_6 + - ansible_test_7 + +- name: Add Contracts to EPG 1 + mso_schema_template_external_epg_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + contract: + name: '{{ item.name }}' + template: '{{ item.template }}' + type: '{{ item.type }}' + state: present + loop: + - { name: Contract1, template: Template 1, type: consumer } + - { name: Contract1, template: Template 1, type: provider } + - { name: Contract2, template: Template 2, type: consumer } + - { name: Contract2, template: Template 2, type: provider } + +- name: Query contract EPG 1(normal mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + register: nm_query_epg1_contract + +- name: Verify nm_query_epg1_contract + assert: + that: + - nm_query_epg1_contract.current.contractRelationships | length == 4 + +- name: Add EPG 1 again (normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF + l3out: + name: L3out + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_epg_1_again + +- name: Verify that EPG 1 didn't change + assert: + that: + - nm_add_epg_1_again is not changed + +- name: Query contract EPG 1 again + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + register: nm_query_epg1_contract_again + +- name: Verify that 4 contracts are in EPG 1 using nm_query_epg1_contract_again + assert: + that: + - nm_query_epg1_contract_again.current.contractRelationships | length == 4 + +# Checking if modifying an external EPG with existing contracts throw an MSO error. (#82) +- name: Change external EPG 1 VRF (normal_mode) + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF2 + l3out: + name: L3out2 + state: present + register: nm_change_ext_epg_1_vrf + +- name: Verify that external EPG 1 did change + assert: + that: + - nm_change_ext_epg_1_vrf is changed + - nm_change_ext_epg_1_vrf.current.vrfRef.templateName == "Template1" + - nm_change_ext_epg_1_vrf.current.vrfRef.vrfName == "VRF2" + - nm_change_ext_epg_1_vrf.current.l3outRef.l3outName == "L3out2" + +- name: Query EPG 1 + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + register: nm_query_contract_ext_epg_1 + +- name: Verify that 4 contracts are in external EPG 1 using nm_query_contract_ext_epg_1 + assert: + that: + - nm_query_contract_ext_epg_1.current.contractRelationships | length == 4
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml new file mode 100644 index 000000000..44eb30623 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml @@ -0,0 +1,645 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +# - name: Ensure site exist +# mso_site: &site_present +# host: '{{ mso_hostname }}' +# username: '{{ mso_username }}' +# password: '{{ mso_password }}' +# validate_certs: '{{ mso_validate_certs | default(false) }}' +# use_ssl: '{{ mso_use_ssl | default(true) }}' +# use_proxy: '{{ mso_use_proxy | default(true) }}' +# output_level: '{{ mso_output_level | default("info") }}' +# site: '{{ mso_site | default("ansible_test") }}' +# apic_username: '{{ apic_username }}' +# apic_password: '{{ apic_password }}' +# apic_site_id: '{{ apic_site_id | default(101) }}' +# urls: +# - https://{{ apic_hostname }} +# state: present + +- name: Remove schemas + mso_schema: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: &schema_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF exist + mso_schema_template_vrf: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: present + +- name: Ensure Filter 1 exist + mso_schema_template_filter_entry: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1-Entry + state: present + +- name: Ensure Filter 2 exist + mso_schema_template_filter_entry: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + filter: Filter2 + entry: Filter2-Entry + state: present + +- name: Ensure Contract1 exist + mso_schema_template_contract_filter: &contract_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Contract2 exist + mso_schema_template_contract_filter: + <<: *contract_present + template: Template 2 + contract: Contract2 + filter: Filter2 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 2 + state: present + +- name: Ensure external EPGs exist + mso_schema_template_externalepg: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + state: present + schema: '{{ item.schema }}' + template: '{{ item.template }}' + externalepg: '{{ item.externalepg }}' + vrf: + name: VRF + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + loop: + - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', externalepg: 'ansible_test_1' } + - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3', externalepg: 'ansible_test_3' } + +# ADD Contract to External EPG +- name: Add Contract1 to External EPG (check_mode) + mso_schema_template_external_epg_contract: &contract_ext_epg_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + contract: + name: Contract1 + type: consumer + state: present + check_mode: true + register: cm_add_contract_rel + +- name: Verify cm_add_contract_rel + assert: + that: + - cm_add_contract_rel is changed + - cm_add_contract_rel.previous == {} + - cm_add_contract_rel.current.contractRef.templateName == "Template1" + - cm_add_contract_rel.current.contractRef.contractName == "Contract1" + - cm_add_contract_rel.current.relationshipType == "consumer" + +- name: Add Contract to External EPG (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + register: nm_add_contract_rel + +- name: Verify nm_add_contract_rel + assert: + that: + - nm_add_contract_rel is changed + - nm_add_contract_rel.previous == {} + - nm_add_contract_rel.current.contractRef.templateName == "Template1" + - nm_add_contract_rel.current.contractRef.contractName == "Contract1" + - nm_add_contract_rel.current.relationshipType == "consumer" + - cm_add_contract_rel.current.contractRef.schemaId == nm_add_contract_rel.current.contractRef.schemaId + +- name: Add Contract to External EPG again (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + check_mode: true + register: cm_add_contract_rel_again + +- name: Verify cm_add_contract_rel_again + assert: + that: + - cm_add_contract_rel_again is not changed + - cm_add_contract_rel_again.previous.contractRef.templateName == "Template1" + - cm_add_contract_rel_again.current.contractRef.templateName == "Template1" + - cm_add_contract_rel_again.previous.contractRef.contractName == "Contract1" + - cm_add_contract_rel_again.current.contractRef.contractName == "Contract1" + - cm_add_contract_rel_again.previous.relationshipType == "consumer" + - cm_add_contract_rel_again.current.relationshipType == "consumer" + - cm_add_contract_rel_again.previous.contractRef.schemaId == cm_add_contract_rel_again.current.contractRef.schemaId + + +- name: Add Contract to External EPG again (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + register: nm_add_contract_rel_again + +- name: Verify nm_add_contract_rel_again + assert: + that: + - nm_add_contract_rel_again is not changed + - nm_add_contract_rel_again.previous.contractRef.templateName == "Template1" + - nm_add_contract_rel_again.current.contractRef.templateName == "Template1" + - nm_add_contract_rel_again.previous.contractRef.contractName == "Contract1" + - nm_add_contract_rel_again.current.contractRef.contractName == "Contract1" + - nm_add_contract_rel_again.previous.relationshipType == "consumer" + - nm_add_contract_rel_again.current.relationshipType == "consumer" + - nm_add_contract_rel_again.previous.contractRef.schemaId == nm_add_contract_rel_again.current.contractRef.schemaId + +- name: Add Contract1 to External EPG - provider (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + contract: + name: Contract1 + type: provider + register: nm_add_contract1_rel_provider + +- name: Add Contract2 to External EPG - consumer (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + contract: + name: Contract2 + template: Template 2 + type: consumer + register: nm_add_contract2_rel_consumer + +- name: Add Contract1 to External EPG 3 - provider (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: 'Template 3' + external_epg: ansible_test_3 + contract: + name: Contract1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + type: provider + register: nm_add_contract3_rel_provider + +- name: Verify nm_add_contract1_rel_provider, nm_add_contract2_rel_consumer and nm_add_contract3_rel_provider + assert: + that: + - nm_add_contract1_rel_provider is changed + - nm_add_contract2_rel_consumer is changed + - nm_add_contract3_rel_provider is changed + - nm_add_contract1_rel_provider.current.contractRef.contractName == "Contract1" + - nm_add_contract2_rel_consumer.current.contractRef.contractName == "Contract2" + - nm_add_contract3_rel_provider.current.contractRef.contractName == "Contract1" + - nm_add_contract1_rel_provider.current.contractRef.templateName == "Template1" + - nm_add_contract2_rel_consumer.current.contractRef.templateName == "Template2" + - nm_add_contract3_rel_provider.current.contractRef.templateName == "Template1" + - nm_add_contract1_rel_provider.current.contractRef.schemaId == nm_add_contract2_rel_consumer.current.contractRef.schemaId == nm_add_contract3_rel_provider.current.contractRef.schemaId + - nm_add_contract2_rel_consumer.current.relationshipType == "consumer" + - nm_add_contract1_rel_provider.current.relationshipType == nm_add_contract3_rel_provider.current.relationshipType == "provider" + +# # QUERY ALL Contract to External EPG +- name: Query all contract relationship for External EPG (check_mode) + mso_schema_template_external_epg_contract: &contract_ext_epg_query + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + check_mode: true + register: cm_query_all_contract_rels + +- name: Query all contract relationship for External EPG (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + register: nm_query_all_contract_rels + +- name: Verify query_all_contract_rels + assert: + that: + - cm_query_all_contract_rels is not changed + - nm_query_all_contract_rels is not changed + - cm_query_all_contract_rels.current | length == nm_query_all_contract_rels.current | length == 3 + + +# QUERY A Contract to External EPG +- name: Query Contract1 relationship for External EPG - consumer (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + type: consumer + check_mode: true + register: cm_query_contract1_consumer_rel + +- name: Query Contract1 relationship for External EPG - consumer (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + type: consumer + register: nm_query_contract1_consumer_rel + +- name: Query Contract1 relationship for External EPG - provider (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + type: provider + register: nm_query_contract1_provider_rel + +- name: Query Contract1 relationship for External EPG - consumer (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract2 + template: Template 2 + type: consumer + register: nm_query_contract2_consumer_rel + +- name: Query Contract1 relationship for External EPG - provider (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + external_epg: ansible_test_3 + contract: + name: Contract1 + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + type: provider + register: nm_query_contract3_provider_rel + +- name: Verify query_contractX_YYYYY_rel + assert: + that: + - cm_query_contract1_consumer_rel is not changed + - nm_query_contract1_consumer_rel is not changed + - nm_query_contract1_provider_rel is not changed + - nm_query_contract2_consumer_rel is not changed + - nm_query_contract3_provider_rel is not changed + - cm_query_contract1_consumer_rel == nm_query_contract1_consumer_rel + - cm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_provider_rel.current.contractRef.contractName == "Contract1" + - nm_query_contract2_consumer_rel.current.contractRef.contractName == "Contract2" + - nm_query_contract3_provider_rel.current.contractRef.contractName == "Contract1" + - cm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_provider_rel.current.contractRef.templateName == "Template1" + - nm_query_contract2_consumer_rel.current.contractRef.templateName == "Template2" + - nm_query_contract3_provider_rel.current.contractRef.templateName == "Template1" + - cm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_provider_rel.current.contractRef.schemaId == nm_query_contract2_consumer_rel.current.contractRef.schemaId == nm_query_contract3_provider_rel.current.contractRef.schemaId + - cm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract2_consumer_rel.current.relationshipType == "consumer" + - nm_query_contract1_provider_rel.current.relationshipType == nm_query_contract3_provider_rel.current.relationshipType == "provider" + + +# REMOVE Contract to External EPG +- name: Remove Contract to External EPG (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + state: absent + check_mode: true + register: cm_remove_contract_rel + +- name: Verify cm_remove_contract_rel + assert: + that: + - cm_remove_contract_rel is changed + - cm_remove_contract_rel.current == {} + +- name: Remove Contract to External EPG (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + state: absent + register: nm_remove_contract_rel + +- name: Verify nm_remove_contract_rel + assert: + that: + - nm_remove_contract_rel is changed + - nm_remove_contract_rel.current == {} + +- name: Remove Contract to External EPG again (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + state: absent + check_mode: true + register: cm_remove_contract_rel_again + +- name: Verify cm_remove_contract_rel_again + assert: + that: + - cm_remove_contract_rel_again is not changed + - cm_remove_contract_rel_again.current == {} + +- name: Remove Contract to External EPG again (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_present + state: absent + register: nm_remove_contract_rel_again + +- name: Verify nm_remove_contract_rel_again + assert: + that: + - nm_remove_contract_rel_again is not changed + - nm_remove_contract_rel_again.current == {} + + +# QUERY NON-EXISTING Contract to External EPG +- name: Query non-existing contract (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: non_existing_contract + type: provider + check_mode: true + ignore_errors: true + register: cm_query_non_contract + +- name: Query non-existing contract (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: non_existing_contract + type: provider + ignore_errors: true + register: nm_query_non_contract + +- name: Verify query_non_contract + assert: + that: + - cm_query_non_contract is not changed + - nm_query_non_contract is not changed + - cm_query_non_contract == nm_query_non_contract + - cm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found") + - nm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found") + +# QUERY NON-EXISTING ExtEPG +- name: Query non-existing ExtEPG (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + external_epg: non_existing_ext_epg + check_mode: true + ignore_errors: true + register: cm_query_non_ext_epg + +- name: Query non-existing ExtEPG (normal mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + external_epg: non_existing_ext_epg + ignore_errors: true + register: nm_query_non_ext_epg + +- name: Verify query_non_ext_epg + assert: + that: + - cm_query_non_ext_epg is not changed + - nm_query_non_ext_epg is not changed + - cm_query_non_ext_epg == nm_query_non_ext_epg + - cm_query_non_ext_epg.msg == nm_query_non_ext_epg.msg == "Provided epg 'non_existing_ext_epg' does not exist. Existing epgs{{':'}} ansible_test_1" + +# USE A NON-EXISTING STATE +- name: Non-existing state for contract relationship (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for contract relationship (normal_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for contract relationship (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + schema: non-existing-schema + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for contract relationship (normal_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + schema: non-existing-schema + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +- name: Non-existing contract schema for contract relationship (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + schema: non-existing-schema + template: Template 1 + type: provider + check_mode: true + ignore_errors: true + register: cm_non_existing_contract_schema + +- name: Non-existing contract schema for contract relationship (normal_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + schema: non-existing-schema + template: Template 1 + type: provider + ignore_errors: true + register: nm_non_existing_contract_schema + +- name: Verify non_existing_contract_schema + assert: + that: + - cm_non_existing_contract_schema is not changed + - nm_non_existing_contract_schema is not changed + - cm_non_existing_contract_schema == nm_non_existing_contract_schema + - cm_non_existing_contract_schema.msg == nm_non_existing_contract_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for contract relationship (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + template: non-existing-template + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for contract relationship (normal_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + template: non-existing-template + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +- name: Non-existing contract template for contract relationship (check_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + template: non-existing-template + type: provider + check_mode: true + ignore_errors: true + register: cm_non_existing_contract_template + +- name: Non-existing contract template for contract relationship (normal_mode) + mso_schema_template_external_epg_contract: + <<: *contract_ext_epg_query + contract: + name: Contract1 + template: non-existing-template + type: provider + ignore_errors: true + register: nm_non_existing_contract_template + +- name: Verify non_existing_contract_template + assert: + that: + - cm_non_existing_contract_template is not changed + - nm_non_existing_contract_template is not changed + - cm_non_existing_contract_template == nm_non_existing_contract_template + - cm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found") + - nm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found")
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml new file mode 100644 index 000000000..5911f7667 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml @@ -0,0 +1,452 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: present + +- name: Ensure VRF2 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: present + +- name: Ensure ANP1 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP + state: present + +- name: Ensure ANP2 exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP2 + state: present + +- name: Ensure L3out exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: L3out + vrf: + name: VRF + state: present + +- name: Ensure L3out2 exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: L3out2 + vrf: + name: VRF2 + state: present + +# ADD External EPGs +- name: Ensure External EPG1 exists + mso_schema_template_externalepg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + externalepg: extEPG1 + vrf: + name: VRF + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + l3out: + name: L3out + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + anp: + name: ANP + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + +- name: Ensure External EPG2 exists + mso_schema_template_externalepg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + externalepg: extEPG2 + vrf: + name: VRF2 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + l3out: + name: L3out2 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + anp: + name: ANP2 + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + state: present + +# ADD Selector to EPG +- name: Add Selector to extEPG1 (check_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: present + check_mode: true + register: cm_add_selector_1 + +- name: Verify cm_add_selector_1 + assert: + that: + - cm_add_selector_1 is changed + - cm_add_selector_1.previous == {} + - cm_add_selector_1.current.name == "selector_1" + - cm_add_selector_1.current.expressions == [] + +- name: Add Selector 1 to extEPG1 (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: present + ignore_errors: true + register: nm_add_selector_1 + +- name: Verify nm_add_selector_1 + assert: + that: + - nm_add_selector_1 is changed + - nm_add_selector_1.previous == {} + - nm_add_selector_1.current.name == "selector_1" + - nm_add_selector_1.current.expressions == [] + +- name: Add Selector 1 to extEPG1 again(normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: present + ignore_errors: true + register: nm_add_selector_1_again + +- name: Verify nm_add_selector_1_again + assert: + that: + - nm_add_selector_1_again is not changed + +- name: Add Selector to extEPG1 again (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: present + register: nm_add_selector_1_again + +- name: Verify nm_add_selector_1_again + assert: + that: + - nm_add_selector_1_again is not changed + +- name: Add Selector 2 to extEPG1 (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_2 + expressions: + - type: ip_address + operator: equals + value: 10.0.0.0 + state: present + register: nm_add_selector_2 + +- name: Verify nm_add_selector_2 + assert: + that: + - nm_add_selector_2 is changed + - nm_add_selector_2.previous == {} + - nm_add_selector_2.current.name == "selector_2" + - nm_add_selector_2.current.expressions[0].key == "ipAddress" + - nm_add_selector_2.current.expressions[0].operator == "equals" + - nm_add_selector_2.current.expressions[0].value == "10.0.0.0" + +- name: Add Selector 3 to extEPG1 (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_3 + expressions: + - type: ip_address + operator: equals + value: 10.1.1.1 + state: present + register: nm_add_selector_3 + +- name: Verify nm_add_selector_3 + assert: + that: + - nm_add_selector_3 is changed + - nm_add_selector_3.previous == {} + - nm_add_selector_3.current.name == "selector_3" + - nm_add_selector_3.current.expressions[0].value == "10.1.1.1" + +- name: Remove slector_1 + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: absent + register: nm_remove_selector_1 + +- name: Verify nm_remove_selector_1 + assert: + that: + - nm_remove_selector_1 is changed + +# QUERY selectors +- name: Query all selectors of extEPG1 + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + state: query + register: nm_query_all + +- name: Verify nm_query_all + assert: + that: + - nm_query_all is not changed + +- name: Query a selector of extEPG1 + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_2 + state: query + register: nm_query_selector_2 + +- name: Verify nm_query_selector_2 + assert: + that: + - nm_query_selector_2 is not changed + - nm_query_selector_2.current.expressions[0].value == "10.0.0.0" + +- name: Query a removed selector_1 of extEPG1 + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_query_removed + +- name: Verify nm_query_removed + assert: + that: + - nm_query_removed.msg == "Selector 'selector_1' not found" + +# QUERY NON-EXISTING External EPG +- name: Query non-existing EPG (normal mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: non_extEPG1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_query_non_epg + +- name: Verify query_non_epg + assert: + that: + - nm_query_non_epg is not changed + - nm_query_non_epg.msg == "Provided external epg 'non_extEPG1' does not exist. Existing epgs{{':'}} extEPG1, extEPG2" + +# USE A NON-EXISTING STATE +- name: Non-existing state (check_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template (check_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + external_epg: extEPG1 + selector: selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + external_epg: extEPG1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema (check_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema (normal_mode) + mso_schema_template_external_epg_selector: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + external_epg: extEPG1 + selector: selector_1 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml new file mode 100644 index 000000000..06b5e65b7 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml @@ -0,0 +1,536 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Undeploy templates if deployed from previous test case + mso_schema_template_deploy: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: "{{ item }}" + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + loop: + - Template 1 + - Template 2 + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure ansible_test_1 external EPG does not exist + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: absent + ignore_errors: true + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure VRF exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: present + +- name: Ensure L3out exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: L3out + vrf: + name: VRF + state: present + +- name: Ensure ANP exists + mso_schema_template_anp: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + anp: ANP1 + state: present + +- name: Add external EPG + mso_schema_template_external_epg: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + vrf: + name: VRF + state: present + +# ADD external EPG subnet +- name: Add external EPG subnet (check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: present + check_mode: true + register: cm_add_epg_subnet + +- name: Verify cm_add_epg_subnet + assert: + that: + - cm_add_epg_subnet is changed + - cm_add_epg_subnet.previous == {} + - cm_add_epg_subnet.current.ip == "10.0.0.0/24" + +- name: Add external EPG subnet (normal mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: present + register: nm_add_epg_subnet + +- name: Verify nm_add_epg_subnet + assert: + that: + - nm_add_epg_subnet is changed + - nm_add_epg_subnet.previous == {} + - nm_add_epg_subnet.current.ip == "10.0.0.0/24" + +- name: Add external EPG subnet again (check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: present + check_mode: true + register: cm_add_epg_subnet_again + +- name: Verify cm_add_epg_subnet_again + assert: + that: + - cm_add_epg_subnet_again is not changed + - cm_add_epg_subnet_again.previous.ip == "10.0.0.0/24" + - cm_add_epg_subnet_again.current.ip == "10.0.0.0/24" + +- name: Add epg again subnet (normal mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: present + register: nm_add_epg_subnet_again + +- name: Verify nm_add_epg_subnet_again + assert: + that: + - nm_add_epg_subnet_again is not changed + - nm_add_epg_subnet_again.previous.ip == "10.0.0.0/24" + - nm_add_epg_subnet_again.current.ip == "10.0.0.0/24" + +- name: Add external EPG subnet 2 + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.2/24 + state: present + register: add_epg_subnet_2 + +- name: Verify add_epg_subnet_2 + assert: + that: + - add_epg_subnet_2 is changed + - add_epg_subnet_2.current.ip == "10.0.0.2/24" + +# QUERY ALL EPG Subnets +- name: Query all EPG (check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + check_mode: true + register: cm_query_all_epg_subnets + +- name: Query all EPG (normal mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + state: query + register: nm_query_all_epg_subnets + +- name: Verify query_all_epg_subnets + assert: + that: + - cm_query_all_epg_subnets is not changed + - nm_query_all_epg_subnets is not changed + - cm_query_all_epg_subnets.current | length == nm_query_all_epg_subnets.current | length == 2 + +# QUERY AN EPG subnet +- name: Query epg subnet 1(check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: query + check_mode: true + register: cm_query_epg_subnet_1 + +- name: Query epg subnet 1(normal_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: query + register: nm_query_epg_subnet_1 + +- name: Verify cm_query_epg_subnet_1 and nm_query_epg_subnet_1 + assert: + that: + - cm_query_epg_subnet_1 is not changed + - nm_query_epg_subnet_1 is not changed + - cm_query_epg_subnet_1.current.ip == "10.0.0.0/24" == nm_query_epg_subnet_1.current.ip + +# REMOVE EPG +- name: Remove EPG subnet 1 (check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: absent + check_mode: true + register: cm_remove_epg_subnet_1 + +- name: Verify cm_remove_epg_subnet_1 + assert: + that: + - cm_remove_epg_subnet_1 is changed + - cm_remove_epg_subnet_1.current == {} + +- name: Remove EPG subnet 1 (normal_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: absent + register: nm_remove_epg_subnet_1 + +- name: Verify nm_remove_epg_subnet_1 + assert: + that: + - nm_remove_epg_subnet_1 is changed + - nm_remove_epg_subnet_1.current == {} + +- name: Remove EPG subnet 1 again (normal mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: absent + register: nm_remove_epg_subnet_1_again + +- name: Verify nm_remove_epg_subnet_1_again + assert: + that: + - nm_remove_epg_subnet_1_again is not changed + - nm_remove_epg_subnet_1_again.previous == nm_remove_epg_subnet_1_again.current == {} + +# Chcek aggregate when scope parameter Shared control is absent and present +- name: Add aggregate without Shared control scope parameter + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.2/24 + scope: import-rtctrl + aggregate: shared-rtctrl + state: present + ignore_errors: true + register: add_epg_subnet_no_ag + +- name: Verify add_epg_subnet_no_ag + assert: + that: + - add_epg_subnet_no_ag is changed + +- name: Verify add_epg_subnet_no_ag (3.1.1g to 3.1.1n) + assert: + that: + - add_epg_subnet_no_ag.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Aggregate should be enabled only if shared-rtctrl is enabled in Scope exception while trying to update schema" + when: + - version.current.version is version('3.1.1g', '>=') + - version.current.version is version('3.2', '<') + +- name: Verify add_epg_subnet_no_ag (version < 3.1.1g) + assert: + that: + - add_epg_subnet_no_ag.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Aggregate should be enabled only if shared-rtctrl is enabled in Scope" + when: + - version.current.version is version('3.1.1g', '<') + +- name: Verify add_epg_subnet_no_ag (version >= 4.0) + assert: + that: + - add_epg_subnet_no_ag.msg == "MSO Error 400{{':'}} ExternalEPG{{':'}} ansible_test_1 in Schema{{':'}} ansible_test , Template{{':'}} Template1 External EPG validation error{{':'}} aggregate should be enabled only if shared-rtctrl is enabled in Scope for subnet 10.0.0.2/24" + when: + - version.current.version is version('4.0', '>=') + +- name: Execute tasks only for MSO version < 4.0 + # mso_schema_validate not supported after 4.0, validation executed upon request + when: version.current.version is version('4.0', '<') + block: + - name: Get Validation status + mso_schema_validate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + state: query + ignore_errors: true + register: query_validate + when: version.current.version is version('3.6', '>=') + + - name: Verify query_validate for a version that's after 3.6 + assert: + that: + - query_validate is not changed + - query_validate.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Aggregate should be enabled only if shared-rtctrl is enabled in Scope exception while trying to update schema") + when: + - version.current.version is version('3.6', '>=') + +- name: Add aggregate with Shared control scope parameter + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.2/24 + scope: shared-rtctrl + aggregate: shared-rtctrl + state: present + ignore_errors: true + register: add_epg_subnet_ag + +- name: Verify add_epg_subnet_ag + assert: + that: + - add_epg_subnet_ag is changed + - add_epg_subnet_ag.current.aggregate[0] == "shared-rtctrl" + +- name: Change EPG subnet 2 by changing Route Controls + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.2/24 + scope: + - import-rtctrl + - shared-security + - export-rtctrl + - shared-security + - import-security + state: present + register: change_epg_subnet + +- name: Verify change_epg_subnet + assert: + that: + - change_epg_subnet is changed + - change_epg_subnet.current.ip == "10.0.0.2/24" + - change_epg_subnet.current.scope | length == 5 + +# QUERY NON-EXISTING EPG subnet +- name: Query non-existing EPG subnet(check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.10/24 + state: query + ignore_errors: true + check_mode: true + register: cm_query_non_existing_epg_subnet + +- name: Query non-existing EPG subnet(normal_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.10/24 + state: query + ignore_errors: true + register: nm_query_non_existing_epg_subnet + +- name: Verify cm_query_non_existing_epg_subnet and nm_query_non_existing_epg_subnet + assert: + that: + - cm_query_non_existing_epg_subnet is not changed + - nm_query_non_existing_epg_subnet is not changed + - cm_query_non_existing_epg_subnet == nm_query_non_existing_epg_subnet + - cm_query_non_existing_epg_subnet.msg == nm_query_non_existing_epg_subnet.msg == "Subnet '10.0.0.10/24' not found" + +# QUERY NON-EXISTING EPG +- name: Query non-existing EPG subnet(check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: non_existing_epg + subnet: 10.0.0.2/24 + state: query + ignore_errors: true + check_mode: true + register: cm_query_non_existing_epg + +- name: Query non-existing EPG subnet(normal_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + external_epg: non_existing_epg + subnet: 10.0.0.2/24 + state: query + ignore_errors: true + register: nm_query_non_existing_epg + +- name: Verify cm_query_non_existing_epg and nm_query_non_existing_epg + assert: + that: + - cm_query_non_existing_epg is not changed + - nm_query_non_existing_epg is not changed + - cm_query_non_existing_epg == nm_query_non_existing_epg + - cm_query_non_existing_epg.msg == nm_query_non_existing_epg.msg == "Provided External EPG 'non_existing_epg' does not exist. Existing epgs{{':'}} ansible_test_1" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for epg subnet(check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for epg subnet(normal_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: non-existing-schema + template: Template 1 + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: query + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for epg subnet(check_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for epg subnet(normal_mode) + mso_schema_template_external_epg_subnet: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non-existing-template + external_epg: ansible_test_1 + subnet: 10.0.0.0/24 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1"
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml new file mode 100644 index 000000000..262a1903d --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml @@ -0,0 +1,326 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exist + mso_site: &site_present + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template1 exist + mso_schema_template: &schema_present + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template1 + state: present + +- name: Create filter with filter entry (check_model) + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + filter_display_name: filter1 + entry: filter_entry1 + state: present + check_mode: true + register: cm_add_filter + +- name: Create filter with filter entry (normal mode) + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: filter_entry1 + state: present + register: nm_add_filter + +- name: Create filter with filter entry again + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: filter_entry1 + state: present + register: add_filter_again + +- name: Verify add_filter + assert: + that: + - cm_add_filter is changed + - cm_add_filter.previous == {} + - cm_add_filter.current.name == "filter_entry1" + - nm_add_filter is changed + - nm_add_filter.previous == {} + - nm_add_filter.current.name == "filter_entry1" + - add_filter_again is not changed + - add_filter_again.previous.name == "filter_entry1" + - nm_add_filter.current == add_filter_again.current + +- name: Add description to filter and filter entry for version greater than 3.3 + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + filter_description: "filter description" + entry: filter_entry1 + filter_entry_description: "filter entry description" + state: present + register: add_filter_descr + when: version.current.version is version('3.3', '>=') + +- name: Verify add_filter_only + assert: + that: + - add_filter_descr is changed + - add_filter_descr.current.description == "filter entry description" + when: version.current.version is version('3.3', '>=') + + +- name: Create filter without filter entry + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + state: present + ignore_errors: true + register: add_filter_only + +- name: Verify add_filter_only + assert: + that: + - add_filter_only is not changed + - add_filter_only.msg == "state is present but all of the following are missing{{':'}} entry" + +- name: Create filter with multiple filter entries + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + filter_display_name: filter1 + display_name: '{{ item }}' + ethertype: ip + ip_protocol: tcp + tcp_session_rules: + - acknowledgement + - established + source_from: 22 + source_to: 22 + destination_from: 22 + destination_to: 22 + arp_flag: request + stateful: true + fragments_only: false + entry: '{{ item }}' + state: present + register: add_multiple_entries + loop: + - 'filter_entry2' + - 'filter_entry3' + +# QUERY the filters +- name: Query a particular filter entry 1 + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: filter_entry1 + state: query + register: query_filter + +- name: Query all filter entries + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + state: query + register: query_all + +- name: Verify query + assert: + that: + - query_filter is not changed + - query_all is not changed + +# QUERY cases +- name: Query existing filter and filter entry + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: non_existing_filter + entry: non_existing_filter_entry + state: query + ignore_errors: true + register: query_non_existing_filter + +- name: Query non-existing filter entry + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: non_existing_filter_entry + state: query + ignore_errors: true + register: query_non_existing_entry + +- name: Verify query cases + assert: + that: + - query_non_existing_filter is not changed + - query_non_existing_entry is not changed + - query_non_existing_entry.msg == "Entry 'non_existing_filter_entry' not found" + +# Delete filter entries +- name: Delete filter entry 3 + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: filter_entry3 + state: absent + register: remove_filter + +- name: Verify delete filter_entry3 + assert: + that: + - remove_filter is changed + - remove_filter.current == {} + +# USE A NON_EXISTING_TEMPLATE +- name: non_existing_template (normal_mode) + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + filter: filter1 + entry: filter_entry1 + state: present + ignore_errors: true + register: nm_non_existing_template + +- name: Verify nm_non_existing_template + assert: + that: + - nm_non_existing_template is not changed + - nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1" + +- name: Query non_existing_filter + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: non_existing_filter + state: query + ignore_errors: true + register: query_non_existing_filter + +- name: Delete non_existing_filter + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: non_existing_filter + entry: filter_entry1 + state: absent + ignore_errors: true + register: remove_non_existing_filter + +- name: Delete non_existing_filter_entry + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: non_existing_filter_entry + state: absent + ignore_errors: true + register: remove_non_existing_entry + +- name: Verify non_existing + assert: + that: + - query_non_existing_filter is not changed + - query_non_existing_filter.msg == "Filter 'non_existing_filter' not found" + - remove_non_existing_filter is not changed + - remove_non_existing_entry is not changed + - nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1" + + +# Delete filter entries +- name: Delete filter entry 3 + cisco.mso.mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + filter: filter1 + entry: '{{ item }}' + state: absent + register: remove_multiple_entries + loop: + - 'filter_entry1' + - 'filter_entry2' + +- name: Verify remove_multiple_entries + assert: + that: + - remove_multiple_entries is changed
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml new file mode 100644 index 000000000..a61752fa6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml @@ -0,0 +1,265 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template1, and Template2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{item.template}}' + state: present + loop: + - { template: Template1} + - { template: Template2} + +- name: Ensure VRF1 exists + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + vrf: VRF1 + state: present + +- name: Verify L3Out doesn't exist + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: absent + +- name: Add new L3Out (check_mode) + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + state: present + check_mode: true + register: cm_add_l3out + +- name: Add new L3Out (normal mode) + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + state: present + register: nm_add_l3out + +- name: Add L3Out again + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + vrf: + name: VRF1 + state: present + register: add_l3out_again + +- name: Verify add + assert: + that: + - cm_add_l3out is changed + - cm_add_l3out.previous == {} + - cm_add_l3out.current.name == "L3out1" + - cm_add_l3out.current.vrfRef.templateName == "Template1" + - cm_add_l3out.current.vrfRef.vrfName == "VRF1" + - nm_add_l3out is changed + - nm_add_l3out.previous == {} + - nm_add_l3out.current.name == "L3out1" + - nm_add_l3out.current.vrfRef.templateName == "Template1" + - nm_add_l3out.current.vrfRef.vrfName == "VRF1" + - add_l3out_again is not changed + - add_l3out_again.previous.name == "L3out1" + - nm_add_l3out.current.vrfRef.schemaId == add_l3out_again.current.vrfRef.schemaId + +- name: Add new L3Outs + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: '{{item.l3out}}' + vrf: + name: VRF1 + state: present + register: new_l3outs + loop: + - { l3out: L3out2} + - { l3out: L3out3} + +- name: Verify add + assert: + that: + - new_l3outs is changed + +- name: Query a specific L3Out + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: query + register: query_l3out + +- name: Query all L3outs + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: query + register: query_all + +- name: Verify query + assert: + that: + - query_l3out is not changed + - query_all is not changed + +- name: Remove an L3Out + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + state: absent + register: delete_l3out + +- name: Verify delete + assert: + that: + - delete_l3out is changed + - delete_l3out.previous.name == "L3out1" + - delete_l3out.current == {} + +# USE A NON_EXISTING_TEMPLATE +- name: non_existing_template (check_mode) + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + l3out: L3out2 + vrf: + name: VRF1 + state: query + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: non_existing_template (normal_mode) + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + l3out: L3out2 + vrf: + name: VRF1 + state: query + ignore_errors: true + register: nm_non_existing_template + +- name: Verify cm_non_existing_template and nm_non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +# QUERY NON-EXISTING L3Out +- name: Query non-existing L3Out (check_mode) + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: non_existing_l3out + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_l3out + +- name: Query non-existing L3Out (normal_mode) + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + l3out: non_existing_l3out + state: query + ignore_errors: true + register: nm_query_non_l3out + +- name: Verify cm_query_non_l3out and nm_query_non_l3out + assert: + that: + - cm_query_non_l3out is not changed + - nm_query_non_l3out is not changed + - cm_query_non_l3out.msg == nm_query_non_l3out.msg == "L3out 'non_existing_l3out' not found" + +# Add description for version >= 3.3 + +- name: Add new L3Out + mso_schema_template_l3out: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + l3out: L3out1 + description: "A L3out description" + vrf: + name: VRF1 + state: present + register: add_desc + when: version.current.version is version('3.3', '>=') + +- name: Verify add description + assert: + that: + - add_desc is changed + when: version.current.version is version('3.3', '>=')
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml new file mode 100644 index 000000000..44940f0d6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml @@ -0,0 +1,383 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +# CLEAN ENVIRONMENT +- name: Ensure site exist + mso_site: &site_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(102) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Undeploy a schema 2 template 2 + mso_schema_template_deploy: + <<: *mso_info + template: Template 2 + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + register: undeploy_template2 + +- name: Undeploy a schema 1 template 1 + mso_schema_template_deploy: + <<: *mso_info + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + register: undeploy_template1 + +- name: Remove a site from a schema 1 with Template 1 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: absent + ignore_errors: true + register: rm_site_temp1 + +- name: Remove a site from a schema 2 with Template 2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: absent + ignore_errors: true + register: rm_site_temp2 + +- name: Remove schemas + mso_schema: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ item }}' + state: absent + ignore_errors: true + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: &tenant_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schemas with Template 1 exist + mso_schema_template: &schema_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ item }}' + tenant: ansible_test + template: Template 1 + state: present + loop: + - '{{ mso_schema | default("ansible_test") }}' + - '{{ mso_schema | default("ansible_test") }}_2' + +- name: Ensure schema 2 with Template 2 exist + mso_schema_template: + <<: *schema_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 2 + state: present + register: schema2_template2 + +- name: Add a new site to a schema 1 with Template 1 in normal mode + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + register: add_site_nm1 + +- name: Add a new site to a schema 2 with Template 2 in normal mode + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: present + register: add_site_nm2 + +- name: Ensure VRF exist + mso_schema_template_vrf: &vrf_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + layer3_multicast: true + state: present + +- name: Ensure ANP exist + mso_schema_template_anp: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: ANP + state: present + +- name: Ensure ANP2 exist + mso_schema_template_anp: + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: ANP2 + state: present + +- name: Ensure ansible_test_1 BD exist + mso_schema_template_bd: + <<: *vrf_present + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + bd: '{{ item }}' + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + when: version.current.version is version('2.2.4e', '!=') + loop: + - '{{ BD_1 | default("ansible_test") }}_1' + - '{{ BD_2 | default("ansible_test") }}_2' + +- name: Ensure EPG exist + mso_schema_template_anp_epg: &epg_present + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + anp: ANP + epg: ansible_test_1 + bd: + name: ansible_test_1 + vrf: + name: VRF + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: cm_add_epg + +- name: Add EPG 2 (normal mode) + mso_schema_template_anp_epg: + <<: *epg_present + anp: ANP2 + epg: '{{ item }}' + loop: + - '{{ EPG_2 | default("ansible_test") }}_2' + - '{{ EPG_3 | default("ansible_test") }}_3' + - '{{ EPG_4 | default("ansible_test") }}_4' + +- name: Migration of objects between templates + mso_schema_template_migrate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + target_schema: '{{ mso_schema | default("ansible_test") }}' + target_template: Template 1 + bds: + - ansible_test_1 + epgs: + - epg: ansible_test_1 + anp: ANP + - epg: ansible_test_2 + anp: ANP2 + state: present + when: version.current.version is version('2.2.4e', '!=') + register: object_migrate + +- name: Deploy a schema 1 template 1 after version 4.0 + ndo_schema_template_deploy: + <<: *mso_info + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: deploy + when: version.current.version is version('4.0', '>=') + +- name: Migration of BD objects between templates + mso_schema_template_migrate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + target_schema: '{{ mso_schema | default("ansible_test") }}_2' + target_template: Template 2 + bds: + - ansible_test_2 + state: present + when: version.current.version is version('2.2.4e', '!=') + register: bd_migrate + +- name: Deploy a schema 2 template 2 after version 4.0 + ndo_schema_template_deploy: + <<: *mso_info + template: Template 2 + schema: '{{ mso_schema | default("ansible_test") }}_2' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: deploy + when: version.current.version is version('4.0', '>=') + +- name: Migration of EPG objects between templates + mso_schema_template_migrate: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 1 + target_schema: '{{ mso_schema | default("ansible_test") }}_2' + target_template: Template 2 + epgs: + - epg: ansible_test_3 + anp: ANP2 + - epg: ansible_test_4 + anp: ANP2 + state: present + when: version.current.version is version('2.2.4e', '!=') + register: epg_migrate + +- name: Undeploy before 4.0 + when: + - version.current.version is version('4.0', '<') + block: + - name: Undeploy a schema 2 template 2 + mso_schema_template_deploy: + <<: *mso_info + template: Template 2 + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + register: undeploy_template2 + + - name: Undeploy a schema 1 template 1 + mso_schema_template_deploy: + <<: *mso_info + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + register: undeploy_template1 + +- name: Undeploy after 4.0 + when: + - version.current.version is version('4.0', '>=') + block: + - name: Undeploy a schema 2 template 2 + ndo_schema_template_deploy: + <<: *mso_info + template: Template 2 + schema: '{{ mso_schema | default("ansible_test") }}_2' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: undeploy + + - name: Undeploy a schema 1 template 1 + ndo_schema_template_deploy: + <<: *mso_info + template: Template 1 + schema: '{{ mso_schema | default("ansible_test") }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: undeploy + +- name: Remove a site from a schema 2 with Template 2 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 2 + state: absent + register: rm_site_temp2 + +- name: Remove a site from a schema 1 with Template 1 + mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: absent + register: rm_site_temp1 diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml new file mode 100644 index 000000000..d11a3f4a2 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml @@ -0,0 +1,396 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template1 + state: present + +- name: Create a service graph (check mode) + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + display_name: sg + service_nodes: + - type: firewall + - type: load-balancer + - type: other + filter_after_first_node: allow_all + state: present + register: sg1_cm + check_mode: true + +- name: Verify sg1_cm + assert: + that: + - sg1_cm is changed + - sg1_cm.current.name == "SG1" + - sg1_cm.current.displayName == "sg" + - sg1_cm.current.nodeFilter == "allow-all" + - sg1_cm.current.serviceGraphRef.templateName == "Template1" + - sg1_cm.current.serviceNodes | length == 3 + - sg1_cm.current.serviceNodes.0.name == "firewall" + - sg1_cm.current.serviceNodes.1.name == "load-balancer" + - sg1_cm.current.serviceNodes.2.name == "other" + +- name: Create a service graph (normal mode) + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + display_name: sg + service_nodes: + - type: firewall + - type: load-balancer + - type: other + filter_after_first_node: allow_all + state: present + register: sg1 + +- name: Create service graph again + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + display_name: sg + service_nodes: + - type: firewall + - type: load-balancer + - type: other + filter_after_first_node: allow_all + state: present + register: sg1_again + +- name: Verify sg1 and sg1_again + assert: + that: + - sg1 is changed + - sg1_again is not changed + - sg1.current.name == "SG1" + - sg1.current.displayName == "sg" + - sg1.current.nodeFilter == "allow-all" + - sg1.current.serviceGraphRef.templateName == "Template1" + - sg1.current.serviceNodes | length == 3 + - sg1.current.serviceNodes.0.name == "firewall" + - sg1.current.serviceNodes.1.name == "load-balancer" + - sg1.current.serviceNodes.2.name == "other" + - sg1_again.current.name == "SG1" + - sg1_again.current.displayName == "sg" + - sg1_again.current.nodeFilter == "allow-all" + - sg1_again.current.serviceGraphRef.templateName == "Template1" + - sg1_again.current.serviceNodes | length == 3 + - sg1_again.current.serviceNodes.0.name == "firewall" + - sg1_again.current.serviceNodes.1.name == "load-balancer" + - sg1_again.current.serviceNodes.2.name == "other" + +- name: Create another service graph SG2 + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG2 + display_name: Service_Graph2 + service_nodes: + - type: firewall + - type: load-balancer + filter_after_first_node: filters_from_contract + state: present + register: sg2 + +- name: Verify sg2 + assert: + that: + - sg2 is changed + - sg2.current.name == "SG2" + - sg2.current.displayName == "Service_Graph2" + - sg2.current.nodeFilter == "filters-from-contract" + - sg2.current.serviceGraphRef.templateName == "Template1" + - sg2.current.serviceNodes | length == 2 + - sg2.current.serviceNodes.0.name == "firewall" + - sg2.current.serviceNodes.1.name == "load-balancer" + +- name: Change Service Graph SG2 + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG2 + display_name: Service_Graph_changed + service_nodes: + - type: firewall + - type: load-balancer + filter_after_first_node: filters_from_contract + state: present + register: sg2_change + +- name: Verify sg2_change + assert: + that: + - sg2_change is changed + - sg2_change.current.name == "SG2" + - sg2_change.current.displayName == "Service_Graph_changed" + - sg2_change.current.nodeFilter == "filters-from-contract" + - sg2_change.current.serviceGraphRef.templateName == "Template1" + - sg2_change.current.serviceNodes | length == 2 + - sg2_change.current.serviceNodes.0.name == "firewall" + - sg2_change.current.serviceNodes.1.name == "load-balancer" + +- name: Create another service graph with no display name + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG3 + service_nodes: + - type: firewall + filter_after_first_node: filters_from_contract + state: present + register: sg3 + +- name: Verify sg3 + assert: + that: + - sg3 is changed + - sg3.current.name == "SG3" + - sg3.current.displayName == "SG3" + - sg3.current.nodeFilter == "filters-from-contract" + - sg3.current.serviceGraphRef.templateName == "Template1" + - sg3.current.serviceNodes.0.name == "firewall" + - sg3.current.serviceNodes | length == 1 + +- name: Create service graph SG3 with addition of new service node type + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG3 + service_nodes: + - type: firewall + - type: other + filter_after_first_node: filters_from_contract + state: present + register: sg3_other + +- name: Verify sg3_other + assert: + that: + - sg3_other is changed + - sg3_other.current.name == "SG3" + - sg3_other.current.displayName == "SG3" + - sg3_other.current.nodeFilter == "filters-from-contract" + - sg3_other.current.serviceGraphRef.templateName == "Template1" + - sg3_other.current.serviceNodes.0.name == "firewall" + - sg3_other.current.serviceNodes.1.name == "other" + - sg3_other.current.serviceNodes | length == 2 + +- name: Create service graph SG3 interchanging the index of service node types + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG3 + service_nodes: + - type: other + - type: firewall + filter_after_first_node: filters_from_contract + state: present + register: sg3_interchange + +- name: Verify sg3_interchange + assert: + that: + - sg3_interchange is changed + - sg3_interchange.current.name == "SG3" + - sg3_interchange.current.displayName == "SG3" + - sg3_interchange.current.nodeFilter == "filters-from-contract" + - sg3_interchange.current.serviceGraphRef.templateName == "Template1" + - sg3_interchange.current.serviceNodes.1.name == "firewall" + - sg3_interchange.current.serviceNodes.0.name == "other" + - sg3_interchange.current.serviceNodes | length == 2 + +- name: Create another service graph with non existing node type + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG4 + service_nodes: + - type: non_existing_type + filter_after_first_node: filters_from_contract + state: present + register: sg4 + ignore_errors: true + +- name: Verify sg4 + assert: + that: + - sg4.msg == "Provided service node type 'non_existing_type' does not exist. Existing node types include{{':'}} firewall, load-balancer, other", + +- name: Query service graph SG + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + state: query + register: query_sg + +- name: Verify query_sg + assert: + that: + - query_sg is not changed + - query_sg.current.name == "SG1" + - query_sg.current.displayName == "sg" + - query_sg.current.nodeFilter == "allow-all" + - query_sg.current.serviceNodes | length == 3 + - query_sg.current.serviceNodes.0.name == "firewall" + - query_sg.current.serviceNodes.1.name == "load-balancer" + - query_sg.current.serviceNodes.2.name == "other" + +- name: Query all service graphs + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current | length == 3 + +- name: Query non_existing service graph + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + service_graph: non_existent + state: query + ignore_errors: true + register: query_non_existing_sg + +- name: Verify query_non_existing_sg + assert: + that: + - query_non_existing_sg.msg == "Service Graph 'non_existent' not found" + +- name: Use non_existing schema + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: non_existing_schema + template: Template1 + service_graph: SG + state: query + ignore_errors: true + register: query_non_existing_schema + +- name: Use non_existing template + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + service_graph: SG + state: query + ignore_errors: true + register: query_non_existing_template + +- name: Verify query_non_existing_schema and query_non_existing_template + assert: + that: + - query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + - query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1" + +- name: Remove service graph (check mode) + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + state: absent + register: rm_sg_cm + check_mode: true + +- name: Verify rm_sg_cm + assert: + that: + - rm_sg_cm is changed + - rm_sg_cm.current == {} + - rm_sg_cm.previous.name == "SG1" + +- name: Remove service graph (normal mode) + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + state: absent + register: rm_sg + +- name: Verify rm_sg + assert: + that: + - rm_sg is changed + - rm_sg.current == {} + - rm_sg.previous.name == "SG1" + +- name: Remove service graph again + cisco.mso.mso_schema_template_service_graph: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + service_graph: SG1 + state: absent + register: rm_sg_again + +- name: Verify rm_sg_again + assert: + that: + - rm_sg_again is not changed + - rm_sg_again.current == {} + - rm_sg_again.previous == {} diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml new file mode 100644 index 000000000..0f25a76b0 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml @@ -0,0 +1,542 @@ +# Test code for the MSO modules +# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# - name: Ensure site exist +# mso_site: &site_present +# host: '{{ mso_hostname }}' +# username: '{{ mso_username }}' +# password: '{{ mso_password }}' +# validate_certs: '{{ mso_validate_certs | default(false) }}' +# use_ssl: '{{ mso_use_ssl | default(true) }}' +# use_proxy: '{{ mso_use_proxy | default(true) }}' +# output_level: '{{ mso_output_level | default("info") }}' +# site: '{{ mso_site | default("ansible_test") }}' +# apic_username: '{{ apic_username }}' +# apic_password: '{{ apic_password }}' +# apic_site_id: '{{ apic_site_id | default(101) }}' +# urls: +# - https://{{ apic_hostname }} +# state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure Filter 1 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1-Entry + state: present + +- name: Ensure Contract1 exist + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +# ADD VRF1 +- name: Add a new VRF1 (check mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + check_mode: true + register: vrf1_cm + +- name: Verify vrf1_cm + assert: + that: + - vrf1_cm is changed + - vrf1_cm.current.name == 'VRF1' + - vrf1_cm.current.displayName == 'VRF1' + +- name: Add VRF1 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + state: present + register: vrf1_nm + +- name: Verify vrf1_nm + assert: + that: + - vrf1_nm is changed + - vrf1_nm.current.name == 'VRF1' + - vrf1_nm.current.displayName == 'VRF1' + +- name: Add VRF2 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF2 + layer3_multicast: true + vzany: true + state: present + register: vrf2_nm + +- name: Verify vrf2_nm + assert: + that: + - vrf2_nm is changed + - vrf2_nm.current.name == 'VRF2' + - vrf2_nm.current.displayName == 'VRF2' + - vrf2_nm.current.vzAnyEnabled == True + - vrf2_nm.current.l3MCast == True + +- name: Add VRF3 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF3 + state: present + register: vrf3_nm + +- name: Verify vrf3_nm + assert: + that: + - vrf3_nm is changed + - vrf3_nm.current.name == 'VRF3' + - vrf3_nm.current.displayName == 'VRF3' + +- name: Add preferred_group to VRF3 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF3 + preferred_group: true + state: present + register: vrf3_nm_change + +- name: Verify vrf3_nm_change + assert: + that: + - vrf3_nm_change is changed + - vrf3_nm_change.current.name == 'VRF3' + - vrf3_nm_change.current.preferredGroup == True + +# ADD EXISTING VRF +- name: Add VRF2 again (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF2 + layer3_multicast: true + vzany: true + state: present + register: vrf2_nm_again + +- name: Verify vrf2_nm_again + assert: + that: + - vrf2_nm_again is not changed + +# CHANGE VRF SETTINGS +- name: Change VRF1 settings (check mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + layer3_multicast: true + vzany: true + state: present + check_mode: true + register: vrf1_change_cm + +- name: Change VRF1 settings (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF1 + layer3_multicast: true + vzany: true + state: present + register: vrf1_change_nm + +- name: Change VRF2 settings (check mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF2 + layer3_multicast: false + vzany: false + preferred_group: true + ip_data_plane_learning: disabled + state: present + check_mode: true + register: vrf2_change_cm + +- name: Change VRF2 settings (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF2 + layer3_multicast: false + vzany: false + preferred_group: true + ip_data_plane_learning: disabled + state: present + register: vrf2_change_nm + +- name: Verify vrf2_nm + assert: + that: + - vrf1_change_cm is changed + - vrf1_change_nm is changed + - vrf2_change_cm is changed + - vrf2_change_nm is changed + - vrf1_change_cm.current.name == vrf1_change_nm.current.name == 'VRF1' + - vrf2_change_cm.current.name == vrf2_change_nm.current.name == 'VRF2' + - vrf1_change_cm.current.vzAnyEnabled == vrf1_change_nm.current.vzAnyEnabled == True + - vrf1_change_cm.current.vzAnyEnabled == vrf1_change_nm.current.l3MCast == True + - vrf2_change_cm.current.vzAnyEnabled == vrf2_change_nm.current.vzAnyEnabled == False + - vrf2_change_cm.current.vzAnyEnabled == vrf2_change_nm.current.l3MCast == False + - vrf2_change_cm.current.preferredGroup == vrf2_change_nm.current.preferredGroup == True + - vrf2_change_cm.current.ipDataPlaneLearning == vrf2_change_nm.current.ipDataPlaneLearning == 'disabled' + +# ADD VRF4 WITH NO STATE +- name: Add VRF4 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF4 + ignore_errors: true + register: vrf4_nm_stateless + +- name: Verify vrf4_nm_stateless + assert: + that: + - vrf4_nm_stateless is changed + +# QUERY A VRF +- name: Query VRF2 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF2 + state: query + register: vrf2_query + +- name: Verify vrf2_query + assert: + that: + - vrf2_query is not changed + +# QUERY ALL VRFs +- name: Query all (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: vrfs_query + +- name: Verify vrfs_query + assert: + that: + - vrfs_query is not changed + +# REMOVE A VRF +- name: Remove VRF3 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF3 + state: absent + register: vrf3_remove + +- name: Verify vrf3_remove + assert: + that: + - vrf3_remove is changed + - vrf3_remove.previous.name == 'VRF3' + - vrf3_remove.previous.displayName == 'VRF3' + +# REMOVE A VRF +- name: Remove VRF3 again (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF3 + state: absent + register: vrf3_remove_again + +- name: Verify vrf3_remove_again + assert: + that: + - vrf3_remove_again is not changed + +# QUERY REMOVED VRF +- name: Query VRF3 (normal mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF3 + state: query + ignore_errors: true + register: vrf3_query_removed + +- name: Verify vrf3_query_removed + assert: + that: + - vrf3_query_removed.msg == "VRF 'VRF3' not found" + +# Enable vzAny on VRF +- name: Ensure VRF exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + vzany: true + state: present + +- name: Add Contract1 to VRF with type consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: present + register: nm_add_contract1_consumer + +- name: Verify nm_add_contract1_consumer + assert: + that: + - nm_add_contract1_consumer is changed + - nm_add_contract1_consumer.previous == {} + - nm_add_contract1_consumer.current.contractRef.templateName == "Template1" + - nm_add_contract1_consumer.current.contractRef.contractName == "Contract1" + - nm_add_contract1_consumer.current.relationshipType == "consumer" + +- name: Add Contract1 to VRF with type provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + state: present + register: nm_add_contract1_provider + +- name: Verify nm_add_contract1_provider + assert: + that: + - nm_add_contract1_provider is changed + - nm_add_contract1_provider.previous == {} + - nm_add_contract1_provider.current.contractRef.templateName == "Template1" + - nm_add_contract1_provider.current.contractRef.contractName == "Contract1" + - nm_add_contract1_provider.current.relationshipType == "provider" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for VRF (check_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: non_existing_schema + template: Template 1 + vrf: VRF5 + check_mode: true + ignore_errors: true + register: cm_non_existing_schema + +- name: Non-existing schema for VRF (normal_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: non_existing_schema + template: Template 1 + vrf: VRF5 + ignore_errors: true + register: nm_non_existing_schema + +- name: Verify nm_non_existing_schema + assert: + that: + - cm_non_existing_schema is not changed + - nm_non_existing_schema is not changed + - cm_non_existing_schema == nm_non_existing_schema + - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing template for vrf (check_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + vrf: VRF5 + check_mode: true + ignore_errors: true + register: cm_non_existing_template + +- name: Non-existing template for vrf (normal_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + vrf: VRF5 + ignore_errors: true + register: nm_non_existing_template + +- name: Verify non_existing_template + assert: + that: + - cm_non_existing_template is not changed + - nm_non_existing_template is not changed + - cm_non_existing_template == nm_non_existing_template + - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +# Checking if contract are removed after re-applying an VRF. +- name: Add VRF again (normal_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + vzany: true + state: present + register: nm_add_VRF_again + +- name: Verify that VRF didn't changed + assert: + that: + - nm_add_VRF_again is not changed + +- name: Verify contract VRF again + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: query + register: nm_query_vrf_contract_again + +- name: Verify 2 contracts are in VRF + assert: + that: + - nm_query_vrf_contract_again is not changed + - nm_query_vrf_contract_again.current | length == 2 + +# Checking if modifying VRF with existing contracts throw an MSO error. (#82) +- name: Change VRF (normal_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + vzany: true + layer3_multicast: true + state: present + register: nm_change_vrf + +- name: Verify that VRF did change + assert: + that: + - nm_change_vrf is changed + - nm_change_vrf.current.name == "VRF" + - nm_change_vrf.current.l3MCast == True + +- name: Verify contract VRF again + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: query + register: nm_query_change_vrf_contract_again + +- name: Verify 2 contracts are in VRF + assert: + that: + - nm_query_change_vrf_contract_again is not changed + - nm_query_change_vrf_contract_again.current | length == 2 diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml new file mode 100644 index 000000000..3a5f816a1 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml @@ -0,0 +1,859 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# - name: Ensure site exist +# mso_site: &site_present +# host: '{{ mso_hostname }}' +# username: '{{ mso_username }}' +# password: '{{ mso_password }}' +# validate_certs: '{{ mso_validate_certs | default(false) }}' +# use_ssl: '{{ mso_use_ssl | default(true) }}' +# use_proxy: '{{ mso_use_proxy | default(true) }}' +# output_level: '{{ mso_output_level | default("info") }}' +# site: '{{ mso_site | default("ansible_test") }}' +# apic_username: '{{ apic_username }}' +# apic_password: '{{ apic_password }}' +# apic_site_id: '{{ apic_site_id | default(101) }}' +# urls: +# - https://{{ apic_hostname }} +# state: present + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exist + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + # sites: + # - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + +- name: Ensure schema 1 with Template 2 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 2 + state: present + +- name: Ensure schema 2 with Template 3 exist + mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 3 + state: present + +- name: Ensure VRF exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: present + +- name: Ensure VRF2 exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + vzany: true + state: present + +- name: Ensure VRF3 exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF3 + vzany: true + state: present + +- name: Ensure VRF4 exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF4 + vzany: true + state: present + +- name: Ensure Filter 1 exist + mso_schema_template_filter_entry: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + filter: Filter1 + entry: Filter1-Entry + state: present + +- name: Ensure Contract1 exist + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract1 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Contract4 exist + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract4 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Contract2 exist + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract2 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +- name: Ensure Contract3 exist + mso_schema_template_contract_filter: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + contract: Contract3 + filter: Filter1 + filter_schema: '{{ mso_schema | default("ansible_test") }}' + filter_template: Template 1 + state: present + +# ADD Contract to VRF +- name: Add Contract1 to VRF with vzany disabled + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: present + ignore_errors: true + register: add_contract1_vrf_vzany_disabled + +- name: Verify add_contract1_vrf_vzany_disabled + assert: + that: + - add_contract1_vrf_vzany_disabled.msg == "vzAny attribute on vrf 'VRF' is disabled." + +# Enable vzAny on VRF +- name: Ensure VRF exist + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + vzany: true + state: present + +- name: Add Contract1 to VRF with type consumer (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: present + check_mode: true + register: cm_add_contract1_consumer + +- name: Verify cm_add_contract1_consumer + assert: + that: + - cm_add_contract1_consumer is changed + - cm_add_contract1_consumer.previous == {} + - cm_add_contract1_consumer.current.contractRef.templateName == "Template1" + - cm_add_contract1_consumer.current.contractRef.contractName == "Contract1" + - cm_add_contract1_consumer.current.relationshipType == "consumer" + +- name: Add Contract1 to VRF with type consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: present + register: nm_add_contract1_consumer + +- name: Verify nm_add_contract1_consumer + assert: + that: + - nm_add_contract1_consumer is changed + - nm_add_contract1_consumer.previous == {} + - nm_add_contract1_consumer.current.contractRef.templateName == "Template1" + - nm_add_contract1_consumer.current.contractRef.contractName == "Contract1" + - nm_add_contract1_consumer.current.relationshipType == "consumer" + - cm_add_contract1_consumer.current.contractRef.schemaId == nm_add_contract1_consumer.current.contractRef.schemaId + +- name: Add Contract1 to VRF with type provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + state: present + register: nm_add_contract1_provider + +- name: Verify nm_add_contract1_provider + assert: + that: + - nm_add_contract1_provider is changed + - nm_add_contract1_provider.previous == {} + - nm_add_contract1_provider.current.contractRef.templateName == "Template1" + - nm_add_contract1_provider.current.contractRef.contractName == "Contract1" + - nm_add_contract1_provider.current.relationshipType == "provider" + +- name: Add Contract1 to VRF with type consumer again(normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: present + register: nm_add_contract1_consumer_again + +- name: Verify nm_add_contract1_consumer_again + assert: + that: + - nm_add_contract1_consumer_again is not changed + - nm_add_contract1_consumer_again.current.contractRef.templateName == "Template1" == nm_add_contract1_consumer_again.previous.contractRef.templateName + - nm_add_contract1_consumer_again.current.contractRef.contractName == "Contract1" == nm_add_contract1_consumer_again.previous.contractRef.contractName + - nm_add_contract1_consumer_again.current.relationshipType == "consumer" == nm_add_contract1_consumer_again.previous.relationshipType + +- name: Add Contract1 to VRF with type provider again(normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + state: present + register: nm_add_contract1_provider_again + +- name: Verify nm_add_contract1_provider_again + assert: + that: + - nm_add_contract1_provider_again is not changed + - nm_add_contract1_provider_again.current.contractRef.templateName == "Template1" == nm_add_contract1_provider_again.previous.contractRef.templateName + - nm_add_contract1_provider_again.current.contractRef.contractName == "Contract1" == nm_add_contract1_provider_again.previous.contractRef.contractName + - nm_add_contract1_provider_again.current.relationshipType == "provider" == nm_add_contract1_provider_again.previous.relationshipType + +- name: Add Contract4 to VRF2 with type consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + contract: + name: Contract4 + type: consumer + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_vrf2_consumer + +- name: Add Contract4 to VRF2 with type provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + contract: + name: Contract4 + type: provider + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_vrf2_provider + +- name: nm_add_vrf2_consumer and nm_add_vrf2_provider + assert: + that: + - nm_add_vrf2_consumer is changed + - nm_add_vrf2_provider is changed + - nm_add_vrf2_consumer.previous == {} == nm_add_vrf2_provider.previous + - nm_add_vrf2_consumer.current.contractRef.templateName == "Template1" == nm_add_vrf2_provider.current.contractRef.templateName + - nm_add_vrf2_consumer.current.contractRef.contractName == "Contract4" == nm_add_vrf2_provider.current.contractRef.contractName + - nm_add_vrf2_consumer.current.relationshipType == "consumer" + - nm_add_vrf2_provider.current.relationshipType == "provider" + +- name: Add Contract3 to VRF3 with type consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF3 + contract: + name: Contract3 + type: consumer + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_vrf3_consumer + +- name: Add Contract3 to VRF3 with type provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF3 + contract: + name: Contract3 + type: provider + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_vrf3_provider + +- name: nm_add_vrf3_consumer and nm_add_vrf3_provider + assert: + that: + - nm_add_vrf3_consumer is changed + - nm_add_vrf3_provider is changed + - nm_add_vrf3_consumer.previous == {} == nm_add_vrf3_provider.previous + - nm_add_vrf3_consumer.current.contractRef.templateName == "Template1" == nm_add_vrf3_provider.current.contractRef.templateName + - nm_add_vrf3_consumer.current.contractRef.contractName == "Contract3" == nm_add_vrf3_provider.current.contractRef.contractName + - nm_add_vrf3_consumer.current.relationshipType == "consumer" + - nm_add_vrf3_provider.current.relationshipType == "provider" + +- name: Add Contract2 to VRF4 with type consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF4 + contract: + name: Contract2 + type: consumer + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_vrf4_consumer + +- name: Add Contract2 to VRF4 with type provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 3 + vrf: VRF4 + contract: + name: Contract2 + type: provider + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: present + register: nm_add_vrf4_provider + +- name: nm_add_vrf4_consumer and nm_add_vrf4_provider + assert: + that: + - nm_add_vrf4_consumer is changed + - nm_add_vrf4_provider is changed + - nm_add_vrf4_consumer.previous == {} == nm_add_vrf4_provider.previous + - nm_add_vrf4_consumer.current.contractRef.templateName == "Template1" == nm_add_vrf4_provider.current.contractRef.templateName + - nm_add_vrf4_consumer.current.contractRef.contractName == "Contract2" == nm_add_vrf4_provider.current.contractRef.contractName + - nm_add_vrf4_consumer.current.relationshipType == "consumer" + - nm_add_vrf4_provider.current.relationshipType == "provider" + +# REMOVE A Contract to VRF +- name: Remove contract4 to VRF2 - provider (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + contract: + name: Contract4 + type: provider + state: absent + check_mode: true + register: cm_remove_contract4_vrf2_provider + +- name: Remove contract4 to VRF2 - provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + contract: + name: Contract4 + type: provider + state: absent + register: nm_remove_contract4_vrf2_provider + +- name: Remove contract4 to VRF2 - consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + contract: + name: Contract4 + type: consumer + state: absent + register: nm_remove_contract4_vrf2_consumer + +- name: Verify cm_remove_contract4_vrf2_provider and cm_remove_contract4_vrf2_provider and nm_remove_contract4_vrf2_consumer + assert: + that: + - cm_remove_contract4_vrf2_provider is changed + - nm_remove_contract4_vrf2_provider is changed + - cm_remove_contract4_vrf2_provider.current == {} + - nm_remove_contract4_vrf2_provider.current == {} + - nm_remove_contract4_vrf2_consumer.current == {} + - cm_remove_contract4_vrf2_provider.previous.contractRef.contractName == nm_remove_contract4_vrf2_provider.previous.contractRef.contractName == nm_remove_contract4_vrf2_consumer.previous.contractRef.contractName == "Contract4" + - cm_remove_contract4_vrf2_provider.previous.contractRef.templateName == nm_remove_contract4_vrf2_provider.previous.contractRef.templateName == nm_remove_contract4_vrf2_consumer.previous.contractRef.templateName == "Template1" + - cm_remove_contract4_vrf2_provider.previous.contractRef.schemaId == nm_remove_contract4_vrf2_provider.previous.contractRef.schemaId == nm_remove_contract4_vrf2_consumer.previous.contractRef.schemaId + - cm_remove_contract4_vrf2_provider.previous.relationshipType == "provider" + - nm_remove_contract4_vrf2_provider.previous.relationshipType == "provider" + - nm_remove_contract4_vrf2_consumer is changed + - nm_remove_contract4_vrf2_consumer.previous.relationshipType == "consumer" + +- name: Remove contract4 to VRF2 - provider again (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + contract: + name: Contract4 + type: provider + state: absent + register: nm_remove_contract4_vrf2_provider_again + +- name: Verify nm_remove_contract4_vrf2_provider_again + assert: + that: + - nm_remove_contract4_vrf2_provider_again is not changed + - nm_remove_contract4_vrf2_provider_again.previous == {} + - nm_remove_contract4_vrf2_provider_again.current == {} + +# QUERY A Contract to VRF +- name: Query Contract1 relationship for VRF - consumer (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: query + check_mode: true + register: cm_query_VRF_contract1_consumer + +- name: Query Contract1 relationship for VRF - consumer (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: consumer + state: query + check_mode: true + register: nm_query_VRF_contract1_consumer + +- name: Verify cm_query_VRF_contract1_consumer and nm_query_VRF_contract1_consumer + assert: + that: + - cm_query_VRF_contract1_consumer is not changed + - nm_query_VRF_contract1_consumer is not changed + - cm_query_VRF_contract1_consumer.current.relationshipType == nm_query_VRF_contract1_consumer.current.relationshipType == "consumer" + - cm_query_VRF_contract1_consumer.current.contractRef.contractName == nm_query_VRF_contract1_consumer.current.contractRef.contractName == "Contract1" + - cm_query_VRF_contract1_consumer.current.contractRef.schemaId == nm_query_VRF_contract1_consumer.current.contractRef.schemaId + - cm_query_VRF_contract1_consumer.current.contractRef.templateName == nm_query_VRF_contract1_consumer.current.contractRef.templateName == "Template1" + +- name: Query Contract1 relationship for VRF - provider (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + state: query + check_mode: true + register: cm_query_VRF_contract1_provider + +- name: Query Contract1 relationship for VRF - provider (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + state: query + check_mode: true + register: nm_query_VRF_contract1_provider + +- name: Verify cm_query_VRF_contract1_provider and nm_query_VRF_contract1_provider + assert: + that: + - cm_query_VRF_contract1_provider is not changed + - nm_query_VRF_contract1_provider is not changed + - cm_query_VRF_contract1_provider.current.relationshipType == nm_query_VRF_contract1_provider.current.relationshipType == "provider" + - cm_query_VRF_contract1_provider.current.contractRef.contractName == nm_query_VRF_contract1_provider.current.contractRef.contractName == "Contract1" + - cm_query_VRF_contract1_provider.current.contractRef.schemaId == nm_query_VRF_contract1_provider.current.contractRef.schemaId + - cm_query_VRF_contract1_provider.current.contractRef.templateName == nm_query_VRF_contract1_provider.current.contractRef.templateName == "Template1" + +# QUERY ALL Contract to VRF +- name: Query all contracts relationship for VRF (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: query + check_mode: true + register: cm_query_all_contract_vrf + +- name: Query all contracts relationship for VRF (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: query + register: nm_query_all_contract_vrf + +- name: Verify cm_query_all_contract_vrf and nm_query_all_contract_vrf + assert: + that: + - nm_query_all_contract_vrf is not changed + - cm_query_all_contract_vrf is not changed + - cm_query_all_contract_vrf.current | length == nm_query_all_contract_vrf.current | length == 2 + +# QUERY ALL Contracts to VRF2 +- name: Query all contracts relationship for VRF2 (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: query + check_mode: true + register: cm_query_all_contract_vrf2 + +- name: Query all contracts relationship for VRF2 (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF2 + state: query + register: nm_query_all_contract_vrf2 + +- name: Verify cm_query_all_contract_vrf2 and nm_query_all_contract_vrf2 + assert: + that: + - nm_query_all_contract_vrf2 is not changed + - cm_query_all_contract_vrf2 is not changed + - cm_query_all_contract_vrf2.current == nm_query_all_contract_vrf2.current == [] + +# QUERY NON-EXISTING Contract to VRF +- name: Query non-existing contract (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: non_existing_contract + type: provider + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_existing_contract + +- name: Query non-existing contract (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: non_existing_contract + type: provider + state: query + ignore_errors: true + register: nm_query_non_existing_contract + +- name: Verify cm_query_non_existing_contract and nm_query_non_existing_contract + assert: + that: + - cm_query_non_existing_contract is not changed + - nm_query_non_existing_contract is not changed + - cm_query_non_existing_contract == nm_query_non_existing_contract + - cm_query_non_existing_contract.msg == "Contract 'non_existing_contract' not found" + - nm_query_non_existing_contract.msg == "Contract 'non_existing_contract' not found" + +# QUERY NON-EXISTING VRF +- name: Query non-existing VRF (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: non_existing_vrf + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_existing_vrf + +- name: Query non-existing VRF (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: non_existing_vrf + state: query + ignore_errors: true + register: nm_query_non_existing_vrf + +- name: Verify cm_query_non_existing_vrf and nm_query_non_existing_vrf + assert: + that: + - cm_query_non_existing_vrf is not changed + - nm_query_non_existing_vrf is not changed + - cm_query_non_existing_vrf == nm_query_non_existing_vrf + - cm_query_non_existing_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF, VRF2" + - nm_query_non_existing_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF, VRF2" + +# USE A NON-EXISTING SCHEMA +- name: Non-existing schema for contract relationship (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: non_existing_schema + template: Template 1 + vrf: VRF + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_existing_schema + +- name: Non-existing schema for contract relationship (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: non_existing_schema + template: Template 1 + vrf: VRF + state: query + ignore_errors: true + register: nm_query_non_existing_schema + +- name: Verify cm_query_non_existing_schema and nm_query_non_existing_schema + assert: + that: + - cm_query_non_existing_schema is not changed + - nm_query_non_existing_schema is not changed + - cm_query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + - nm_query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +- name: Non-existing contract schema for contract relationship (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + schema: non_existing_schema + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_existing_contract_schema + +- name: Non-existing schema for contract relationship (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + schema: non_existing_schema + state: query + ignore_errors: true + register: nm_query_non_existing_contract_schema + +- name: Verify cm_query_non_existing_contract_schema and nm_query_non_existing_contract_schema + assert: + that: + - cm_query_non_existing_contract_schema is not changed + - nm_query_non_existing_contract_schema is not changed + - cm_query_non_existing_contract_schema.msg == "Provided schema 'non_existing_schema' does not exist." + - nm_query_non_existing_contract_schema.msg == "Provided schema 'non_existing_schema' does not exist." + +# USE A NON-EXISTING TEMPLATE +- name: Non-existing templateName for contract relationship (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + vrf: VRF + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_existing_template + +- name: Non-existing templateName for contract relationship (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + vrf: VRF + state: query + ignore_errors: true + register: nm_query_non_existing_template + +- name: Verify cm_query_non_existing_template and nm_query_non_existing_template + assert: + that: + - cm_query_non_existing_template is not changed + - nm_query_non_existing_template is not changed + - cm_query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + - nm_query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2" + +- name: Non-existing contract templateName for contract relationship (check_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + state: query + check_mode: true + ignore_errors: true + register: cm_query_non_existing_contract_template + +- name: Non-existing contract templateName for contract relationship (normal_mode) + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + contract: + name: Contract1 + type: provider + schema: '{{ mso_schema | default("ansible_test") }}' + template: non_existing_template + state: query + ignore_errors: true + register: nm_query_non_existing_contract_template + +- name: Verify cm_query_non_existing_contract_template and nm_query_non_existing_contract_template + assert: + that: + - cm_query_non_existing_contract_template is not changed + - nm_query_non_existing_contract_template is not changed + - cm_query_non_existing_contract_template.msg == "Contract 'Contract1' not found" + - nm_query_non_existing_contract_template.msg == "Contract 'Contract1' not found" + +# Checking if contract are removed after re-applying an VRF. +- name: Add VRF again (normal_mode) + mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + vzany: true + state: present + register: nm_add_VRF_again + +- name: Verify that VRF didn't changed + assert: + that: + - nm_add_VRF_again is not changed + +- name: Verify contract VRF again + mso_schema_template_vrf_contract: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF + state: query + register: nm_query_vrf_contract_again + +- name: Verify 2 contracts are in VRF + assert: + that: + - nm_query_vrf_contract_again is not changed + - nm_query_vrf_contract_again.current | length == 2
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml new file mode 100644 index 000000000..9de7d1fb1 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml @@ -0,0 +1,251 @@ +# Test code for the MSO modules +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exist + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Undeploy template from Schema 1 + cisco.mso.mso_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + +- name: Undeploy template from Schema 1 + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + sites: + - '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +# Validate schema when MSO version >= 3.3 +- name: Execute tasks only for MSO version >= 3.3 + when: version.current.version is version('3.3', '>=') + block: + - name: Ensure schema 1 with Template 1 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: Template 1 + state: present + + - name: Ensure VRF exist + cisco.mso.mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + vrf: VRF_1 + state: present + + - name: Add bd + cisco.mso.mso_schema_template_bd: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template1 + bd: BD_1 + vrf: + name: VRF_1 + state: present + + - name: Get Validation status + cisco.mso.mso_schema_validate: + <<: *mso_info + schema: ansible_test + state: query + register: query_validate + + - name: Verify query_validate for NDO 4.1 and higher + ansible.builtin.assert: + that: + - query_validate is not changed + - query_validate.current.result == true + when: version.current.version is version('4.0', '>=') + + - name: Verify query_validate + ansible.builtin.assert: + that: + - query_validate is not changed + - query_validate.current.result == "true" + when: version.current.version is version('4.0', '<') + + - name: Add physical site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + + - name: Get Validation status + cisco.mso.mso_schema_validate: + <<: *mso_info + schema: ansible_test + state: query + register: query_validate_again + + - name: Verify query_validate_again for NDO 4.1 and higher + ansible.builtin.assert: + that: + - query_validate_again is not changed + - query_validate_again.current.result == true + when: version.current.version is version('4.0', '>=') + + - name: Verify query_validate_again for NDO 3.7 and lower + ansible.builtin.assert: + that: + - query_validate_again is not changed + - query_validate_again.current.result == "true" + when: version.current.version is version('4.0', '<') + + - name: Deploy templates for NDO 3.7 and lower (normal_mode) + cisco.mso.mso_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + site: '{{ mso_site | default("ansible_test") }}' + state: deploy + register: nm_deploy_template_37 + when: version.current.version is version('4.0', '<') + + - name: Deploy templates for NDO 4.1 and higher (normal_mode) + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + sites: + - '{{ mso_site | default("ansible_test") }}' + state: deploy + register: nm_deploy_template_40 + when: version.current.version is version('4.0', '>=') + + - name: Verify nm_deploy_template for NDO 3.7 and lower + ansible.builtin.assert: + that: + - nm_deploy_template_37 is not changed + - nm_deploy_template_37.msg == "Successfully deployed" + when: version.current.version is version('4.0', '<') + + - name: Verify nm_deploy_template for NDO 4.1 and higher + ansible.builtin.assert: + that: + - '"deploy" in nm_deploy_template_40.current.reqDetails' + when: version.current.version is version('4.0', '>=') + + - name: Ensure schema 2 with Template 2 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + tenant: ansible_test + template: Template 2 + state: present + + - name: Ensure VRF exist + cisco.mso.mso_schema_template_vrf: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}_2' + template: Template 2 + vrf: VRF_2 + layer3_multicast: true + vzany: true + state: present + + - name: Get Validation status + cisco.mso.mso_schema_validate: + <<: *mso_info + schema: ansible_test_2 + state: query + ignore_errors: true + register: query_validate_2 + + - name: Verify query_validate_2 for NDO 4.1 and higher + ansible.builtin.assert: + that: + - query_validate_2 is not changed + - query_validate_2.msg == "MSO Error 400{{':'}} VRF{{':'}} VRF_2 in Schema{{':'}} ansible_test_2 , Template{{':'}} Template2 has VzAnyEnabled flag enabled but is not consuming or providing contracts" + when: version.current.version is version('4.0', '>=') + + - name: Verify query_validate_2 for NDO 3.7 and lower + ansible.builtin.assert: + that: + - query_validate_2 is not changed + - query_validate_2.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF_2 exception while trying to update schema" + when: version.current.version is version('4.0', '<') + + always: + - name: Undeploy template from Schema 1 + cisco.mso.mso_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + site: '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + + - name: Undeploy template from Schema 1 + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + sites: + - '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml new file mode 100644 index 000000000..d6b25df94 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml @@ -0,0 +1,199 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(false) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Remove existing nodes added during test + cisco.mso.mso_service_node_type: + <<: *mso_info + name: '{{ item }}' + state: absent + loop: + - TEST1 + - TEST2 + +- name: Add a new node type (check_mode) + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + display_name: test + state: present + register: node_cm + check_mode: true + +- name: Verify node_cm + assert: + that: + - node_cm is changed + - node_cm.current.displayName == "test" + - node_cm.current.name == "TEST1" + +- name: Add a new node type (normal mode) + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + display_name: test + state: present + register: node1 + +- name: Verify node1 + assert: + that: + - node1 is changed + - node1.current.displayName == "test" + - node1.current.name == "TEST1" + +- name: Add another node type + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST2 + state: present + register: node2 + +- name: Verify node2 + assert: + that: + - node2 is changed + - node2.current.displayName == "TEST2" + - node2.current.name == "TEST2" + +- name: Add node type TEST2 again + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST2 + state: present + register: node2_again + +- name: Verify node2_again + assert: + that: + - node2_again is not changed + - node2_again.current.displayName == "TEST2" + - node2_again.current.name == "TEST2" + +- name: Add TEST1 again with a different display name + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + display_name: change_test + state: present + register: node2_different_display_name + ignore_errors: true + +- name: Verify node2_different_display_name + assert: + that: + - node2_different_display_name.msg == "Service Node Type 'TEST1' already exists with display name 'test' which is different from provided display name 'change_test'." + +- name: Query a node type + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + state: query + register: query_node1 + +- name: Verify query_node1 + assert: + that: + - query_node1 is not changed + - query_node1.current.displayName == "test" + - query_node1.current.name == "TEST1" + +- name: Query all node types + cisco.mso.mso_service_node_type: + <<: *mso_info + state: query + register: query_all + +- name: Verify query_all + assert: + that: + - query_all is not changed + - query_all.current | length >= 4 + +- name: Remove a node type (check_mode) + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + state: absent + check_mode: true + register: cm_rm + +- name: Verify cm_rm + assert: + that: + - cm_rm is changed + - cm_rm.previous.name == "TEST1" + +- name: Remove a node type (normal_mode) + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + state: absent + register: rm_node1 + +- name: Verify rm_node1 + assert: + that: + - rm_node1 is changed + - rm_node1.current == {} + - rm_node1.previous.name == "TEST1" + +- name: Query absent node type + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST1 + state: query + register: query_absent + ignore_errors: true + +- name: Verify query_absent + assert: + that: + - query_absent.msg == "Service Node Type 'TEST1' not found" + +- name: Remove another node type + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST2 + state: absent + register: rm_node2 + +- name: Verify rm_node2 + assert: + that: + - rm_node2 is changed + - rm_node2.current == {} + - rm_node2.previous.name == "TEST2" + +- name: Remove node type again + cisco.mso.mso_service_node_type: + <<: *mso_info + name: TEST2 + state: absent + register: rm_node2_again + +- name: Verify rm_node2_again + assert: + that: + - rm_node2_again is not changed + - rm_node2_again.current == {} + - rm_node2_again.previous == {} diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2 b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2 new file mode 100644 index 000000000..bb8804324 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2 @@ -0,0 +1,489 @@ +{ + "siteGroup": { + "name": "default", + "common": { + "peeringType": "full-mesh", + "ttl": 16, + "keepAliveInterval": 60, + "holdInterval": 180, + "staleInterval": 300, + "gracefulRestartEnabled": true, + "maxAsLimit": 0, + "externalSubnetPools": [ + "169.254.0.0/16", + "10.104.0.0/16", + "20.253.0.0/16" + ] + }, + "dcnm": { + "l2VniRange": "130000-149000", + "l3VniRange": "150000-159000", + "msiteAnycastTepPool": "10.10.0.0/24", + "msiteAnycastMac": "2020.0000.00aa", + "routeTargetPrefix": 23456, + "peeringType": "" + }, + "apic": { + "gracefulRestartEnabled": false, + "ipns": [ + { + "name": "test", + "ip": "2.2.2.2" + } + ], + "cloudsecUdpPortCtrl": false + }, + "capic": { + "ospfAreaID": "0.0.0.0" + }, + "externalDevices": {} + }, + "sites": [ + { + "id": "{{ site_dict.azure_ansible_test.id }}", + "siteId": "{{ azure_site_id }}", + "siteGroupId": "{{ site_dict.azure_ansible_test.site_group_id }}", + "siteType": "CloudApic", + "bgpAsn": 64701, + "msiteEnabled": true, + "health": {}, + "apic": { + "srgbRange": {} + }, + "dcnm": { + "fabricType": "" + }, + "capic": { + "regions": [ + { + "name": "westus", + "cloudRouters": [ + { + "name": "ct_routerp_westus_1", + "routerType": "CSR", + "msiteControlPlaneTep": "10.253.254.116/28", + "bgpPeeringEnabled": true, + "routeReflectorEnabled": false + }, + { + "name": "ct_routerp_westus_0", + "routerType": "CSR", + "bgpPeeringEnabled": true, + "routeReflectorEnabled": false + } + ], + "cApicDeployed": true + }, + { + "name": "westus2", + "cloudRouters": [ + { + "name": "ct_routerp_westus2_0", + "routerType": "CSR", + "bgpPeeringEnabled": true, + "routeReflectorEnabled": false + }, + { + "name": "ct_routerp_westus2_1", + "routerType": "CSR", + "bgpPeeringEnabled": true, + "routeReflectorEnabled": false + } + ] + } + ] + }, + "status": { + "state": "success" + }, + "deployed": true + }, + { + "id": "{{ site_dict.aws_ansible_test.id }}", + "siteId": "{{ aws_site_id }}", + "siteGroupId": "{{ site_dict.aws_ansible_test.site_group_id }}", + "siteType": "CloudApic", + "bgpAsn": 200, + "msiteEnabled": true, + "health": {}, + "apic": { + "srgbRange": {} + }, + "dcnm": { + "fabricType": "" + }, + "capic": { + "regions": [ + { + "name": "us-west-1", + "cloudRouters": [], + "cApicDeployed": true + }, + { + "name": "us-east-1", + "cloudRouters": [] + }, + { + "name": "us-east-2", + "cloudRouters": [] + }, + { + "name": "us-west-2", + "cloudRouters": [] + } + ] + }, + "status": { + "state": "success" + }, + "deployed": true + }, + { + "id": "{{ site_dict.ansible_test.id }}", + "siteId": "{{ apic_site_id }}", + "siteGroupId": "{{ site_dict.ansible_test.site_group_id }}", + "siteType": "Apic", + "bgpAsn": 100, + "msiteEnabled": true, + "ospfAreaID": "0.0.0.1", + "ospfAreaType": "nssa", + "ospfPolicies": [ + { + "name": "default", + "networkType": "broadcast", + "priority": 1, + "interfaceCost": 0, + "interfaceControls": [], + "helloInterval": 10, + "deadInterval": 40, + "retransmitInterval": 5, + "transmitDelay": 1 + }, + { + "name": "msc-ospf-policy-default", + "networkType": "point-to-point", + "priority": 1, + "interfaceCost": 0, + "interfaceControls": [], + "helloInterval": 10, + "deadInterval": 40, + "retransmitInterval": 5, + "transmitDelay": 1 + }, + { + "name": "common/default", + "networkType": "unspecified", + "priority": 1, + "interfaceCost": 0, + "interfaceControls": [], + "helloInterval": 10, + "deadInterval": 40, + "retransmitInterval": 5, + "transmitDelay": 1 + } + ], + "health": {}, + "apic": { + "fabricID": 1, + "dpMcTep": "1.1.1.2", + "extRoutedDom": "uni/l3dom-L3out_Dom", + "srgbRange": {}, + "pods": [ + { + "podId": 1, + "name": "pod-1", + "msiteDataPlaneUnicastTep": "1.1.1.1", + "spines": [ + { + "nodeId": 201, + "name": "lh-dmz1-spine201", + "ports": [ + { + "portId": "1/5", + "ipAddress": "10.1.0.202/12", + "mtu": "inherit", + "routingPolicy": "default", + "ospfAuthType": "none", + "ospfAuthKeyId": 1, + "bgpPeer": { + "ttl": 1, + "adminStateEnabled": false + } + } + ], + "bgpPeeringEnabled": true, + "msiteControlPlaneTep": "1.2.2.2", + "routeReflectorEnabled": false, + "health": {} + } + ], + "msiteDataPlaneRoutableTEPPools": [ + { + "pool": "192.168.1.0/24", + "reserveAddressCount": 2 + } + ], + "health": {}, + "podFabricTepPools": [ + { + "pool": "10.0.0.0/16" + } + ] + } + ] + }, + "dcnm": { + "fabricType": "" + }, + "capic": {}, + "status": { + "state": "success" + }, + "deployed": true + } + ], + "sitesUc": [ + { + "id": "{{ site_dict.azure_ansible_test.id }}", + "siteId": "{{ azure_site_id }}", + "siteType": "CloudApic", + "apic": {}, + "capic": { + "provider": "Azure", + "accountID": "85ca999d-c9c7-484b-82b8-6854bc1e2af5", + "regions": [ + { + "regionName": "westus", + "cloudProviderID": "/subscriptions/85ca999d-c9c7-484b-82b8-6854bc1e2af5/resourceGroups/cAPIC-02/providers/Microsoft.Network/virtualNetworks/overlay-1", + "cidrs": [ + "10.253.253.128/25", + "10.253.253.0/25", + "10.253.254.0/25" + ] + }, + { + "regionName": "westus2", + "cloudProviderID": "/subscriptions/85ca999d-c9c7-484b-82b8-6854bc1e2af5/resourceGroups/CAPIC_infra_overlay-1_westus2/providers/Microsoft.Network/virtualNetworks/overlay-1", + "cidrs": [ + "10.253.255.0/25", + "10.253.255.128/25", + "10.253.254.128/25" + ] + } + ] + }, + "dcnm": {}, + "remoteSites": [ + { + "remoteType": "", + "id": "{{ site_dict.aws_ansible_test.id }}", + "siteId": "{{ aws_site_id }}", + "siteType": "CloudApic", + "connections": [ + { + "priority": 0, + "connectionType": "Public", + "ipsec": true, + "ikev": "ikev2", + "protocol": "BgpEvpn", + "nonEvpnConfig": {}, + "bfdConfig": { + "name": "default" + }, + "tunnels": [ + { + "ikev": "ikev2", + "bgpPeer": { + "asn": "" + } + } + ] + } + ] + }, + { + "remoteType": "", + "id": "{{ site_dict.ansible_test.id }}", + "siteId": "{{ apic_site_id }}", + "siteType": "Apic", + "connections": [ + { + "priority": 0, + "connectionType": "Public", + "ipsec": true, + "ikev": "ikev2", + "protocol": "BgpEvpn", + "nonEvpnConfig": {}, + "bfdConfig": { + "name": "default" + }, + "tunnels": [ + { + "ikev": "ikev2", + "bgpPeer": { + "asn": "" + } + } + ] + } + ] + } + ], + "status": { + "state": "success" + }, + "ecStatus": { + "state": "success" + } + }, + { + "id": "{{ site_dict.aws_ansible_test.id }}", + "siteId": "{{ aws_site_id }}", + "siteType": "CloudApic", + "apic": {}, + "capic": { + "provider": "Aws", + "accountID": "787820171958", + "regions": [ + { + "regionName": "us-west-1", + "cloudProviderID": "vpc-01d3ce3ef88c18087", + "cloudDirectoryID": "787820171958", + "cidrs": [ + "10.10.0.0/25" + ] + } + ] + }, + "dcnm": {}, + "remoteSites": [ + { + "remoteType": "", + "id": "{{ site_dict.azure_ansible_test.id }}", + "siteId": "{{ azure_site_id }}", + "siteType": "CloudApic", + "connections": [ + { + "priority": 0, + "connectionType": "Public", + "ipsec": true, + "ikev": "ikev2", + "protocol": "BgpEvpn", + "nonEvpnConfig": {}, + "bfdConfig": { + "name": "default" + }, + "tunnels": [ + { + "ikev": "ikev2", + "bgpPeer": { + "asn": "" + } + } + ] + } + ] + }, + { + "remoteType": "", + "id": "{{ site_dict.ansible_test.id }}", + "siteId": "{{ apic_site_id }}", + "siteType": "Apic", + "connections": [ + { + "priority": 0, + "connectionType": "Public", + "ipsec": true, + "ikev": "ikev2", + "protocol": "BgpEvpn", + "nonEvpnConfig": {}, + "bfdConfig": { + "name": "default" + }, + "tunnels": [ + { + "ikev": "ikev2", + "bgpPeer": { + "asn": "" + } + } + ] + } + ] + } + ], + "status": { + "state": "success" + }, + "ecStatus": { + "state": "success" + } + }, + { + "id": "{{ site_dict.ansible_test.id }}", + "siteId": "{{ apic_site_id }}", + "siteType": "Apic", + "apic": {}, + "capic": {}, + "dcnm": {}, + "remoteSites": [ + { + "remoteType": "", + "id": "{{ site_dict.azure_ansible_test.id }}", + "siteId": "{{ azure_site_id }}", + "siteType": "CloudApic", + "connections": [ + { + "priority": 0, + "connectionType": "Public", + "ipsec": true, + "ikev": "ikev2", + "protocol": "BgpEvpn", + "nonEvpnConfig": {}, + "bfdConfig": { + "name": "default" + }, + "tunnels": [ + { + "ikev": "ikev2", + "bgpPeer": { + "asn": "" + } + } + ] + } + ] + }, + { + "remoteType": "", + "id": "{{ site_dict.aws_ansible_test.id }}", + "siteId": "{{ aws_site_id }}", + "siteType": "CloudApic", + "connections": [ + { + "priority": 0, + "connectionType": "Public", + "ipsec": true, + "ikev": "ikev2", + "protocol": "BgpEvpn", + "nonEvpnConfig": {}, + "bfdConfig": { + "name": "default" + }, + "tunnels": [ + { + "ikev": "ikev2", + "bgpPeer": { + "asn": "" + } + } + ] + } + ] + } + ], + "status": {}, + "ecStatus": {} + } + ] +}
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml new file mode 100644 index 000000000..9285613cb --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml @@ -0,0 +1,565 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + site_dict: {} + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Undeploy a schema 1 template 1 + mso_schema_template_deploy: &schema_undeploy + <<: *mso_info + schema: ansible_test + template: Template 1 + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + +- name: Undeploy a schema 1 template 2 + mso_schema_template_deploy: + <<: *schema_undeploy + template: Template 2 + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + +- name: Undeploy a schema 2 template 3 + mso_schema_template_deploy: + <<: *schema_undeploy + schema: ansible_test_2 + template: Template 3 + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - '{{ mso_site | default("ansible_test") }}_2' + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + - 'Schema1' + - 'Schema2' + +- name: Remove tenant ansible_test + mso_tenant: &tenant_absent + <<: *mso_info + tenant: ansible_test + state: absent + +- name: Remove tenant ansible_test2 + mso_tenant: + <<: *tenant_absent + tenant: ansible_test2 + register: cm_remove_tenant + +- name: Remove site + mso_site: &site_absent + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + state: absent + +- name: Remove site 2 + mso_site: + <<: *site_absent + site: '{{ mso_site | default("ansible_test") }}_2' + register: cm_remove_site + + +# ADD SITE +- name: Add site (check_mode) + mso_site: &site_present + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + location: + latitude: 50.887318 + longitude: 4.447084 + labels: + - Diegem + - EMEA + - POD51 + state: present + check_mode: true + register: cm_add_site + +- name: Verify cm_add_site + assert: + that: + - cm_add_site is changed + - cm_add_site.previous == {} + +- name: Verify cm_add_site (MSO) + assert: + that: + - cm_add_site.current.id is not defined + - cm_add_site.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify cm_add_site (ND) + assert: + that: + - cm_add_site.current.id == "" + - cm_add_site.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Add site (normal mode) + mso_site: *site_present + register: nm_add_site + +- name: Verify nm_add_site + assert: + that: + - nm_add_site is changed + - nm_add_site.previous == {} + +- name: Verify nm_add_site (MSO) + assert: + that: + - nm_add_site.current.id is defined + - nm_add_site.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify nm_add_site (ND) + assert: + that: + - nm_add_site.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Add site again (check_mode) + mso_site: *site_present + check_mode: true + register: cm_add_site_again + +- name: Verify cm_add_site_again + assert: + that: + - cm_add_site_again is not changed + - cm_add_site_again.current.id == nm_add_site.current.id + +- name: Verify cm_add_site_again (MSO) + assert: + that: + - cm_add_site_again.previous.name == mso_site|default("ansible_test") + - cm_add_site_again.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify cm_add_site_again (ND) + assert: + that: + - cm_add_site_again.previous.common.name == mso_site|default("ansible_test") + - cm_add_site_again.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Add site again (normal mode) + mso_site: *site_present + register: nm_add_site_again + +- name: Verify nm_add_site_again + assert: + that: + - nm_add_site_again is not changed + - nm_add_site_again.current.id == nm_add_site.current.id + +- name: Verify nm_add_site_again (MSO) + assert: + that: + - nm_add_site_again.previous.name == mso_site|default("ansible_test") + - nm_add_site_again.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify nm_add_site_again (ND) + assert: + that: + - nm_add_site_again.previous.common.name == mso_site|default("ansible_test") + - nm_add_site_again.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + + +# CHANGE SITE +- name: Change site (check_mode) + mso_site: + <<: *site_present + site: '{{ mso_site | default("ansible_test") }}' + apic_login_domain: '{{ apic_login_domain | default("test") }}' + location: + latitude: 51.887318 + longitude: 5.447084 + labels: + - Charleroi + - EMEA + check_mode: true + register: cm_change_site + +- name: Verify cm_change_site + assert: + that: + - cm_change_site.current.id == nm_add_site.current.id + +- name: Verify cm_change_site (MSO) + assert: + that: + - cm_change_site is changed + - cm_change_site.current.location.lat == 51.887318 + - cm_change_site.current.location.long == 5.447084 + - cm_change_site.current.labels[0] != nm_add_site.current.labels[0] + - cm_change_site.current.labels[1] == nm_add_site.current.labels[1] + - cm_change_site.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify cm_change_site (ND) + assert: + that: + - cm_change_site.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Change site (normal mode) + mso_site: + <<: *site_present + site: '{{ mso_site | default("ansible_test") }}' + apic_login_domain: '{{ apic_login_domain | default("test") }}' + location: + latitude: 51.887318 + longitude: 5.447084 + labels: + - Charleroi + - EMEA + output_level: debug + register: nm_change_site + +- name: Verify nm_change_site + assert: + that: + - nm_change_site.current.id == nm_add_site.current.id + +- name: Verify nm_change_site (MSO) + assert: + that: + - nm_change_site is changed + - nm_change_site.current.location.lat == 51.887318 + - nm_change_site.current.location.long == 5.447084 + - nm_change_site.current.labels[0] != nm_add_site.current.labels[0] + - nm_change_site.current.labels[1] == nm_add_site.current.labels[1] + - nm_change_site.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify nm_change_site (ND) + assert: + that: + - nm_change_site.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Change site again (check_mode) + mso_site: + <<: *site_present + site: '{{ mso_site | default("ansible_test") }}' + apic_login_domain: '{{ apic_login_domain | default("test") }}' + location: + latitude: 51.887318 + longitude: 5.447084 + labels: + - Charleroi + - EMEA + check_mode: true + register: cm_change_site_again + +- name: Verify cm_change_site_again + assert: + that: + - cm_change_site_again is not changed + - cm_change_site_again.current.id == nm_add_site.current.id + +- name: Verify cm_change_site_again (MSO) + assert: + that: + - cm_change_site_again.current.location.lat == 51.887318 + - cm_change_site_again.current.location.long == 5.447084 + - cm_change_site_again.current.labels[0] == nm_change_site.current.labels[0] + - cm_change_site_again.current.labels[1] == nm_change_site.current.labels[1] + - cm_change_site_again.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify cm_change_site_again (ND) + assert: + that: + - cm_change_site_again.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Change site again (normal mode) + mso_site: + <<: *site_present + site: '{{ mso_site | default("ansible_test") }}' + apic_login_domain: '{{ apic_login_domain | default("test") }}' + location: + latitude: 51.887318 + longitude: 5.447084 + labels: + - Charleroi + - EMEA + output_level: debug + register: nm_change_site_again + +- name: Verify nm_change_site_again + assert: + that: + - nm_change_site_again is not changed + - nm_change_site_again.current.id == nm_add_site.current.id + +- name: Verify nm_change_site_again (MSO) + assert: + that: + - nm_change_site_again.current.location.lat == 51.887318 + - nm_change_site_again.current.location.long == 5.447084 + - nm_change_site_again.current.labels[0] == nm_change_site.current.labels[0] + - nm_change_site_again.current.labels[1] == nm_change_site.current.labels[1] + - nm_change_site_again.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify nm_change_site_again (ND) + assert: + that: + - nm_change_site_again.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +# QUERY ALL SITES +- name: Query all sites (check_mode) + mso_site: &site_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_all_sites + +- name: Query all sites (normal mode) + mso_site: *site_query + register: nm_query_all_sites + +- name: Verify query_all_sites + assert: + that: + - cm_query_all_sites is not changed + - nm_query_all_sites is not changed + # NOTE: Order of sites is not stable between calls + #- cm_query_all_sites == nm_query_all_sites + + +# QUERY A SITE +- name: Query our site + mso_site: + <<: *site_query + site: '{{ mso_site | default("ansible_test") }}' + check_mode: true + register: cm_query_site + +- name: Query our site + mso_site: + <<: *site_query + site: '{{ mso_site | default("ansible_test") }}' + register: nm_query_site + +- name: Verify query_site + assert: + that: + - cm_query_site is not changed + - cm_query_site.current.id == nm_add_site.current.id + - nm_query_site is not changed + - nm_query_site.current.id == nm_add_site.current.id + - cm_query_site == nm_query_site + +- name: Verify query_site (MSO) + assert: + that: + - cm_query_site.current.name == mso_site|default("ansible_test") + - nm_query_site.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify query_site (ND) + assert: + that: + - cm_query_site.current.common.name == mso_site|default("ansible_test") + - nm_query_site.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +# REMOVE SITE +- name: Remove site (check_mode) + mso_site: *site_absent + check_mode: true + register: cm_remove_site + +- name: Verify cm_remove_site + assert: + that: + - cm_remove_site is changed + - cm_remove_site.current == {} + +- name: Remove site (normal mode) + mso_site: *site_absent + register: nm_remove_site + +- name: Verify nm_remove_site + assert: + that: + - nm_remove_site is changed + - nm_remove_site.current == {} + +- name: Remove site again (check_mode) + mso_site: *site_absent + check_mode: true + register: cm_remove_site_again + +- name: Verify cm_remove_site_again + assert: + that: + - cm_remove_site_again is not changed + - cm_remove_site_again.current == {} + +- name: Remove site again (normal mode) + mso_site: *site_absent + register: nm_remove_site_again + +- name: Verify nm_remove_site_again + assert: + that: + - nm_remove_site_again is not changed + - nm_remove_site_again.current == {} + + +# QUERY NON-EXISTING SITE +- name: Query non-existing site (check_mode) + mso_site: + <<: *site_query + site: '{{ mso_site | default("ansible_test") }}' + check_mode: true + register: cm_query_non_site + +- name: Query non-existing site (normal mode) + mso_site: + <<: *site_query + site: '{{ mso_site | default("ansible_test") }}' + register: nm_query_non_site + +# TODO: Implement more tests +- name: Verify query_non_site + assert: + that: + - cm_query_non_site is not changed + - nm_query_non_site is not changed + - cm_query_non_site == nm_query_non_site + +# USE A NON-EXISTING STATE +- name: Non-existing state for site (check_mode) + mso_site: + <<: *site_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for bd (normal_mode) + mso_site: + <<: *site_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state" + +# ADD SITE +- name: Add site (normal_mode) + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + apic_login_domain: '{{ apic_login_domain | default("test") }}' + urls: + - https://{{ apic_hostname }} + state: present + register: nm_add_site_no_location + +- name: Verify nm_add_site_no_location + assert: + that: + - nm_add_site_no_location is changed + - nm_add_site_no_location.previous == {} + - nm_add_site_no_location.current.id is defined + +- name: Verify nm_add_site_no_location (MSO) + assert: + that: + - nm_add_site_no_location.current.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '<') + +- name: Verify nm_add_site_no_location (ND) + assert: + that: + - nm_add_site_no_location.current.common.name == mso_site|default("ansible_test") + when: version.current.version is version('3.2', '>=') + +- name: Execute tasks only for MSO version >= 4.0 to reset site connectivity + when: version.current.version is version('4.0', '>=') + block: + - name: Query all sites (check_mode) + mso_site: + <<: *mso_info + state: query + register: sites + + - name: Add sites to dict + set_fact: + site_dict: "{{ site_dict | combine( { item.common.name : { 'id' : item.id, 'site_group_id' : item.common.siteGroup } } ) }}" + loop: "{{ sites.current }}" + + - name: Render a connectivity jinja2 template + set_fact: + site_payload: "{{ lookup('template', 'connectivity.j2') }}" + + - name: Configure site connectivity + cisco.mso.mso_rest: + <<: *mso_info + path: /mso/api/v2/sites/fabric-connectivity + method: put + content: "{{ site_payload }}"
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml new file mode 100644 index 000000000..e5cc5a0e6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml @@ -0,0 +1,758 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Ensure sites exists + mso_site: + <<: *mso_info + site: '{{ item.site }}' + apic_username: '{{ item.username }}' + apic_password: '{{ item.password }}' + apic_site_id: '{{ item.id }}' + urls: + - https://{{ item.urls }} + state: present + loop: + - { site: '{{ mso_site | default("ansible_test") }}', username: '{{ apic_username }}', password: '{{ apic_password }}', id: '{{ apic_site_id | default(101) }}', urls: '{{ apic_hostname }}' } + - { site: 'aws_{{ mso_site | default("ansible_test") }}', username: '{{ aws_apic_username }}', password: '{{ aws_apic_password }}', id: '{{ aws_site_id | default(102) }}', urls: '{{ aws_apic_hostname }}' } + +- name: Undeploy a schema 1 template 1 + mso_schema_template_deploy: &schema_undeploy + <<: *mso_info + schema: ansible_test + template: Template 1 + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + +- name: Undeploy a schema 1 template 2 + mso_schema_template_deploy: + <<: *schema_undeploy + template: Template 2 + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + +- name: Undeploy a schema 2 template 3 + mso_schema_template_deploy: + <<: *schema_undeploy + schema: ansible_test_2 + template: Template 3 + site: '{{ item }}' + state: undeploy + ignore_errors: true + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Remove tenants + mso_tenant: &tenant_absent + <<: *mso_info + tenant: '{{ item }}' + state: absent + loop: + - ansible_test + - ansible_test2 + - ansible_test3 + - tenant_with_site + +# ADD TENANT +- name: Add tenant (check_mode) + mso_tenant: &tenant_present + <<: *mso_info + tenant: ansible_test + display_name: Ansible test title + description: Ansible test tenant + state: present + check_mode: true + register: cm_add_tenant + +- name: Verify cm_add_tenant + assert: + that: + - cm_add_tenant is changed + - cm_add_tenant.previous == {} + - cm_add_tenant.current.id is not defined + - cm_add_tenant.current.name == 'ansible_test' + - cm_add_tenant.current.description == 'Ansible test tenant' + - cm_add_tenant.current.userAssociations | length == 1 + +- name: Add tenant (normal mode) + mso_tenant: *tenant_present + register: nm_add_tenant + +- name: Verify nm_add_tenant + assert: + that: + - nm_add_tenant is changed + - nm_add_tenant.previous == {} + - nm_add_tenant.current.id is defined + - nm_add_tenant.current.name == 'ansible_test' + - nm_add_tenant.current.description == 'Ansible test tenant' + - nm_add_tenant.current.userAssociations | length == 1 + +- name: Add tenant again (check_mode) + mso_tenant: *tenant_present + check_mode: true + register: cm_add_tenant_again + +- name: Verify cm_add_tenant_again + assert: + that: + - cm_add_tenant_again is not changed + - cm_add_tenant_again.previous.name == 'ansible_test' + - cm_add_tenant_again.previous.description == 'Ansible test tenant' + - cm_add_tenant_again.current.id == nm_add_tenant.current.id + - cm_add_tenant_again.current.name == 'ansible_test' + - cm_add_tenant_again.current.description == 'Ansible test tenant' + - cm_add_tenant_again.current.userAssociations == cm_add_tenant_again.previous.userAssociations + +- name: Add tenant again (normal mode) + mso_tenant: *tenant_present + register: nm_add_tenant_again + +- name: Verify nm_add_tenant_again + assert: + that: + - nm_add_tenant_again is not changed + - nm_add_tenant_again.previous.name == 'ansible_test' + - nm_add_tenant_again.previous.description == 'Ansible test tenant' + - nm_add_tenant_again.current.id == nm_add_tenant.current.id + - nm_add_tenant_again.current.name == 'ansible_test' + - nm_add_tenant_again.current.description == 'Ansible test tenant' + - nm_add_tenant_again.current.userAssociations == nm_add_tenant_again.previous.userAssociations + +# ADD TENANT WITH USERS +- name: Add tenant 2 (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test2 + users: + - '{{ mso_username }}' + display_name: null + state: present + register: nm_add_tenant2 + +- name: Verify nm_add_tenant2 + assert: + that: + - nm_add_tenant2 is changed + +- name: Verify nm_add_tenant2 (when mso_username != admin) + assert: + that: + - nm_add_tenant2.current.userAssociations | length == 2 + when: mso_username != 'admin' + +- name: Verify nm_add_tenant2 (when mso_username == admin) + assert: + that: + - nm_add_tenant2.current.userAssociations | length == 1 + when: mso_username == 'admin' + +- name: Add tenant 2 again (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test2 + users: + - '{{ mso_username }}' + display_name: null + state: present + register: nm_add_tenant2_again + +- name: Verify nm_add_tenant2_again + assert: + that: + - nm_add_tenant2_again is not changed + +- name: Verify nm_add_tenant2_again (when mso_username != admin) + assert: + that: + - nm_add_tenant2_again.current.userAssociations | length == 2 + when: mso_username != 'admin' + +- name: Verify nm_add_tenant2_again (when mso_username == admin) + assert: + that: + - nm_add_tenant2_again.current.userAssociations | length == 1 + when: mso_username == 'admin' + +- name: Add tenant 3 with duplicate admin user (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test3 + users: + - admin + - admin + display_name: null + state: present + ignore_errors: true + register: nm_add_tenant3_with_duplicate_admin + +- name: Verify nm_add_tenant3_with_duplicate_admin + assert: + that: + - nm_add_tenant3_with_duplicate_admin is not changed + - nm_add_tenant3_with_duplicate_admin.msg == "User 'admin' is duplicate." + +- name: Add tenant 3 with invalid user (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test3 + users: + - invalid user + display_name: null + state: present + ignore_errors: true + register: nm_add_tenant3_with_invalid_user + +- name: nm_add_tenant3_with_invalid_user + assert: + that: + - nm_add_tenant3_with_invalid_user is not changed + - nm_add_tenant3_with_invalid_user.msg == "User 'invalid user' is not a valid user name." + +- name: Add tenant 3 (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test3 + users: + - '{{ mso_username }}' + display_name: null + state: present + register: nm_add_tenant3 + +- name: Verify nm_add_tenant3 + assert: + that: + - nm_add_tenant3 is changed + +- name: Verify nm_add_tenant3 (when mso_username != admin) + assert: + that: + - nm_add_tenant3.current.userAssociations | length == 2 + when: mso_username != 'admin' + +- name: Verify nm_add_tenant3 (when mso_username == admin) + assert: + that: + - nm_add_tenant3.current.userAssociations | length == 1 + when: mso_username == 'admin' + +# CHANGE TENANT +- name: Change tenant (check_mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test + description: Ansible test tenant 2 + check_mode: true + register: cm_change_tenant + +- name: Verify cm_change_tenant + assert: + that: + - cm_change_tenant is changed + - cm_change_tenant.current.id == nm_add_tenant.current.id + - cm_change_tenant.current.name == 'ansible_test' + - cm_change_tenant.current.description == 'Ansible test tenant 2' + +- name: Change tenant (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test + description: Ansible test tenant 2 + output_level: debug + register: nm_change_tenant + +- name: Verify nm_change_tenant + assert: + that: + - nm_change_tenant is changed + - nm_change_tenant.current.id == nm_add_tenant.current.id + - nm_change_tenant.current.name == 'ansible_test' + - nm_change_tenant.current.description == 'Ansible test tenant 2' + +- name: Change tenant again (check_mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test + description: Ansible test tenant 2 + check_mode: true + register: cm_change_tenant_again + +- name: Verify cm_change_tenant_again + assert: + that: + - cm_change_tenant_again is not changed + - cm_change_tenant_again.current.id == nm_add_tenant.current.id + - cm_change_tenant_again.current.name == 'ansible_test' + - cm_change_tenant_again.current.description == 'Ansible test tenant 2' + +- name: Change tenant again (normal mode) + mso_tenant: + <<: *tenant_present + tenant: ansible_test + description: Ansible test tenant 2 + register: nm_change_tenant_again + +- name: Verify nm_change_tenant_again + assert: + that: + - nm_change_tenant_again is not changed + - nm_change_tenant_again.current.id == nm_add_tenant.current.id + - nm_change_tenant_again.current.name == 'ansible_test' + - nm_change_tenant_again.current.description == 'Ansible test tenant 2' + + +# QUERY ALL TENANTS +- name: Query all tenants (check_mode) + mso_tenant: &tenant_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_all_tenants + +- name: Query all tenants (normal mode) + mso_tenant: *tenant_query + register: nm_query_all_tenants + +- name: Verify query_all_tenants + assert: + that: + - cm_query_all_tenants is not changed + - nm_query_all_tenants is not changed + # NOTE: Order of tenants is not stable between calls + #- cm_query_all_tenants == nm_query_all_tenants + + +# QUERY A TENANT +- name: Query our tenant + mso_tenant: + <<: *tenant_query + tenant: ansible_test + check_mode: true + register: cm_query_tenant + +- name: Query our tenant + mso_tenant: + <<: *tenant_query + tenant: ansible_test + register: nm_query_tenant + +- name: Verify query_tenant + assert: + that: + - cm_query_tenant is not changed + - cm_query_tenant.current.id == nm_add_tenant.current.id + - cm_query_tenant.current.name == 'ansible_test' + - cm_query_tenant.current.description == 'Ansible test tenant 2' + - nm_query_tenant is not changed + - nm_query_tenant.current.id == nm_add_tenant.current.id + - nm_query_tenant.current.name == 'ansible_test' + - nm_query_tenant.current.description == 'Ansible test tenant 2' + - cm_query_tenant.current == nm_query_tenant.current + + +# REMOVE TENANT +- name: Remove tenant (check_mode) + mso_tenant: + <<: *tenant_absent + tenant: ansible_test + check_mode: true + register: cm_remove_tenant + +- name: Verify cm_remove_tenant + assert: + that: + - cm_remove_tenant is changed + - cm_remove_tenant.current == {} + +- name: Remove tenant (normal mode) + mso_tenant: + <<: *tenant_absent + tenant: ansible_test + register: nm_remove_tenant + +- name: Verify nm_remove_tenant + assert: + that: + - nm_remove_tenant is changed + - nm_remove_tenant.current == {} + +- name: Remove tenant again (check_mode) + mso_tenant: + <<: *tenant_absent + tenant: ansible_test + check_mode: true + register: cm_remove_tenant_again + +- name: Verify cm_remove_tenant_again + assert: + that: + - cm_remove_tenant_again is not changed + - cm_remove_tenant_again.current == {} + +- name: Remove tenant again (normal mode) + mso_tenant: + <<: *tenant_absent + tenant: ansible_test + register: nm_remove_tenant_again + +- name: Verify nm_remove_tenant_again + assert: + that: + - nm_remove_tenant_again is not changed + - nm_remove_tenant_again.current == {} + + +# QUERY NON-EXISTING TENANT +- name: Query non-existing tenant (check_mode) + mso_tenant: + <<: *tenant_query + tenant: ansible_test + check_mode: true + register: cm_query_non_tenant + +- name: Query non-existing tenant (normal mode) + mso_tenant: + <<: *tenant_query + tenant: ansible_test + register: nm_query_non_tenant + +# TODO: Implement more tests +- name: Verify query_non_tenant + assert: + that: + - cm_query_non_tenant is not changed + - nm_query_non_tenant is not changed + - cm_query_non_tenant.current == nm_query_non_tenant.current + +- name: Add common tenant + mso_tenant: + <<: *tenant_present + tenant: common + display_name: common + sites: ['{{ mso_site | default("ansible_test") }}', 'aws_{{ mso_site | default("ansible_test") }}'] + register: nm_add_common_tenant + +- name: Verify nm_add_common_tenant + assert: + that: + - nm_add_common_tenant is changed + - nm_add_common_tenant.current.name == "common" + +- name: Add tenant with site + mso_tenant: + <<: *tenant_present + tenant: tenant_with_site + display_name: tenant_with_site + sites: '{{ mso_site | default("ansible_test") }}' + register: nm_add_tenant_with_site + +- name: Verify nm_add_tenant_with_site + assert: + that: + - nm_add_tenant_with_site is changed + - nm_add_tenant_with_site.current.name == "tenant_with_site" + +- name: Remove common tenant + mso_tenant: + <<: *tenant_absent + tenant: common + ignore_errors: true + register: rm_common + +- name: Verify rm_common + assert: + that: + - rm_common.msg is search("Common [Tt]enant cannot be deleted") + +- name: Remove tenant_with_site + mso_tenant: + <<: *tenant_absent + tenant: tenant_with_site + register: rm_tenant_with_site + +- name: Verify rm_tenant_with_site + assert: + that: + - rm_tenant_with_site is changed + - rm_tenant_with_site.current == {} + +- name: Remove "anstest_imp_tenant" to the MSO if exists + mso_tenant: + <<: *mso_info + tenant: anstest_imp_tenant + display_name: anstest_imp_tenant_display_name + description: anstest_imp_tenant_description + sites: '{{ mso_site | default("ansible_test") }}' + state: absent + register: pre_test_anstest_imp_tenant_absent + +- name: Import "anstest_imp_tenant" to the MSO with check mode + mso_tenant: &cm_import_anstest_imp_tenant_present + <<: *mso_info + tenant: anstest_imp_tenant + display_name: anstest_imp_tenant_display_name + description: anstest_imp_tenant_description + sites: '{{ mso_site | default("ansible_test") }}' + state: present + check_mode: true + register: cm_import_anstest_imp_tenant_present + +- name: Assertions check for import "anstest_imp_tenant" to the MSO with check mode + assert: + that: + - cm_import_anstest_imp_tenant_present is changed + - cm_import_anstest_imp_tenant_present.current != {} + - cm_import_anstest_imp_tenant_present.previous == {} + - cm_import_anstest_imp_tenant_present.current.name == "anstest_imp_tenant" + - cm_import_anstest_imp_tenant_present.current.displayName == "anstest_imp_tenant_display_name" + - cm_import_anstest_imp_tenant_present.current.description == "anstest_imp_tenant_description" + +- name: Import "anstest_imp_tenant" to the MSO with normal mode + mso_tenant: &nm_import_anstest_imp_tenant_present + <<: *cm_import_anstest_imp_tenant_present + register: nm_import_anstest_imp_tenant_present + +- name: Assertions check for import "anstest_imp_tenant" to the MSO with normal mode + assert: + that: + - nm_import_anstest_imp_tenant_present is changed + - nm_import_anstest_imp_tenant_present.current != {} + - nm_import_anstest_imp_tenant_present.previous == {} + - nm_import_anstest_imp_tenant_present.current.name == "anstest_imp_tenant" + - nm_import_anstest_imp_tenant_present.current.displayName == "anstest_imp_tenant_display_name" + - nm_import_anstest_imp_tenant_present.current.description == "anstest_imp_tenant_description" + +- name: Import "anstest_imp_tenant" to the MSO with normal mode - idempotency works + mso_tenant: + <<: *nm_import_anstest_imp_tenant_present + register: idempotency_nm_import_anstest_imp_tenant_present + +- name: Idempotency assertions check for import "anstest_imp_tenant" to the MSO with normal mode + assert: + that: + - idempotency_nm_import_anstest_imp_tenant_present is not changed + - idempotency_nm_import_anstest_imp_tenant_present.current != {} + - idempotency_nm_import_anstest_imp_tenant_present.previous != {} + - idempotency_nm_import_anstest_imp_tenant_present.current.name == "anstest_imp_tenant" + - idempotency_nm_import_anstest_imp_tenant_present.current.displayName == "anstest_imp_tenant_display_name" + - idempotency_nm_import_anstest_imp_tenant_present.current.description == "anstest_imp_tenant_description" + - idempotency_nm_import_anstest_imp_tenant_present.previous.name == "anstest_imp_tenant" + - idempotency_nm_import_anstest_imp_tenant_present.previous.displayName == "anstest_imp_tenant_display_name" + - idempotency_nm_import_anstest_imp_tenant_present.previous.description == "anstest_imp_tenant_description" + +- name: Query a tenant with name "anstest_imp_tenant" when it is imported to the MSO + mso_tenant: + <<: *mso_info + tenant: anstest_imp_tenant + state: query + register: query_anstest_imp_tenant + +- name: Assertions check for query a tenant with name "anstest_imp_tenant" when it is imported to the MSO + assert: + that: + - query_anstest_imp_tenant is not changed + - query_anstest_imp_tenant.current != {} + - query_anstest_imp_tenant.current.name == "anstest_imp_tenant" + - query_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name" + - query_anstest_imp_tenant.current.description == "anstest_imp_tenant_description" + +- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with check mode + mso_tenant: &cm_anstest_imp_tenant_absent_orchestrator_only_yes + <<: *mso_info + tenant: anstest_imp_tenant + orchestrator_only: yes + state: absent + check_mode: true + register: cm_anstest_imp_tenant_absent_orchestrator_only_yes + +- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with check mode + assert: + that: + - cm_anstest_imp_tenant_absent_orchestrator_only_yes is changed + - cm_anstest_imp_tenant_absent_orchestrator_only_yes.current == {} + - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous != {} + - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.name == "anstest_imp_tenant" + - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.displayName == "anstest_imp_tenant_display_name" + - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.description == "anstest_imp_tenant_description" + +- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode + mso_tenant: &nm_anstest_imp_tenant_absent_orchestrator_only_yes + <<: *cm_anstest_imp_tenant_absent_orchestrator_only_yes + register: nm_anstest_imp_tenant_absent_orchestrator_only_yes + +- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode + assert: + that: + - nm_anstest_imp_tenant_absent_orchestrator_only_yes is changed + - nm_anstest_imp_tenant_absent_orchestrator_only_yes.current == {} + - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous != {} + - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.name == "anstest_imp_tenant" + - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.displayName == "anstest_imp_tenant_display_name" + - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.description == "anstest_imp_tenant_description" + +- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode - idempotency works + mso_tenant: + <<: *nm_anstest_imp_tenant_absent_orchestrator_only_yes + register: idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes + +- name: Idempotency assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode + assert: + that: + - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes is not changed + - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes.current == {} + - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous == {} + +- name: Import "anstest_imp_tenant" to the MSO with normal mode once again + mso_tenant: + <<: *nm_import_anstest_imp_tenant_present + register: nm_import_tenant_once_again + +- name: Assertions check for import "anstest_imp_tenant" to the MSO with normal mode once again + assert: + that: + - nm_import_tenant_once_again is changed + - nm_import_tenant_once_again.current != {} + - nm_import_tenant_once_again.previous == {} + - nm_import_tenant_once_again.current.name == "anstest_imp_tenant" + - nm_import_tenant_once_again.current.displayName == "anstest_imp_tenant_display_name" + - nm_import_tenant_once_again.current.description == "anstest_imp_tenant_description" + +- name: Update "anstest_imp_tenant" tenant description with check mode + mso_tenant: &cm_update_anstest_imp_tenant + <<: *nm_import_anstest_imp_tenant_present + description: "updated_anstest_imp_tenant_description" + check_mode: true + register: cm_update_anstest_imp_tenant + +- name: Assertions check for update "anstest_imp_tenant" tenant description with check mode + assert: + that: + - cm_update_anstest_imp_tenant is changed + - cm_update_anstest_imp_tenant.current != {} + - cm_update_anstest_imp_tenant.previous != {} + - cm_update_anstest_imp_tenant.current.name == "anstest_imp_tenant" + - cm_update_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name" + - cm_update_anstest_imp_tenant.current.description == "updated_anstest_imp_tenant_description" + - cm_update_anstest_imp_tenant.previous.name == "anstest_imp_tenant" + - cm_update_anstest_imp_tenant.previous.displayName == "anstest_imp_tenant_display_name" + - cm_update_anstest_imp_tenant.previous.description == "anstest_imp_tenant_description" + +- name: Update "anstest_imp_tenant" tenant description with normal mode + mso_tenant: &nm_update_anstest_imp_tenant + <<: *cm_update_anstest_imp_tenant + register: nm_update_anstest_imp_tenant + +- name: Assertions check for update "anstest_imp_tenant" tenant description with normal mode + assert: + that: + - nm_update_anstest_imp_tenant is changed + - nm_update_anstest_imp_tenant.current != {} + - nm_update_anstest_imp_tenant.previous != {} + - nm_update_anstest_imp_tenant.current.name == "anstest_imp_tenant" + - nm_update_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name" + - nm_update_anstest_imp_tenant.current.description == "updated_anstest_imp_tenant_description" + - nm_update_anstest_imp_tenant.previous.name == "anstest_imp_tenant" + - nm_update_anstest_imp_tenant.previous.displayName == "anstest_imp_tenant_display_name" + - nm_update_anstest_imp_tenant.previous.description == "anstest_imp_tenant_description" + +- name: Update "anstest_imp_tenant" tenant description with normal mode - idempotency works + mso_tenant: + <<: *nm_update_anstest_imp_tenant + register: nm_idempotency_update_anstest_imp_tenant + +- name: Idempotency assertions check for update "anstest_imp_tenant" tenant description with normal mode + assert: + that: + - nm_idempotency_update_anstest_imp_tenant is not changed + - nm_idempotency_update_anstest_imp_tenant.current != {} + - nm_idempotency_update_anstest_imp_tenant.previous != {} + - nm_idempotency_update_anstest_imp_tenant.current.name == "anstest_imp_tenant" + - nm_idempotency_update_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name" + - nm_idempotency_update_anstest_imp_tenant.current.description == "updated_anstest_imp_tenant_description" + - nm_idempotency_update_anstest_imp_tenant.previous.name == "anstest_imp_tenant" + - nm_idempotency_update_anstest_imp_tenant.previous.displayName == "anstest_imp_tenant_display_name" + - nm_idempotency_update_anstest_imp_tenant.previous.description == "updated_anstest_imp_tenant_description" + +# Orchestrator Only no will remove the tenant from MSO and APIC +- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with check mode + mso_tenant: &cm_anstest_imp_tenant_absent_orchestrator_only_no + <<: *mso_info + tenant: anstest_imp_tenant + orchestrator_only: no + state: absent + check_mode: true + register: cm_anstest_imp_tenant_absent_orchestrator_only_no + +- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with check mode + assert: + that: + - cm_anstest_imp_tenant_absent_orchestrator_only_no is changed + - cm_anstest_imp_tenant_absent_orchestrator_only_no.current == {} + - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous != {} + - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous.name == "anstest_imp_tenant" + - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous.displayName == "anstest_imp_tenant_display_name" + - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous.description == "updated_anstest_imp_tenant_description" + +- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode + mso_tenant: &nm_anstest_imp_tenant_absent_orchestrator_only_no + <<: *cm_anstest_imp_tenant_absent_orchestrator_only_no + register: nm_anstest_imp_tenant_absent_orchestrator_only_no + +- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode + assert: + that: + - nm_anstest_imp_tenant_absent_orchestrator_only_no is changed + - nm_anstest_imp_tenant_absent_orchestrator_only_no.current == {} + - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous != {} + - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous.name == "anstest_imp_tenant" + - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous.displayName == "anstest_imp_tenant_display_name" + - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous.description == "updated_anstest_imp_tenant_description" + +- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode - idempotency works + mso_tenant: + <<: *nm_anstest_imp_tenant_absent_orchestrator_only_no + register: idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no + +- name: Idempotency assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode + assert: + that: + - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no is not changed + - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no.current == {} + - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no.previous == {} diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml new file mode 100644 index 000000000..dbe1bb9e2 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml @@ -0,0 +1,679 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case) +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Remove schemas + mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Dissociate clouds that are associated with ansible_tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ item }}' + state: absent + loop: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + ignore_errors: true + +- name: Remove tenant ansible_test + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: absent + +- name: Ensure non-cloud site exists + mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure azure site exists + mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Ensure aws site exists + mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure tenant ansible_test exists + mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + state: present + +- name: Associate non-cloud site with ansible_test in check mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + check_mode: true + register: ncs_cm + +- name: Verify ncs_cm + assert: + that: + - ncs_cm is changed + +- name: Associate non-cloud site with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + register: ncs_nm + +- name: Verify ncs_nm + assert: + that: + - ncs_nm is changed + +- name: Associate non-cloud site with ansible_test again in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: present + register: ncs_nm_again + +- name: Verify ncs_nm_again + assert: + that: + - ncs_nm_again is not changed + +- name: Associate aws site with ansible_test in check mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + check_mode: true + register: aaws_cm + +- name: Verify aaws_cm + assert: + that: + - aaws_cm is changed + - aaws_cm.current.awsAccount != 'null' + - aaws_cm.current.awsAccount[0].isAccountInOrg == false + - aaws_cm.current.awsAccount[0].isTrusted == false + +- name: Associate aws site with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + register: aaws_nm + +- name: Verify aaws_nm + assert: + that: + - aaws_nm is changed + - aaws_nm.current.awsAccount != 'null' + - aaws_nm.current.awsAccount[0].isAccountInOrg == false + - aaws_nm.current.awsAccount[0].isTrusted == false + +- name: Associate aws site with ansible_test again in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + secret_key: "0" + state: present + register: aaws_nm_again + +- name: Verify aaws_nm_again + assert: + that: + - aaws_nm_again is not changed + +- name: Associate aws site with ansible_test in normal mode when aws_trusted is false and aws_access_key is missing + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + secret_key: "0" + state: present + ignore_errors: true + register: aaws_nm_ak + +- name: Verify aaws_nm_ak + assert: + that: + - aaws_nm_ak.msg is match ("aws_access_key is a required field in untrusted mode.") + +- name: Associate aws site with ansible_test in normal mode when aws_trusted is true + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: true + state: present + register: aws_nm_trusted + +- name: Verify aws_nm_trusted + assert: + that: + - aws_nm_trusted is changed + +- name: Associate aws site with ansible_test in normal mode when aws_trusted is false and secret_key is missing + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: "000000000000" + aws_trusted: false + aws_access_key: "1" + state: present + ignore_errors: true + register: aaws_nm_sk + +- name: Verify aaws_nm_sk + assert: + that: + - aaws_nm_sk.msg is match ("secret_key is a required field in untrusted mode.") + +- name: Associate aws site with ansible_test, with organization mode true + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + aws_account_org: true + cloud_account: "000000000000" + secret_key: "0" + aws_access_key: "1" + state: present + ignore_errors: true + register: aaws_nm_om + +- name: Verify aaws_nm_om + assert: + that: + - aaws_nm_om.current.awsAccount[0].isAccountInOrg == true + +- name: Associate azure site with access_type not present, with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure + state: present + register: aazure_shared_nm + +- name: Verify aazure_shared_nm + assert: + that: + - aazure_shared_nm is changed + +- name: Associate azure site in shared mode with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure + azure_access_type: shared + state: present + register: aazure_shared_nm + +- name: Verify aazure_shared_nm + assert: + that: + - aazure_shared_nm is not changed + +- name: Associate azure site with managed mode, with ansible_test in normal mode having no application_id + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: managed + state: present + ignore_errors: true + register: aazure_managed_nm_app + +- name: Verify aazure_managed_nm_app + assert: + that: + - aazure_managed_nm_app.msg is match ("azure_application_id is required when in managed mode.") + +- name: Associate azure site with managed mode, with ansible_test in normal mode having no subscription_id + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_application_id: "100" + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: managed + state: present + ignore_errors: true + register: aazure_managed_nm_si + +- name: Verify aazure_managed_nm_si + assert: + that: + - aazure_managed_nm_si.msg is match ("azure_susbscription_id is required when in managed mode.") + +- name: Associate azure site with managed mode, with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_application_id: "100" + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: managed + state: present + ignore_errors: true + register: aazure_managed_nm + +- name: Verify aazure_managed_nm + assert: + that: + - aazure_managed_nm is changed + - aazure_managed_nm.current.azureAccount != 'null' + - aazure_managed_nm.current.azureAccount[0].cloudSubscription.cloudApplicationId == '100' + - aazure_managed_nm.current.azureAccount[0].cloudSubscription.cloudSubscriptionId == '9' + - aazure_managed_nm.current.azureAccount[0].cloudApplication == [] + - aazure_managed_nm.current.azureAccount[0].cloudActiveDirectory == [] + +- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_subscription_id + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_application_id: "100" + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: "32" + azure_active_directory_name: CiscoINSBUAd + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + ignore_errors: true + register: aazure_credentials_nm_si + +- name: Verify aazure_credentials_nm_si + assert: + that: + - aazure_credentials_nm_si.msg is match ("azure_subscription_id is required when in unmanaged mode.") + +- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_application_id + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: "32" + azure_active_directory_name: CiscoINSBUAd + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + ignore_errors: true + register: aazure_credentials_nm_app + +- name: Verify aazure_credentials_nm_app + assert: + that: + - aazure_credentials_nm_app.msg is match ("azure_application_id is required when in unmanaged mode.") + +- name: Associate azure site with credentials mode, with ansible_test in normal mode having no secret_key + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_credential_name: cApicApp + azure_active_directory_id: "32" + azure_active_directory_name: CiscoINSBUAd + azure_application_id: "100" + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + ignore_errors: true + register: aazure_credentials_nm_secret + +- name: Verify aazure_credentials_nm_secret + assert: + that: + - aazure_credentials_nm_secret.msg is match ("secret_key is required when in unmanaged mode.") + +- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_active_directory_id + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_credential_name: cApicApp + azure_active_directory_name: CiscoINSBUAd + azure_application_id: "100" + secret_key: iins + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + ignore_errors: true + register: aazure_credentials_nm_ad + +- name: Verify aazure_credentials_nm_ad + assert: + that: + - aazure_credentials_nm_ad.msg is match ("azure_active_directory_id is required when in unmanaged mode.") + +- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_active_directory_name + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: "32" + azure_application_id: "100" + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + ignore_errors: true + register: aazure_credentials_nm_adn + +- name: Verify aazure_credentials_nm_adn + assert: + that: + - aazure_credentials_nm_adn.msg is match ("azure_active_directory_name is required when in unmanaged mode.") + +- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_credential_name + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + secret_key: iins + azure_active_directory_name: CiscoINSBUAd + azure_active_directory_id: "32" + azure_application_id: "100" + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + ignore_errors: true + register: aazure_credentials_nm_cdn + +- name: Verify aazure_credentials_nm_cdn + assert: + that: + - aazure_credentials_nm_cdn.msg is match ("azure_credential_name is required when in unmanaged mode.") + +- name: Associate azure site with credentials mode, with ansible_test in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_application_id: "100" + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: "32" + azure_active_directory_name: CiscoINSBUAd + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + register: aazure_credentials_nm + +- name: Verify aazure_credentials_nm + assert: + that: + - aazure_credentials_nm is changed + - aazure_credentials_nm.current.azureAccount[0].cloudSubscription.cloudApplicationId == '100' + - aazure_credentials_nm.current.azureAccount[0].cloudSubscription.cloudSubscriptionId == '9' + - aazure_credentials_nm.current.azureAccount[0].cloudActiveDirectory[0].cloudActiveDirectoryId == '32' + - aazure_credentials_nm.current.azureAccount[0].cloudActiveDirectory[0].cloudActiveDirectoryName == 'CiscoINSBUAd' + - aazure_credentials_nm.current.azureAccount[0].cloudApplication[0].cloudApplicationId == '100' + - aazure_credentials_nm.current.azureAccount[0].cloudApplication[0].cloudActiveDirectoryId == '32' + - aazure_credentials_nm.current.azureAccount[0].cloudApplication[0].cloudCredentialName == 'cApicApp' + +- name: Associate azure site with credentials mode, with ansible_test again in normal mode + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_application_id: "100" + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: "32" + azure_active_directory_name: CiscoINSBUAd + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: present + register: aazure_credentials_nm_again + +- name: Verify aazure_credentials_nm_again + assert: + that: + - aazure_credentials_nm_again is not changed + +- name: Query associated non-cloud site of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: query + register: anc_query + +- name: Verify anc_query + assert: + that: + - anc_query is not changed + +- name: Query associated azure site of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + azure_subscription_id: "9" + azure_application_id: "100" + azure_credential_name: cApicApp + secret_key: iins + azure_active_directory_id: "32" + azure_active_directory_name: CiscoINSBUAd + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + azure_access_type: unmanaged + state: query + register: aazure_query + +- name: Verify aazure_query + assert: + that: + - aazure_query is not changed + +- name: Query associated aws site of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + state: query + register: aaws_query + +- name: Verify aaws_query + assert: + that: + - aaws_query is not changed + +- name: Query all associated sites of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + state: query + register: all_sites_query + +- name: Verify all_sites_query + assert: + that: + - all_sites_query is not changed + +- name: Dissociate non-cloud site with ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: absent + register: dnc + +- name: Verify dnc + assert: + that: + - dnc is changed + +- name: Query dissociated non-cloud site of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: '{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: dnc_query + +- name: Verify dnc_query + assert: + that: + - dnc_query.msg is match ("Site Id [0-9a-zA-Z]* not associated with tenant Id [0-9a-zA-Z]*") + +- name: Dissociate azure site with ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + state: absent + register: dazure + +- name: Verify dazure + assert: + that: + - dazure is changed + +- name: Query dissociated azure site of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: dazure_query + +- name: Verify dnc_query + assert: + that: + - dazure_query.msg is match ("Site Id [0-9a-zA-Z]* not associated with tenant Id [0-9a-zA-Z]*") + +- name: Dissociate aws site with ansible_test + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + state: absent + register: daaws + +- name: Verify daaws + assert: + that: + - daaws is changed + +- name: Query dissociated aws site of a tenant + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + state: query + ignore_errors: true + register: daaws_query + +- name: Verify daaws_query + assert: + that: + - daaws_query.msg is match ("No site associated with tenant Id [0-9a-zA-Z]*") + +- name: Query all + mso_tenant_site: + <<: *mso_info + tenant: ansible_test + state: query + ignore_errors: true + register: query_all + +- name: Verify query_all + assert: + that: + - query_all.msg is match ("No site associated with tenant Id [0-9a-zA-Z]*")
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml new file mode 100644 index 000000000..2aa2d2bf6 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml @@ -0,0 +1,511 @@ +# Test code for the MSO modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + mso_version: + <<: *mso_info + state: query + register: version + +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + # CLEAN ENVIRONMENT + - name: Remove user ansible_test + mso_user: &user_absent + <<: *mso_info + user: '{{ item }}' + state: absent + loop: + - ansible_test + - ansible_test2 + - ansible_test_read + - ansible_test_read_2 + + # ADD USER + - name: Add user (check_mode) + mso_user: &user_present + <<: *mso_info + user: ansible_test + user_password: 'S0m3!1n1t14l!p455w0rd' + # NOTE: First name, last name, phone and email are mandatory on creation + first_name: Ansible + last_name: Test + email: mso@cisco.com + phone: +32 478 436 299 + account_status: active + roles: + - name: powerUser + access_type: write + domain: Local + state: present + check_mode: true + register: cm_add_user + + - name: Verify cm_add_user + assert: + that: + - cm_add_user is changed + - cm_add_user.previous == {} + - cm_add_user.current.id is not defined + - cm_add_user.current.username == 'ansible_test' + - cm_add_user.current.lastName == 'Test' + - cm_add_user.current.firstName == 'Ansible' + - cm_add_user.current.emailAddress == 'mso@cisco.com' + - cm_add_user.current.phoneNumber == '+32 478 436 299' + - cm_add_user.current.accountStatus == 'active' + - cm_add_user.current.roles[0].accessType == 'readWrite' + + - name: Add user (normal mode) + mso_user: *user_present + register: nm_add_user + + - name: Verify nm_add_user + assert: + that: + - nm_add_user is changed + - nm_add_user.previous == {} + - nm_add_user.current.id is defined + - nm_add_user.current.username == 'ansible_test' + - nm_add_user.current.lastName == 'Test' + - nm_add_user.current.firstName == 'Ansible' + - nm_add_user.current.emailAddress == 'mso@cisco.com' + - nm_add_user.current.phoneNumber == '+32 478 436 299' + - nm_add_user.current.accountStatus == 'active' + - nm_add_user.current.roles[0].accessType == 'readWrite' + + - name: Add user again (check_mode) + mso_user: + <<: *user_present + # NOTE: We need to modify the password for a new user + user_password: 'S0m3!n3w!p455w0rd' + check_mode: true + register: cm_add_user_again + + - name: Verify cm_add_user_again + assert: + that: + - cm_add_user_again is changed + - cm_add_user_again.previous.username == 'ansible_test' + - cm_add_user_again.current.id == nm_add_user.current.id + - cm_add_user_again.current.username == 'ansible_test' + + - name: Add user again (normal mode) + mso_user: + <<: *user_present + # NOTE: We need to modify the password for a new user + user_password: 'S0m3!n3w!p455w0rd' + register: nm_add_user_again + + - name: Verify nm_add_user_again + assert: + that: + - nm_add_user_again is changed + - nm_add_user_again.previous.username == 'ansible_test' + - nm_add_user_again.current.id == nm_add_user.current.id + - nm_add_user_again.current.username == 'ansible_test' + + - name: Add user with read only role (check_mode) + mso_user: &user_present2 + <<: *mso_info + user: ansible_test_read + user_password: '#123455#123455Aa' + # NOTE: First name, last name, phone and email are mandatory on creation + first_name: Ansible2 + last_name: Test2 + email: mso3@cisco.com + phone: +32 478 436 299 + account_status: active + roles: + - name: powerUser + access_type: read + domain: Local + state: present + check_mode: true + register: cm_add_user2 + + - name: Verify cm_add_user2 + assert: + that: + - cm_add_user2 is changed + - cm_add_user2.previous == {} + - cm_add_user2.current.id is not defined + - cm_add_user2.current.username == 'ansible_test_read' + - cm_add_user2.current.lastName == 'Test2' + - cm_add_user2.current.firstName == 'Ansible2' + - cm_add_user2.current.emailAddress == 'mso3@cisco.com' + - cm_add_user2.current.phoneNumber == '+32 478 436 299' + - cm_add_user2.current.accountStatus == 'active' + - cm_add_user2.current.roles[0].accessType == 'readOnly' + + - name: Add user with read only role (normal mode) + mso_user: *user_present2 + register: nm_add_user2 + + - name: Verify nm_add_user2 + assert: + that: + - nm_add_user2 is changed + - nm_add_user2.current.id is defined + - nm_add_user2.current.username == 'ansible_test_read' + - nm_add_user2.current.lastName == 'Test2' + - nm_add_user2.current.firstName == 'Ansible2' + - nm_add_user2.current.emailAddress == 'mso3@cisco.com' + - nm_add_user2.current.phoneNumber == '+32 478 436 299' + - nm_add_user2.current.accountStatus == 'active' + - nm_add_user2.current.roles[0].accessType == 'readOnly' + + - name: Add user with read only role again (check mode) + mso_user: + <<: *user_present2 + user_password: '#123455#123455Aa' + check_mode: true + register: cm_add_user2_again + + - name: Add user with read only role again (normal mode) + mso_user: *user_present2 + register: nm_add_user2 + + - name: Add user3 with read only role and no password (check_mode) + mso_user: &user_present3 + <<: *mso_info + user: ansible_test_read_2 + # NOTE: First name, last name, phone and email are mandatory on creation + first_name: Ansible3 + #user_password: '#123455#123455Aa' + last_name: Test3 + email: mso4@cisco.com + phone: +32 478 436 299 + account_status: active + roles: + - name: powerUser + access_type: read + domain: Local + state: present + ignore_errors: true + register: nm_add_user3 + + - name: Verify nm_add_user2 + assert: + that: + - nm_add_user3.msg == "The user ansible_test_read_2 does not exist. The 'user_password' attribute is required to create a new user." + + - name: Add user3 with read only role and with password (normal mode) + mso_user: + <<: *user_present3 + user_password: '#123455#123455Aa' + register: nm_add_user3_again + + - name: Verify nm_add_user3_again + assert: + that: + - nm_add_user3_again is changed + - nm_add_user3_again.current.id is defined + - nm_add_user3_again.current.username == 'ansible_test_read_2' + - nm_add_user3_again.current.lastName == 'Test3' + - nm_add_user3_again.current.firstName == 'Ansible3' + - nm_add_user3_again.current.emailAddress == 'mso4@cisco.com' + - nm_add_user3_again.current.phoneNumber == '+32 478 436 299' + - nm_add_user3_again.current.accountStatus == 'active' + - nm_add_user3_again.current.roles[0].accessType == 'readOnly' + + # CHANGE USER + - name: Change user (check_mode) + mso_user: &user_change + <<: *mso_info + user: ansible_test + roles: + - name: powerUser + access_type: write + domain: Local + state: present + # FIXME: Add support for name change + email: mso2@cisco.com + phone: +32 478 436 300 + check_mode: true + register: cm_change_user + + - name: Verify cm_change_user + assert: + that: + - cm_change_user is changed + - cm_change_user.current.id == nm_add_user.current.id + - cm_change_user.current.username == 'ansible_test' + - cm_change_user.current.emailAddress == 'mso2@cisco.com' + - cm_change_user.current.phoneNumber == '+32 478 436 300' + + - name: Change user (normal mode) + mso_user: + <<: *user_change + output_level: debug + register: nm_change_user + + - name: Verify nm_change_user + assert: + that: + - nm_change_user is changed + - nm_change_user.current.id == nm_add_user.current.id + - nm_change_user.current.username == 'ansible_test' + - nm_change_user.current.emailAddress == 'mso2@cisco.com' + - nm_change_user.current.phoneNumber == '+32 478 436 300' + + - name: Change user again (check_mode) + mso_user: + <<: *user_change + check_mode: true + register: cm_change_user_again + + - name: Verify cm_change_user_again + assert: + that: + - cm_change_user_again is not changed + - cm_change_user_again.current.id == nm_add_user.current.id + - cm_change_user_again.current.username == 'ansible_test' + - cm_change_user_again.current.emailAddress == 'mso2@cisco.com' + - cm_change_user_again.current.phoneNumber == '+32 478 436 300' + + - name: Change user again (normal mode) + mso_user: + <<: *user_change + register: nm_change_user_again + + - name: Verify nm_change_user_again + assert: + that: + - nm_change_user_again is not changed + - nm_change_user_again.current.id == nm_add_user.current.id + - nm_change_user_again.current.username == 'ansible_test' + - nm_change_user_again.current.emailAddress == 'mso2@cisco.com' + - nm_change_user_again.current.phoneNumber == '+32 478 436 300' + + - name: Add second user + mso_user: + <<: *user_change + user: ansible_test2 + user_password: 'S0m3!1n1t14l!p455w0rd' + first_name: Ansible + last_name: Test + roles: + - powerUser + state: present + register: nm_add_user_2 + + - name: Change user 2 again (normal mode) + mso_user: + <<: *user_change + user: ansible_test2 + user_password: null + first_name: Ansible + last_name: Test + register: nm_change_user_2_again + + - name: Verify nm_change_user_2_again + assert: + that: + - nm_change_user_2_again is not changed + - nm_change_user_2_again.current.id == nm_add_user_2.current.id + - nm_change_user_2_again.current.username == 'ansible_test2' + + # TODO: Add query with user ansible_test2 to try if user can login. + +# QUERY ALL USERS +- name: Query all users (check_mode) + mso_user: &user_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_all_users + +- name: Query all users (normal mode) + mso_user: *user_query + register: nm_query_all_users + +- name: Verify query_all_users + assert: + that: + - cm_query_all_users is not changed + - nm_query_all_users is not changed + # NOTE: Order of users is not stable between calls + #- cm_query_all_users == nm_query_all_users + + +# QUERY A USER +- name: Query our user + mso_user: + <<: *user_query + user: '{{ mso_username }}' + check_mode: true + register: cm_query_user + +- name: Query our user + mso_user: + <<: *user_query + user: '{{ mso_username }}' + register: nm_query_user + +- name: Verify query_user + assert: + that: + - cm_query_user is not changed + - cm_query_user.current.id is defined + - cm_query_user.current.username == '{{ mso_username }}' + - nm_query_user is not changed + - nm_query_user.current.id is defined + - nm_query_user.current.username == '{{ mso_username }}' + - cm_query_user == nm_query_user + +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + - name: Query our read-only user + mso_user: + <<: *user_query + user: ansible_test_read + register: nm_query_user2 + + - name: Verify query_user2 + assert: + that: + - nm_query_user2 is not changed + - nm_query_user2.current.roles[0].accessType == 'readOnly' + + # REMOVE USER + - name: Remove user (check_mode) + mso_user: + <<: *user_absent + user: ansible_test + state: absent + check_mode: true + register: cm_remove_user + + - name: Verify cm_remove_user + assert: + that: + - cm_remove_user is changed + - cm_remove_user.current == {} + + - name: Remove user (normal mode) + mso_user: + <<: *user_absent + user: ansible_test + state: absent + register: nm_remove_user + + - name: Verify nm_remove_user + assert: + that: + - nm_remove_user is changed + - nm_remove_user.current == {} + + - name: Remove user again (check_mode) + mso_user: + <<: *user_absent + user: ansible_test + state: absent + check_mode: true + register: cm_remove_user_again + + - name: Verify cm_remove_user_again + assert: + that: + - cm_remove_user_again is not changed + - cm_remove_user_again.current == {} + + - name: Remove user again (normal mode) + mso_user: + <<: *user_absent + user: ansible_test + state: absent + register: nm_remove_user_again + + - name: Verify nm_remove_user_again + assert: + that: + - nm_remove_user_again is not changed + - nm_remove_user_again.current == {} + +# QUERY NON-EXISTING USER +- name: Query non-existing user (check_mode) + mso_user: + <<: *user_query + user: ansible_test + check_mode: true + register: cm_query_non_user + +- name: Query non-existing user (normal mode) + mso_user: + <<: *user_query + user: ansible_test + register: nm_query_non_user + +# TODO: Implement more tests +- name: Verify query_non_user + assert: + that: + - cm_query_non_user is not changed + - nm_query_non_user is not changed + - cm_query_non_user == nm_query_non_user + +- name: Execute tasks only for MSO version < 3.2 + when: version.current.version is version('3.2', '<') + block: + - name: inactive user (check_mode) + mso_user: + <<: *user_present + account_status: inactive + check_mode: true + register: cm_inactive_user + + - name: inactive user (normal_mode) + mso_user: + <<: *user_present + account_status: inactive + register: nm_inactive_user + + - name: Verify cm_inactive_user and nm_inactive_user + assert: + that: + - cm_inactive_user is changed + - nm_inactive_user is changed + - cm_inactive_user.current.accountStatus == "inactive" + - nm_inactive_user.current.accountStatus == "inactive" + + + - name: active user (check_mode) + mso_user: + <<: *user_present + account_status: active + check_mode: true + register: cm_active_user + + - name: active user (normal_mode) + mso_user: + <<: *user_present + account_status: active + register: nm_active_user + + - name: Verify cm_active_user and nm_active_user + assert: + that: + - cm_active_user is changed + - nm_active_user is changed + - cm_active_user.previous.accountStatus == nm_active_user.previous.accountStatus == "inactive" + - cm_active_user.current.accountStatus == nm_active_user.current.accountStatus == "active"
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml new file mode 100644 index 000000000..4ee73eb90 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml @@ -0,0 +1,96 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# QUERY VERSION +- name: Query MSO version + mso_version: &mso_query + <<: *mso_info + state: query + check_mode: true + register: cm_query_version + +- name: Verify cm_query_version + assert: + that: + - cm_query_version is not changed + - cm_query_version.current.id is defined + - cm_query_version.current.version is defined + - cm_query_version.current.timestamp is defined + +- name: Query MSO version (normal mode) + mso_version: + <<: *mso_query + register: nm_query_version + +- name: Verify nm_query_version + assert: + that: + - nm_query_version is not changed + - nm_query_version.current.id is defined + - nm_query_version.current.version is defined + - nm_query_version.current.timestamp is defined + - nm_query_version.current.id == cm_query_version.current.id + - nm_query_version.current.version == cm_query_version.current.version + - nm_query_version.current.timestamp == cm_query_version.current.timestamp + +# USE A NON-EXISTING STATE +- name: Non-existing state for version (check_mode) + mso_version: + <<: *mso_query + state: non-existing-state + check_mode: true + ignore_errors: true + register: cm_non_existing_state + +- name: Non-existing state for version (normal_mode) + mso_version: + <<: *mso_query + state: non-existing-state + ignore_errors: true + register: nm_non_existing_state + +- name: Verify non_existing_state + assert: + that: + - cm_non_existing_state is not changed + - nm_non_existing_state is not changed + - cm_non_existing_state == nm_non_existing_state + - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} query, got{{':'}} non-existing-state" + +# query without setting username&password in task +- name: Query MSO version + cisco.mso.mso_version: + state: query + register: query_version_global_params + when: ansible_connection != 'local' + +- name: Verify query_version_global_params + assert: + that: + - query_version_global_params is not changed + - query_version_global_params.current.id is defined + - query_version_global_params.current.version is defined + - query_version_global_params.current.timestamp is defined + - query_version_global_params.current.id == cm_query_version.current.id + - query_version_global_params.current.version == cm_query_version.current.version + - query_version_global_params.current.timestamp == cm_query_version.current.timestamp + when: ansible_connection != 'local'
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases new file mode 100644 index 000000000..5042c9c09 --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml new file mode 100644 index 000000000..3af935cfc --- /dev/null +++ b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml @@ -0,0 +1,407 @@ +# Test code for the MSO modules +# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + ansible.builtin.fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + + +# CLEAN ENVIRONMENT +- name: Set vars + ansible.builtin.set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +- name: Query MSO version + cisco.mso.mso_version: + <<: *mso_info + state: query + register: version + +- name: Ensure site exist + cisco.mso.mso_site: + <<: *mso_info + site: '{{ mso_site | default("ansible_test") }}' + apic_username: '{{ apic_username }}' + apic_password: '{{ apic_password }}' + apic_site_id: '{{ apic_site_id | default(101) }}' + urls: + - https://{{ apic_hostname }} + state: present + +- name: Ensure aws site exists + cisco.mso.mso_site: + <<: *mso_info + site: 'aws_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ aws_apic_username }}' + apic_password: '{{ aws_apic_password }}' + apic_site_id: '{{ aws_site_id | default(102) }}' + urls: + - https://{{ aws_apic_hostname }} + state: present + +- name: Ensure azure site exists + cisco.mso.mso_site: + <<: *mso_info + site: 'azure_{{ mso_site | default("ansible_test") }}' + apic_username: '{{ azure_apic_username }}' + apic_password: '{{ azure_apic_password }}' + apic_site_id: '{{ azure_site_id | default(103) }}' + urls: + - https://{{ azure_apic_hostname }} + state: present + +- name: Undeploy template + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: "{{ item }}" + sites: + - '{{ mso_site | default("ansible_test") }}' + state: undeploy + ignore_errors: true + loop: + - Template 1 + - Template 2 + +- name: Remove schemas + cisco.mso.mso_schema: + <<: *mso_info + schema: '{{ item }}' + state: absent + loop: + - '{{ mso_schema | default("ansible_test") }}_2' + - '{{ mso_schema | default("ansible_test") }}' + +- name: Ensure tenant ansible_test exists + cisco.mso.mso_tenant: + <<: *mso_info + tenant: ansible_test + users: + - '{{ mso_username }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: present + +- name: Ensure schema 1 with Template 1, and Template 2 exist + cisco.mso.mso_schema_template: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + tenant: ansible_test + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Add physical site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: '{{ mso_site | default("ansible_test") }}' + template: '{{ item }}' + state: present + loop: + - Template 1 + - Template 2 + +- name: Deploy templates (check_mode) + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: "{{ item }}" + sites: + - '{{ mso_site | default("ansible_test") }}' + state: deploy + check_mode: true + register: cm_deploy_template + loop: + - Template 1 + - Template 2 + +- name: Verify cm_deploy_template + ansible.builtin.assert: + that: + - item is not changed + loop: "{{ cm_deploy_template.results }}" + +- name: Deploy templates (normal_mode) + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: "{{ item }}" + sites: + - '{{ mso_site | default("ansible_test") }}' + state: deploy + register: nm_deploy_template + loop: + - Template 1 + - Template 2 + +- name: Verify nm_deploy_template + ansible.builtin.assert: + that: + - item is not changed + - '"isRedeploy" not in item.current.reqDetails' + loop: "{{ nm_deploy_template.results }}" + +- name: Verify nm_deploy_template 4.0 specific + ansible.builtin.assert: + that: + - '"deploy" in item.current.reqDetails' + loop: "{{ nm_deploy_template.results }}" + when: version.current.version is version('4.0', '>=') + +- name: Query deployment + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: "{{ item }}" + state: query + register: query_deploy_status + loop: + - Template 1 + - Template 2 + +- name: Verify query_deploy_status + ansible.builtin.assert: + that: + - item is not changed + - item.current.status.0.status.siteStatus == "Succeeded" + loop: "{{ query_deploy_status.results }}" + +- name: Redeploy templates + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: '{{ item }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: redeploy + register: redeploy_template + loop: + - Template 1 + - Template 2 + +- name: Verify redeploy_template + ansible.builtin.assert: + that: + - item is not changed + - item.current.reqDetails.isRedeploy == true + loop: "{{ redeploy_template.results }}" + +- name: Verify redeploy_template 4.0 specific + ansible.builtin.assert: + that: + - '"deploy" in item.current.reqDetails' + loop: "{{ redeploy_template.results }}" + when: version.current.version is version('4.0', '>=') + +- name: Undeploy templates + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: '{{ item }}' + sites: + - '{{ mso_site | default("ansible_test") }}' + state: undeploy + register: undeploy_template + loop: + - Template 1 + - Template 2 + +- name: Verify undeploy_template + assert: + that: + - item is not changed + - '"undeploy" in item.current.reqDetails' + loop: "{{ undeploy_template.results }}" + +- name: Add VRF1 with validation error + cisco.mso.mso_schema_template_vrf: &fail_validation + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 2 + vrf: VRF1 + layer3_multicast: true + vzany: true + state: present + +- name: Deploy template with validation error + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 2 + sites: + - '{{ mso_site | default("ansible_test") }}' + state: deploy + register: failed_validaton_deploy + ignore_errors: true + +- name: Redeploy template with validation error + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 2 + sites: + - '{{ mso_site | default("ansible_test") }}' + state: redeploy + register: failed_validaton_redeploy + ignore_errors: true + +- name: Verify validation errors before deploy + ansible.builtin.assert: + that: + - failed_validaton_deploy.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF1 exception while trying to update schema" + - failed_validaton_redeploy.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF1 exception while trying to update schema" + when: version.current.version is version('4.0', '<') + +- name: Verify validation errors before deploy and redploy + ansible.builtin.assert: + that: + - failed_validaton_deploy.msg == "MSO Error 400{{':'}} VRF{{':'}} VRF1 in Schema{{':'}} ansible_test , Template{{':'}} Template2 has VzAnyEnabled flag enabled but is not consuming or providing contracts" + - failed_validaton_redeploy.msg == "MSO Error 400{{':'}} VRF{{':'}} VRF1 in Schema{{':'}} ansible_test , Template{{':'}} Template2 has VzAnyEnabled flag enabled but is not consuming or providing contracts" + when: version.current.version is version('4.0', '>=') + +- name: Remove VRF1 with validation error + cisco.mso.mso_schema_template_vrf: + <<: *fail_validation + state: absent + +- name: Ensure AWS site is present under tenant ansible_test + cisco.mso.mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'aws_{{ mso_site | default("ansible_test") }}' + cloud_account: '000000000000' + aws_access_key: 1 + secret_key: 0 + state: present + +- name: Ensure Azure site is present under tenant ansible_test + cisco.mso.mso_tenant_site: + <<: *mso_info + tenant: ansible_test + site: 'azure_{{ mso_site | default("ansible_test") }}' + cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure + state: present + +- name: Add AWS site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'aws_{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + +- name: Add Azure site to a schema + cisco.mso.mso_schema_site: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + site: 'azure_{{ mso_site | default("ansible_test") }}' + template: Template 1 + state: present + +- name: Deploy templates + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + sites: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + state: deploy + register: deploy_template_all + +- name: Query deployment + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + state: query + register: query_deploy_status_all + +- name: Check deployment status of Template 1 + cisco.mso.mso_schema_template_deploy_status: + <<: *mso_info + schema: '{{ mso_schema | default("ansible_test") }}' + template: Template 1 + state: query + register: deployment_status + +- name: Redeploy templates + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + sites: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + state: redeploy + register: redeploy_template_all + +- name: Query redeployment + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + state: query + register: query_redeploy_status_all + +- name: Undeploy templates + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + sites: + - '{{ mso_site | default("ansible_test") }}' + - 'aws_{{ mso_site | default("ansible_test") }}' + - 'azure_{{ mso_site | default("ansible_test") }}' + state: undeploy + register: undeploy_template_all + +- name: Query undeployment + cisco.mso.ndo_schema_template_deploy: + <<: *mso_info + schema: ansible_test + template: Template 1 + state: query + register: query_undeploy_status_all + +- name: Verify multiple sites + ansible.builtin.assert: + that: + - deploy_template_all is not changed + - redeploy_template_all is not changed + - '"isRedeploy" in redeploy_template_all.current.reqDetails' + - undeploy_template_all is not changed + - '"undeploy" in undeploy_template_all.current.reqDetails' + - undeploy_template_all.current.reqDetails.undeploy | length == 3 + - deployment_status.current | length == 3 + - query_deploy_status_all.current.status | length == 3 + - query_deploy_status_all.current.status.0.status.siteStatus == "Succeeded" + - query_deploy_status_all.current.status.1.status.siteStatus == "Succeeded" + - query_deploy_status_all.current.status.2.status.siteStatus == "Succeeded" + - query_redeploy_status_all.current.status | length == 3 + - query_redeploy_status_all.current.status.0.status.siteStatus == "Succeeded" + - query_redeploy_status_all.current.status.1.status.siteStatus == "Succeeded" + - query_redeploy_status_all.current.status.2.status.siteStatus == "Succeeded" + - query_undeploy_status_all.current.status == [] + +- name: Verify multiple sites 4.0 specific + ansible.builtin.assert: + that: + - '"deploy" in deploy_template_all.current.reqDetails' + - '"deploy" in redeploy_template_all.current.reqDetails' + when: version.current.version is version('4.0', '>=')
\ No newline at end of file diff --git a/ansible_collections/cisco/mso/tests/sanity/requirements.txt b/ansible_collections/cisco/mso/tests/sanity/requirements.txt new file mode 100644 index 000000000..66ac0c81a --- /dev/null +++ b/ansible_collections/cisco/mso/tests/sanity/requirements.txt @@ -0,0 +1,5 @@ +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+ +requests-toolbelt
\ No newline at end of file |