├── VERSION ├── docs ├── labs │ ├── lab07-aaa │ │ ├── cvaas.tok │ │ ├── aaa_users.csv │ │ ├── delete_all_expired_svc_account_tokens.py │ │ ├── delete_svc_account.py │ │ ├── get_user_info.py │ │ ├── create_svc_account.py │ │ ├── delete_svc_account_created_by_user.py │ │ ├── add_new_user_onprem.py │ │ ├── create_svc_account_token.py │ │ ├── add_new_user_cvaas.py │ │ ├── svc_account_misc.py │ │ ├── add_users_from_csv_cvaas.py │ │ └── create_terminattr_tokens.py │ ├── static │ │ ├── serviceaccount1.png │ │ ├── serviceaccount2.png │ │ └── serviceaccount3.png │ ├── lab03-configlet-management │ │ ├── common.cfg │ │ ├── configlet_list.txt │ │ ├── create_configlet_from_file.py │ │ ├── backup_configlets.py │ │ ├── create_configlet.py │ │ ├── assign_configlet_to_device.py │ │ ├── update_configlet.py │ │ ├── reorder_configlet_on_device.py │ │ ├── get_applied_netelements.py │ │ ├── backup_configletsV2.py │ │ ├── get_configlets.py │ │ └── config_search.py │ ├── lab01-cvp-info │ │ └── get_cvp_info.py │ ├── lab04-container-management │ │ ├── create_container.py │ │ ├── add_image_to_container.py │ │ ├── remove_image_from_container.py │ │ ├── assign_configlet_to_container.py │ │ └── rename_container.py │ ├── lab05-device-management │ │ ├── add_image_to_devices.py │ │ ├── remove_image_from_device.py │ │ ├── set_mgmt_ip.py │ │ └── add_image_wo_tempaction.py │ ├── lab06-provisioning │ │ ├── move_device.py │ │ ├── change_control_workflow.py │ │ ├── change_control_workflow_rapi.py │ │ ├── gen_builder.py │ │ ├── auto_reconcile_on_rc_change.py │ │ ├── change_control_custom_rapi.py │ │ ├── configlets │ │ │ ├── AVD_spine1.cfg │ │ │ ├── AVD_spine2.cfg │ │ │ ├── AVD_leaf1.cfg │ │ │ ├── AVD_leaf2.cfg │ │ │ ├── AVD_leaf3.cfg │ │ │ └── AVD_leaf4.cfg │ │ ├── vc_task_retrigger.py │ │ ├── atd_e2e_provisioning_workflow.py │ │ └── mlag_issu.py │ ├── lab02-inventory-operations │ │ ├── get_running_configs.py │ │ ├── get_running_configs_by_time.py │ │ ├── remove_all_devices_legacy.py │ │ ├── remove_and_decommission_device.py │ │ ├── remove_devices_from_container_legacy.py │ │ ├── remove_devices_legacy.py │ │ └── compliance_check.py │ ├── README.md │ └── lab08-resource-apis │ │ ├── topology_tag_assignment.py │ │ └── resource_cvprac.py ├── release-notes-1.3.1.rst ├── release-notes-1.0.3.rst ├── release-notes-1.4.1.rst ├── release-notes-1.4.2.rst ├── release-notes-1.3.2.rst ├── release-notes-1.4.0.rst ├── release-notes-1.0.7.rst ├── release-notes-1.2.2.rst ├── release-notes-1.0.5.rst ├── release-notes-1.3.0.rst ├── release-notes-1.0.1.rst ├── release-notes-0.7.0.rst ├── release-notes-1.0.6.rst ├── release-notes-0.8.0.rst ├── release-notes-0.9.0.rst ├── release-notes-1.0.2.rst ├── release-notes-1.0.0.rst ├── release-notes-1.0.4.rst └── release-notes-1.2.0.rst ├── requirements.txt ├── dev-requirements.txt ├── test ├── fixtures │ ├── image-file.swix │ └── cvp_nodes.yaml ├── lib │ └── systestlib.py └── system │ └── test_cvp_base.py ├── .arista └── secret_allowlist.yaml ├── MANIFEST.in ├── .gitignore ├── cvprac.spec ├── .github ├── workflows │ └── unittest.yml └── pull_request_template.md ├── LICENSE ├── cvprac ├── __init__.py └── cvp_client_errors.py ├── pre-commit.sh ├── Makefile ├── Jenkinsfile └── setup.py /VERSION: -------------------------------------------------------------------------------- 1 | 1.4.2.dev 2 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/cvaas.tok: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests[socks]>=2.27.0 2 | packaging>=23.2 3 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | check-manifest 2 | coverage 3 | pdoc 4 | pep8 5 | pyflakes 6 | pylint 7 | pyyaml 8 | twine 9 | -------------------------------------------------------------------------------- /test/fixtures/image-file.swix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aristanetworks/cvprac/HEAD/test/fixtures/image-file.swix -------------------------------------------------------------------------------- /docs/labs/static/serviceaccount1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aristanetworks/cvprac/HEAD/docs/labs/static/serviceaccount1.png -------------------------------------------------------------------------------- /docs/labs/static/serviceaccount2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aristanetworks/cvprac/HEAD/docs/labs/static/serviceaccount2.png -------------------------------------------------------------------------------- /docs/labs/static/serviceaccount3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aristanetworks/cvprac/HEAD/docs/labs/static/serviceaccount3.png -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/common.cfg: -------------------------------------------------------------------------------- 1 | ! 2 | ip name-server vrf management 1.1.1.1 3 | ip name-server vrf management 8.8.8.8 4 | ! 5 | ntp server vrf management time.google.com 6 | ! 7 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/configlet_list.txt: -------------------------------------------------------------------------------- 1 | tp-avd_tp-avd-leaf1 2 | tp-avd_tp-avd-leaf2 3 | tp-avd_tp-avd-leaf3 4 | tp-avd_tp-avd-leaf4 5 | tp-avd_tp-avd-spine1 6 | tp-avd_tp-avd-spine2 -------------------------------------------------------------------------------- /.arista/secret_allowlist.yaml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | allowed_secrets: 3 | - filepath_literal: "docs/labs/lab07-aaa/svc_account_misc.py" 4 | secret_literal: "9bfb39ff892c81d6ac9f25ff95d0389719595feb" 5 | category: FALSE_POSITIVE 6 | reason: Example token in documentation example file 7 | -------------------------------------------------------------------------------- /docs/release-notes-1.3.1.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.3.1 3 | ###### 4 | 5 | 2023-4-12 6 | 7 | Fixed 8 | ^^^^^ 9 | 10 | * Add workaround for issue in CVP API validate config. (`248 `_) [`chetryan `_] 11 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/aaa_users.csv: -------------------------------------------------------------------------------- 1 | username,first_name,last_name,email,user_type,role,status 2 | alice,,,alice@abc.xyz,SSO,network-admin,Enabled 3 | bob,,,bob@abc.xyz,SSO,network-admin,Enabled 4 | jane,Jane,Smith,jane@abc.xyz,SSO,network-admin,Enabled 5 | john,John,Smith,john@abc.xyz,SSO,network-admin,Enabled -------------------------------------------------------------------------------- /docs/release-notes-1.0.3.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.3 3 | ###### 4 | 5 | 2020-6-4 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | Enhancements 11 | ^^^^^^^^^^^^ 12 | 13 | Fixed 14 | ^^^^^ 15 | 16 | * Fix issue where cvprac is using the incorrect version of the API for 2018.1.x. (`104 `_) [`colinmacgiolla `_] 17 | -------------------------------------------------------------------------------- /docs/release-notes-1.4.1.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.4.1 3 | ###### 4 | 5 | 2025-5-8 6 | 7 | Enhancements 8 | ^^^^^^^^^^^^ 9 | 10 | * Add support new service account resource APIs. (`281 `_) [`noredistribution `_] 11 | * Updates for CVP 2024.3.0 suppot. (`282 `_) [`mharista `_] 12 | -------------------------------------------------------------------------------- /test/fixtures/cvp_nodes.yaml: -------------------------------------------------------------------------------- 1 | - node: cvp 2 | username: CvpRacTest 3 | password: AristaInnovates 4 | device: python-test-2 5 | - node: cvp2 6 | username: CvpRacTest 7 | password: AristaInnovates 8 | device: python-test-2 9 | - node: cvp3 10 | username: CvpRacTest 11 | password: AristaInnovates 12 | device: python-test-2 13 | # The below fields can be defined to test the token test cases. 14 | # If they are undefined, the tests are skipped 15 | # api_token: 16 | # api_token_expired: 17 | -------------------------------------------------------------------------------- /docs/labs/lab01-cvp-info/get_cvp_info.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision 13 | clnt = CvpClient() 14 | clnt.connect(nodes=['cvp1'], username="username",password="password") 15 | 16 | result = clnt.api.get_cvp_info() 17 | print(result) 18 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include Makefile 3 | include *.spec 4 | include *.txt 5 | include LICENSE 6 | include VERSION 7 | include .pylintrc 8 | recursive-include docs *.rst 9 | recursive-include docs *.cfg 10 | recursive-include docs *.csv 11 | recursive-include docs *.md 12 | recursive-include docs *.png 13 | recursive-include docs *.py 14 | recursive-include docs *.tok 15 | recursive-include docs *.txt 16 | recursive-include test *.py 17 | recursive-include test *.yaml 18 | recursive-include test *.swix 19 | exclude Jenkinsfile 20 | exclude pre-commit.sh 21 | exclude report 22 | exclude htmlcov 23 | recursive-exclude .arista *.yaml 24 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/delete_all_expired_svc_account_tokens.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision using user/password (on-prem only) 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | clnt.api.svc_account_delete_expired_tokens() 17 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/delete_svc_account.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision using user/password (on-prem only) 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | username = "cvprac2" 17 | clnt.api.svc_account_delete(username) 18 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/create_configlet_from_file.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | configletName = "cvprac_example2" 16 | 17 | with open("common.cfg") as file: 18 | configlet = file.read() 19 | clnt.api.add_configlet(configletName, configlet) 20 | -------------------------------------------------------------------------------- /docs/labs/lab04-container-management/create_container.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | # Get parent container information 16 | parent = clnt.api.get_container_by_name("ContainerA") 17 | 18 | # Create new container ContainerB under ContainerA 19 | 20 | clnt.api.add_container("ContainerB",parent["name"],parent["key"]) 21 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/get_user_info.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | from cvprac.cvp_client import CvpClient 12 | 13 | with open("cvaas.tok") as f: 14 | token = f.read().strip('\n') 15 | 16 | clnt = CvpClient() 17 | clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token) 18 | 19 | user_info = clnt.api.get_user('kishore') 20 | print (user_info) 21 | -------------------------------------------------------------------------------- /docs/labs/lab05-device-management/add_image_to_devices.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | image_name = "vEOS-4.26.0.1F" 16 | image = clnt.api.get_image_bundle_by_name(image_name) 17 | 18 | device_name = "tp-avd-leaf2" 19 | device = clnt.api.get_device_by_name(device_name) 20 | 21 | clnt.api.apply_image_to_device(image, device) 22 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/backup_configlets.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | # 5 | # Get configlets and save them to individual files 6 | from cvprac.cvp_client import CvpClient 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | configlets = clnt.api.get_configlets(start=0,end=0) 17 | 18 | for configlet in configlets['data']: 19 | with open(configlet['name'],'w') as f: 20 | f.write(configlet['config']) 21 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/create_configlet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | configletName = "cvprac_example" 16 | 17 | configlet = """! 18 | interface Ethernet10 19 | description test 20 | ip address 10.144.144.1/24 21 | ! 22 | """ 23 | 24 | clnt.api.add_configlet(configletName,configlet) 25 | -------------------------------------------------------------------------------- /docs/labs/lab05-device-management/remove_image_from_device.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | image_name = "vEOS-4.26.0.1F" 16 | image = clnt.api.get_image_bundle_by_name(image_name) 17 | 18 | device_name = "tp-avd-leaf2" 19 | device = clnt.api.get_device_by_name(device_name) 20 | 21 | clnt.api.remove_image_from_device(image, device) 22 | -------------------------------------------------------------------------------- /docs/labs/lab04-container-management/add_image_to_container.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | image_name = "vEOS-4.26.0.1F" 16 | image = clnt.api.get_image_bundle_by_name(image_name) 17 | 18 | container_name = "TP_FABRIC" 19 | container = clnt.api.get_container_by_name(container_name) 20 | 21 | clnt.api.apply_image_to_container(image, container) 22 | -------------------------------------------------------------------------------- /docs/labs/lab04-container-management/remove_image_from_container.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | image_name = "vEOS-4.26.0.1F" 16 | image = clnt.api.get_image_bundle_by_name(image_name) 17 | 18 | container_name = "TP_FABRIC" 19 | container = clnt.api.get_container_by_name(container_name) 20 | 21 | clnt.api.remove_image_from_container(image, container) 22 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/assign_configlet_to_device.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision 13 | clnt = CvpClient() 14 | clnt.connect(nodes=['cvp1'], username="username",password="password") 15 | 16 | configletName = 'cvprac_example2' 17 | 18 | device_name = "tp-avd-leaf1" 19 | device = clnt.api.get_device_by_name(device_name) 20 | 21 | configlet = clnt.api.get_configlet_by_name(configletName) 22 | 23 | clnt.api.apply_configlets_to_device("", device, [configlet]) 24 | -------------------------------------------------------------------------------- /docs/release-notes-1.4.2.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.4.2 3 | ###### 4 | 5 | 2025-10-20 6 | 7 | Enhancements 8 | ^^^^^^^^^^^^ 9 | 10 | * Add Github actions for lint and unittests. Add PR template. (`294 `_) [`mharista `_] 11 | * Add exception handling around network provisioning APIs for CVP 2025.2.X+. (`295 `_) [`mharista `_] 12 | * Bump API version for enrollment token API move to resource API. (`297 `_) [`mharista `_] 13 | 14 | Fixed 15 | ^^^^^ 16 | 17 | * Fix versions format in develop branch to allow installs/testing from develop branch. (`296 `_) [`mharista `_] 18 | -------------------------------------------------------------------------------- /docs/labs/lab04-container-management/assign_configlet_to_container.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | container_name = "TP_LEAFS" 16 | 17 | configletName = 'cvprac_example2' 18 | 19 | container = clnt.api.get_container_by_name(container_name) 20 | 21 | configlet = clnt.api.get_configlet_by_name(configletName) 22 | 23 | clnt.api.apply_configlets_to_container("", container, [configlet]) 24 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/create_svc_account.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision using user/password (on-prem only) 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | username = "cvprac2" 17 | description = "test cvprac" 18 | roles = ["network-admin", "clouddeploy"] # both role names and role IDs are supported 19 | status = 1 # 1 is equivalent to "ACCOUNT_STATUS_ENABLED" 20 | clnt.api.svc_account_set(username, description, roles, status) 21 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/move_device.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | with open("token.tok") as f: 13 | token = f.read().strip('\n') 14 | 15 | clnt = CvpClient() 16 | clnt.connect(nodes=['cvp1'], username='',password='',api_token=token) 17 | 18 | container = clnt.api.get_container_by_name('TP_LEAFS') # container object 19 | 20 | app_name = "my app" # can be any string 21 | 22 | device = {"key":"00:1c:73:c5:4c:87", "fqdn":"co633.ire.aristanetworks.com"} 23 | 24 | move_device_to_container(app_name, device, container) 25 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/update_configlet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | # Modify existing configlet 16 | 17 | configletName = "cvprac_example" 18 | 19 | configlet = """! 20 | interface Ethernet10 21 | description DUB_R04 22 | ip address 10.144.144.2/24 23 | ! 24 | """ 25 | 26 | configletID = clnt.api.get_configlet_by_name(configletName)['key'] 27 | 28 | clnt.api.update_configlet( configlet, configletID, configletName) 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | rpmbuild 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | docs/_modules/modules_by_category.rst 55 | docs/_modules/list_of_*.rst 56 | docs/_modules/*_module.rst 57 | 58 | # .DS_Store 59 | .DS_Store 60 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/delete_svc_account_created_by_user.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision using user/password (on-prem only) 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | svc_accounts = clnt.api.svc_account_get_all() 17 | created_by = 'john.smith' 18 | 19 | # Delete service accounts created by user john.smith 20 | for account in svc_accounts: 21 | if account['value']['created_by'] == created_by: 22 | clnt.api.svc_account_delete(account['value']['key']['name']) 23 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/add_new_user_onprem.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | from getpass import getpass 12 | 13 | # Create connection to CloudVision 14 | clnt = CvpClient() 15 | clnt.connect(['cvp1'],'username', 'password') 16 | 17 | username = "cvpuser2" 18 | password = getpass() 19 | role = "network-admin" 20 | status = "Enabled" 21 | first_name = "Cloud" 22 | last_name = "Vision" 23 | email = "cvp@arista.com" 24 | utype = "TACACS" 25 | 26 | try: 27 | clnt.api.add_user(username,password,role,status,first_name,last_name,email,utype) 28 | except CvpApiError as e: 29 | print(e) 30 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/create_svc_account_token.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision using user/password (on-prem only) 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | username = "cvprac2" 17 | duration = "31536000s" # 1 year validity 18 | description = "test cvprac" 19 | svc_token = clnt.api.svc_account_token_set(username, duration, description) 20 | 21 | # Write the token to file in .tok format 22 | with open(svc_token[0]['value']['user'] + ".tok", "w") as f: 23 | f.write(svc_token[0]['value']['token']) 24 | -------------------------------------------------------------------------------- /cvprac.spec: -------------------------------------------------------------------------------- 1 | Name: cvprac 2 | Version: Replaced_by_make 3 | Release: 1%{?dist} 4 | Summary: REST API client for communicating with an Arista Cloudvision(R) Portal node. 5 | 6 | Group: Development/Libraries 7 | License: BSD (3-clause) 8 | URL: http://www.arista.com 9 | Source0: %{name}-%{version}.tar.gz 10 | BuildArch: noarch 11 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 12 | 13 | %description 14 | The cvprac package is part of the devops library for EOS developed by Arista. The cvprac provides a python REST API client for communicating with an Arista Cloudvision(R) Portal node. 15 | 16 | %prep 17 | %setup -q 18 | 19 | %build 20 | %{__python} setup.py build 21 | 22 | %install 23 | rm -rf %{buildroot} 24 | %{__python} setup.py install --skip-build --root $RPM_BUILD_ROOT 25 | 26 | %clean 27 | rm -rf $RPM_BUILD_ROOT 28 | 29 | %postun 30 | 31 | %files 32 | %defattr(-,root,root,-) 33 | %{python_sitelib}/cvprac* 34 | 35 | %changelog 36 | * Thu Apr 19 2016 John Corbin 37 | -- Initial Build. 38 | -------------------------------------------------------------------------------- /.github/workflows/unittest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Run Unit-tests 3 | 4 | on: push 5 | 6 | jobs: 7 | run_unit_test: 8 | runs-on: ubuntu-22.04 9 | strategy: 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 23 | if [ -f dev-requirements.txt ]; then pip install -r dev-requirements.txt; fi 24 | - name: Lint with pylinit 25 | continue-on-error: true 26 | run: | 27 | make pylint 28 | - name: Run unit tests 29 | if: success() || failure() 30 | run: | 31 | make unittest 32 | make coverage_report 33 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/reorder_configlet_on_device.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | configletNames = ['tp-avd_tp-avd-leaf1','vlan144','api_models'] 16 | 17 | device_name = "tp-avd-leaf1" 18 | device = clnt.api.get_device_by_name(device_name) 19 | 20 | configlets = [] 21 | 22 | for name in configletNames: 23 | configlets.append(clnt.api.get_configlet_by_name(name)) 24 | 25 | # Apply configlets in the order specified in the list 26 | clnt.api.apply_configlets_to_device("", device, configlets, reorder_configlets=True) 27 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/change_control_workflow.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | from datetime import datetime 11 | 12 | # Note API token auth method is not yet supported with Change Controls 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | ccid = 'cvprac0904211418' 17 | name = "cvprac CC test" 18 | tlist = ['1021','1020','1019','1018'] 19 | 20 | ### Create Change control with the list of tasks 21 | clnt.api.create_change_control_v3(ccid, name, tlist) 22 | 23 | ### Approve CC 24 | clnt.api.approve_change_control('cvprac0904211418', timestamp=datetime.utcnow().isoformat() + 'Z') 25 | 26 | ### Execute CC 27 | clnt.api.execute_change_controls(['cvprac0904211418']) 28 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/add_new_user_cvaas.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | from cvprac.cvp_client import CvpClient 12 | 13 | # Create connection to CloudVision using Service Account token 14 | with open("cvaas.tok") as f: 15 | token = f.read().strip('\n') 16 | 17 | clnt = CvpClient() 18 | clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token) 19 | 20 | username = "john" 21 | password = "" 22 | role = "network-admin" 23 | status = "Enabled" 24 | first_name = "John" 25 | last_name = "Smith" 26 | email = "john.smith@abc.xyz" 27 | utype = "SSO" 28 | 29 | try: 30 | clnt.api.add_user(username,password,role,status,first_name,last_name,email,utype) 31 | except CvpApiError as e: 32 | print(e) 33 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/get_running_configs.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | # Get the full inventory 16 | inventory = clnt.api.get_inventory() 17 | 18 | # Create a list of MAC addresses 19 | device_macs = [] 20 | for i in inventory: 21 | device_macs.append(i['systemMacAddress']) 22 | 23 | # Create a dictionary with MAC to running-config mapping 24 | running_configs = {} 25 | for i in device_macs: 26 | running_configs[i] = clnt.api.get_device_configuration(i) 27 | 28 | # Write the running-configs of each device using the hostname as the filename 29 | for i in inventory: 30 | with open(i['fqdn']+'.cfg', 'w') as f: 31 | f.write(running_configs[i['systemMacAddress']]) -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/svc_account_misc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision using user/password (on-prem only) 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | # Get all service accounts states 17 | 18 | accounts = clnt.api.svc_account_get_all() 19 | 20 | # Get specific service account state 21 | 22 | account = clnt.api.svc_account_get_one("cvprac2") 23 | 24 | # Get all service account token states 25 | 26 | tokens = clnt.api.svc_account_token_get_all() 27 | 28 | # Get specific token state 29 | 30 | token = clnt.api.svc_account_token_get_one("9bfb39ff892c81d6ac9f25ff95d0389719595feb") 31 | 32 | # Delete a service account token 33 | 34 | clnt.api.svc_account_token_delete("9bfb39ff892c81d6ac9f25ff95d0389719595feb") 35 | -------------------------------------------------------------------------------- /docs/labs/lab04-container-management/rename_container.py: -------------------------------------------------------------------------------- 1 | from cvprac.cvp_client import CvpClient 2 | import ssl 3 | ssl._create_default_https_context = ssl._create_unverified_context 4 | import requests.packages.urllib3 5 | requests.packages.urllib3.disable_warnings() 6 | 7 | # Create connection to CloudVision 8 | clnt = CvpClient() 9 | clnt.connect(['cvp1'],'username', 'password') 10 | oldName = "test" 11 | newName = "test121" 12 | 13 | container_id = clnt.api.get_container_by_name(oldName)['key'] 14 | 15 | data = {"data":[{"info": "Container {} renamed from {}".format(newName, oldName), 16 | "infoPreview": "Container {} renamed from {}".format(newName, oldName), 17 | "action": "update", 18 | "nodeType": "container", 19 | "nodeId": container_id, 20 | "toId":"", 21 | "fromId":"", 22 | "nodeName": newName, 23 | "fromName": "", 24 | "toName": "", 25 | "toIdType": "container", 26 | "oldNodeName": oldName 27 | } 28 | ] 29 | } 30 | 31 | clnt.api._add_temp_action(data) 32 | clnt.api._save_topology_v2([]) 33 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/add_users_from_csv_cvaas.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from cvprac.cvp_client_errors import CvpApiError 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | from cvprac.cvp_client import CvpClient 12 | import csv 13 | 14 | # Create connection to CloudVision using Service Account token 15 | with open("cvaas.tok") as f: 16 | token = f.read().strip('\n') 17 | 18 | clnt = CvpClient() 19 | clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token) 20 | 21 | 22 | with open("aaa_users.csv") as csvfile: 23 | for i in csv.DictReader(csvfile): 24 | data = dict(i) 25 | try: 26 | clnt.api.add_user(data['username'], "", data['role'], data['status'], data['first_name'], data['last_name'], data['email'], data['user_type']) 27 | except CvpApiError as e: 28 | print(e) 29 | print ("Adding user {} to CVaaS".format(data['username'])) 30 | -------------------------------------------------------------------------------- /docs/release-notes-1.3.2.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.3.2 3 | ###### 4 | 5 | 2023-12-14 6 | 7 | Enhancements 8 | ^^^^^^^^^^^^ 9 | 10 | * Add handling of new password change logout functionality in 2023.1.0. (`254 `_) [`mharista `_] 11 | * Add support for config validation during config assign. (`255 `_) [`noredistribution `_] 12 | * Add support for config validation during config removal. (`256 `_) [`noredistribution `_] 13 | 14 | Fixed 15 | ^^^^^ 16 | 17 | * Add ability to use device_decommissioning for unprovisioned devices. (`253 `_) [`noredistribution `_] 18 | * Add check to connect() to ensure token works. (`258 `_) [`chetryan `_] 19 | 20 | Documentation 21 | ^^^^^^^^^^^^^ 22 | 23 | * Add documentation for CVaaS regional URLs. (`259 `_) [`noredistribution `_] 24 | -------------------------------------------------------------------------------- /docs/release-notes-1.4.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.4.0 3 | ###### 4 | 5 | 2024-5-6 6 | 7 | Enhancements 8 | ^^^^^^^^^^^^ 9 | 10 | * Move from pkg_resources to packaging for Python 3.12 support. (`271 `_) [`mharista `_] 11 | * Add support for searchTopology V3 endpoint. (`275 `_) [`mharista `_] 12 | 13 | Fixed 14 | ^^^^^ 15 | 16 | * Add missing url encoding for get_user. (`264 `_) [`noredistribution `_] 17 | * Updated the enrollment endpoint for CVaaS. (`269 `_) [`noredistribution `_] 18 | * Add timeout to get_configlets_and_mappers(). (`270 `_) [`noredistribution `_] 19 | * Update setup.py to reference python3 only. (`272 `_) [`mharista `_] 20 | * Python3 lint/format fixes. (`273 `_) [`mharista `_] 21 | -------------------------------------------------------------------------------- /docs/labs/lab07-aaa/create_terminattr_tokens.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | # 5 | # Example script to generate the TerminAttr token via REST API from CVaaS and CV on-prem 6 | # and save them to a file 7 | 8 | from cvprac.cvp_client import CvpClient 9 | from pprint import pprint as pp 10 | import ssl 11 | ssl._create_default_https_context = ssl._create_unverified_context 12 | import requests.packages.urllib3 13 | requests.packages.urllib3.disable_warnings() 14 | 15 | # Reading the service account token from a file 16 | with open("cvaas.tok") as f: 17 | token = f.read().strip('\n') 18 | 19 | clnt = CvpClient() 20 | clnt.connect(nodes=['www.arista.io'], username='',password='',is_cvaas=True, api_token=token) 21 | 22 | terminattr_token = clnt.api.create_enroll_token('720h') 23 | with open('cv-onboarding-token', 'w') as f: 24 | f.write(terminattr_token[0]['enrollmentToken']['token']) 25 | 26 | primary = CvpClient() 27 | primary.connect(nodes=['cvp1'], username='username',password='password') 28 | 29 | terminattr_token = primary.api.create_enroll_token('720h') 30 | 31 | with open('token', 'w') as f: 32 | f.write(terminattr_token['data']) 33 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/get_running_configs_by_time.py: -------------------------------------------------------------------------------- 1 | from cvprac.cvp_client import CvpClient 2 | import ssl 3 | ssl._create_default_https_context = ssl._create_unverified_context 4 | import requests.packages.urllib3 5 | requests.packages.urllib3.disable_warnings() 6 | 7 | clnt = CvpClient() 8 | clnt.connect(nodes=['cvp1'], username="username",password="password") 9 | 10 | ts = "2021-11-19T15:04:05.0Z" # rfc3339 time 11 | uri = "/api/v3/services/compliancecheck.Compliance/GetConfig" 12 | 13 | # Fetch the inventory 14 | inventory = clnt.api.get_inventory() 15 | 16 | # Iterate through all devices and get the running-config at the specified time for each device 17 | for device in inventory: 18 | sn = device['serialNumber'] 19 | data = {"request":{ 20 | "device_id": sn, 21 | "timestamp": ts, 22 | "type":"RUNNING_CONFIG" 23 | } 24 | } 25 | try: 26 | resultRunningConfig = clnt.post(uri, data=data) 27 | for idx in resultRunningConfig: 28 | if 'config' in idx: 29 | result = idx['config'] 30 | break 31 | with open(device['hostname']+'.cfg','w') as f: 32 | f.write(result) 33 | except Exception as e: 34 | print("Not able to get configuration for device {} - exception {}".format(device['fqdn'], e)) 35 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/remove_all_devices_legacy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | inventory = clnt.api.get_inventory() 16 | 17 | devices = [] 18 | for netelement in inventory: 19 | devices.append(netelement['systemMacAddress']) 20 | 21 | # Remove devices from provisioning 22 | # This is a legacy API call that removes the devices from Network Provisioning 23 | # in CVP versions older than 2021.3.0, however it does not remove them from 24 | # the Device Inventory as that requires the streaming agent (TerminAttr) to be shutdown, 25 | # which this API does not support. 26 | # To fully decommission a device the device_decommissioning() API can be used, which is 27 | # supported from 2021.3.0+. 28 | # Note that using the delete_devices() function post CVP 2021.3.0 the device will be 29 | # automatically added back to the Undefined container. 30 | clnt.api.delete_devices(devices) 31 | -------------------------------------------------------------------------------- /docs/labs/lab05-device-management/set_mgmt_ip.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | 16 | data = {"data":[{"info":"Device IP Address Changed", 17 | "infoPreview":" Device IP Address Changed to 10.83.13.214", 18 | "action":"associate", 19 | "nodeType":"ipaddress", 20 | "nodeId":"", 21 | "toId":"50:08:00:a7:ca:c3", # MAC of the device 22 | "fromId":"", 23 | "nodeName":"", 24 | "fromName":"", 25 | "toName":"tp-avd-leaf1", # hostname 26 | "toIdType":"netelement", 27 | "nodeIpAddress":"10.83.13.219", # the temporary management IP Address 28 | "nodeTargetIpAddress":"10.83.13.214" # the final management IP address 29 | } 30 | ] 31 | } 32 | clnt.api._add_temp_action(data) 33 | 34 | clnt.api._save_topology_v2([]) 35 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/remove_and_decommission_device.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | import uuid 8 | import time 9 | ssl._create_default_https_context = ssl._create_unverified_context 10 | import requests.packages.urllib3 11 | requests.packages.urllib3.disable_warnings() 12 | 13 | # Create connection to CloudVision 14 | clnt = CvpClient() 15 | clnt.connect(nodes=['cvp1'], username="username", password="password") 16 | 17 | device_id = input("Serial number of the device to be decommissioned: ") 18 | request_id = str(uuid.uuid4()) 19 | clnt.api.device_decommissioning(device_id, request_id) 20 | 21 | # This API call will fully decommission the device, ie remove it from both 22 | # Network Provisioning and Device Inventory (telemetry). It send an eAPI request 23 | # to EOS to shutdown the TerminAttr daemon, waits for streaming to stop and then removes 24 | # the device from provisioning and finally decommissions it. This operation can take a few minutes. 25 | # Supported from CVP 2021.3.0+ and CVaaS. 26 | decomm_status = "DECOMMISSIONING_STATUS_SUCCESS" 27 | decomm_result = "" 28 | while decomm_result != decomm_status: 29 | decomm_result = clnt.api.device_decommissioning_status_get_one(request_id)['value']['status'] 30 | time.sleep(10) 31 | 32 | print(decomm_result) 33 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/remove_devices_from_container_legacy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | # Get devices in a specific container 16 | inventory = clnt.api.get_devices_in_container("Undefined") 17 | 18 | # Create device list 19 | devices = [] 20 | for netelement in inventory: 21 | devices.append(netelement['systemMacAddress']) 22 | 23 | # Remove devices from provisioning 24 | # This is a legacy API call that removes the devices from Network Provisioning 25 | # in CVP versions older than 2021.3.0, however it does not remove them from 26 | # the Device Inventory as that requires the streaming agent (TerminAttr) to be shutdown, 27 | # which this API does not support. 28 | # To fully decommission a device the device_decommissioning() API can be used, which is 29 | # supported from 2021.3.0+. 30 | # Note that using the delete_devices() function post CVP 2021.3.0 the device will be 31 | # automatically added back to the Undefined container. 32 | clnt.api.delete_devices(devices) 33 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/remove_devices_legacy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision using Service account token 12 | with open("token.tok") as f: 13 | token = f.read().strip('\n') 14 | 15 | clnt = CvpClient() 16 | clnt.connect(nodes=['cvp1'], username='', password='', api_token=token) 17 | 18 | devices = ["50:08:00:a7:ca:c3","50:08:00:b1:5b:0b","50:08:00:60:c6:76", 19 | "50:08:00:25:9d:36","50:08:00:8b:ee:b1","50:08:00:8c:22:49"] 20 | 21 | # Remove devices from provisioning 22 | # This is a legacy API call that removes the devices from Network Provisioning 23 | # in CVP versions older than 2021.3.0, however it does not remove them from 24 | # the Device Inventory as that requires the streaming agent (TerminAttr) to be shutdown, 25 | # which this API does not support. 26 | # To fully decommission a device the device_decommissioning() API can be used, which is 27 | # supported from 2021.3.0+. 28 | # Note that using the delete_devices() function post CVP 2021.3.0 the device will be 29 | # automatically added back to the Undefined container. 30 | clnt.api.delete_devices(devices) 31 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.7.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.7 3 | ###### 4 | 5 | 2021-7-1 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added new method for searching for a device by serial_number. (`f23e154 `_) [`titom73 `_] 11 | 12 | Enhancements 13 | ^^^^^^^^^^^^ 14 | 15 | * Added form parameter to update_configlet_builder function. (`e5e3719 `_) [`mharista `_] 16 | * Added parameter to apply_configlets_to_device for reordering existing configlets. (`6681800 `_) [`mharista `_] 17 | * Added documentation examples of cvprac usage in docs/labs. (`b1c3443 `_) [`noredistribution `_] 18 | * Added support for searching by hostname to get_device_by_name. (`1eb08cb `_) [`titom73 `_] 19 | * Added lab example for saving topology with specific tempaction data. (`f3282a2 `_) [`noredistribution `_] 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Type of Change 4 | - [ ] New feature 5 | - [ ] Bug fix 6 | - [ ] Documentation update 7 | - [ ] Refactoring 8 | - [ ] Hotfix 9 | - [ ] Security patch 10 | - [ ] UI/UX improvement 11 | - [ ] Version Bump 12 | 13 | ## Closes 14 | 15 | 16 | Closes X 17 | 18 | ## Depends On 19 | 20 | Depends on X 21 | 22 | ## Description 23 | 24 | 25 | ## Testing 26 | 27 | 28 | ## Impact 29 | 30 | 31 | 32 | ## Additional Information 33 | 34 | 35 | 36 | ## Effort required on reviewer's end 37 | - [ ] Easy 38 | - [ ] Medium 39 | - [ ] Hard 40 | 41 | ## Checklist 42 | - [ ] I have performed a self-review of my own code 43 | - [ ] I have commented my code, particularly in hard-to-understand areas 44 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/get_applied_netelements.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | import argparse 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision 13 | clnt = CvpClient() 14 | clnt.connect(nodes=['cvp1'], username="username", password="password") 15 | 16 | parser = argparse.ArgumentParser( 17 | description='Get the list of devices and containers a configlet is attached to') 18 | parser.add_argument('-c', '--configlet', required=True, help='The name of the configlet') 19 | args = parser.parse_args() 20 | 21 | configlet_name = args.configlet 22 | devices = clnt.api.get_applied_devices(configlet_name) 23 | 24 | containers = clnt.api.get_applied_containers(configlet_name) 25 | print(f"Total number of devices {configlet_name} is attached to: {devices['total']}\n") 26 | print(f"Total number of containers {configlet_name} is attached to: {containers['total']}\n") 27 | col1 = "Device FQDN/hostname" 28 | col2 = "IP Address" 29 | print(f"{col1:<40}{col2:<40}") 30 | print("="*80) 31 | for device in devices['data']: 32 | print(f"{device['hostName']:<40}{device['ipAddress']}") 33 | 34 | print("\nList of containers:\n") 35 | for container in containers['data']: 36 | print(container['containerName']) 37 | -------------------------------------------------------------------------------- /docs/release-notes-1.2.2.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.2.2 3 | ###### 4 | 5 | 2022-10-6 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Add function for getUsers API. (`203 `_) [`mharista `_] 11 | * Added service account Resource APIs. (`208 `_) [`noredistribution `_] 12 | 13 | Enhancements 14 | ^^^^^^^^^^^^ 15 | 16 | * Updated and added examples. (`205 `_) [`noredistribution `_] 17 | * Update documentation for PR semantics. (`206 `_) [`tgodaA `_] 18 | * Update development tools. (`209 `_) [`mharista `_] 19 | * Update system tests for CVP 2022.2.0 support. (`2fcf3d2 `_) [`mharista `_] 20 | * Simplified CVP version handling. (`211 `_) [`mharista `_] 21 | 22 | Fixed 23 | ^^^^^ 24 | 25 | * Raise error for JSON decoding when incomplete block is found. (`202 `_) [`mharista `_] 26 | * Fixed issue with "data" key for Resource API GetAll calls that return a single object. (`215 `_) [`mharista `_] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ======= 2 | License 3 | ======= 4 | 5 | Copyright (c) 2017, Arista Networks EOS+ 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name of the {organization} nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.5.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.5 3 | ###### 4 | 5 | 2021-2-11 6 | 7 | Enhancements 8 | ^^^^^^^^^^^^ 9 | 10 | * Never fallback to HTTP in case of connection failure. (`c2e6d97 `_) [`freedge `_] 11 | * Add client handling for CVP 2020.3. Update get_logs_by_id for CVP 2020.3. (`ac23188 `_) [`mharista `_] 12 | * Add more detailed docstring to check_compliance function. (`e1ad7e8 `_) [`mharista `_] 13 | * Update get_device_by_name and get_device_by_mac to use search_topology instead of get_inventory. (`a2b35cb `_) [`mharista `_] 14 | * Add general support for using api tokens for access to REST API. (`409b68d `_) [`mharista `_] 15 | * Updated/Enhanced user APIs. (`0c652ea `_) [`noredistribution `_] 16 | * Update approve_change_control to provide current time timestamp as default. (`fb13861 `_) [`colinmacgiolla `_] 17 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/change_control_workflow_rapi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | # 5 | # NOTE: The following example is using the new Change Control Resource APIs supported in 2021.2.0 or newer and in CVaaS. 6 | # For CVaaS service-account token based auth has to be used. 7 | 8 | from cvprac.cvp_client import CvpClient 9 | import ssl 10 | import uuid 11 | from datetime import datetime 12 | ssl._create_default_https_context = ssl._create_unverified_context 13 | import requests.packages.urllib3 14 | requests.packages.urllib3.disable_warnings() 15 | 16 | # Create connection to CloudVision 17 | clnt = CvpClient() 18 | clnt.connect(['cvp1'],'username', 'password') 19 | 20 | # Generate change control id and change control name 21 | cc_id = str(uuid.uuid4()) 22 | name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}" 23 | 24 | # Select the tasks and create a CC where all tasks will be run in parallel 25 | tasks = ["1249","1250","1251","1252"] 26 | clnt.api.change_control_create_for_tasks(cc_id, name, tasks, series=False) 27 | 28 | # Approve the change control 29 | approve_note = "Approving CC via cvprac" 30 | clnt.api.change_control_approve(cc_id, notes=approve_note) 31 | 32 | # # Schedule the change control 33 | # # Executing scheduled CCs might only work post 2021.3.0+ 34 | # schedule_note = "Scheduling CC via cvprac" 35 | # schedule_time = "2021-12-23T03:17:00Z" 36 | # clnt.api.change_control_schedule(cc_id,schedule_time,notes=schedule_note) 37 | 38 | # Start the change control 39 | start_note = "Start the CC via cvprac" 40 | clnt.api.change_control_start(cc_id, notes=start_note) -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/backup_configletsV2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | # 5 | # Get configlets and save them to individual files using multi threading 6 | from cvprac.cvp_client import CvpClient 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | from concurrent.futures import ThreadPoolExecutor 12 | from functools import wraps 13 | 14 | # Create connection to CloudVision 15 | clnt = CvpClient() 16 | clnt.connect(['cvp1'],'username', 'password') 17 | 18 | total = clnt.api.get_configlets(start=0,end=1)['total'] 19 | 20 | def get_list_of_configlets(): 21 | """ 22 | Create a thread pool and download specified urls 23 | """ 24 | 25 | futures_list = [] 26 | results = [] 27 | 28 | with ThreadPoolExecutor(max_workers=40) as executor: 29 | for i in range(0,total+1,10): 30 | futures = executor.submit(clnt.api.get_configlets, start=i,end=i+10) 31 | futures_list.append(futures) 32 | 33 | for future in futures_list: 34 | try: 35 | result = future.result(timeout=60) 36 | results.append(result) 37 | except Exception: 38 | results.append(None) 39 | print(future.result()) 40 | return results 41 | 42 | if __name__ == "__main__": 43 | 44 | results = get_list_of_configlets() 45 | for future in results: 46 | for configlet in future['data']: 47 | with open(configlet['name'],'w') as f: 48 | f.write(configlet['config']) 49 | -------------------------------------------------------------------------------- /cvprac/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017, Arista Networks, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # Neither the name of Arista Networks nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS 24 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 27 | # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 30 | # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | # 32 | ''' RESTful API Client class for Cloudvision(R) Portal 33 | ''' 34 | 35 | __version__ = '1.4.2.dev' 36 | __author__ = 'Arista Networks, Inc.' 37 | -------------------------------------------------------------------------------- /docs/release-notes-1.3.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.3.0 3 | ###### 4 | 5 | 2023-2-28 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added functions for role APIs. (`238 `_) [`vmmor `_] 11 | * Added support for proxies. (`243 `_) [`mharista `_] 12 | 13 | Enhancements 14 | ^^^^^^^^^^^^ 15 | 16 | * Add timeouts as configurable parameters for system tests. (`228 `_) [`mharista `_] 17 | * Update delete_change_control() to call new Resource API endpoint for supported versions of CVP. (`230 `_) [`mharista `_] 18 | 19 | Fixed 20 | ^^^^^ 21 | 22 | * Fixed format to allow usage of latest requests package. (`227 `_) [`mharista `_] 23 | * Fixed Service Account functions to use State endpoints instead of Config endpoints. (`235 `_) [`noredistribution `_] 24 | * Handle case for get_parent_container_for_device() returning None within reset_device(). (`240 `_) [`Shivani-chourasiya `_] 25 | 26 | Documentation 27 | ^^^^^^^^^^^^^ 28 | 29 | * Add end to end provisioning example for ATD. (`229 `_) [`noredistribution `_] 30 | * Add example to list applied devices and containers for specified configlet. (`241 `_) [`noredistribution `_] 31 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.1.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.1 3 | ###### 4 | 5 | 2019-1-16 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Add cancel_image API method for removing an added image before it is saved. (`76 `_) [`mharista `_] 11 | Used in system tests for add_image to reset the CVP node under test to its original state. 12 | * Add delete_image_bundle API method for removing an image bundle. (`76 `_) [`mharista `_] 13 | Used in system tests for add_update_delete_image_bundle to reset the CVP node under test to its original state. 14 | 15 | Enhancements 16 | ^^^^^^^^^^^^ 17 | 18 | * Updated all necessary modules and tests to support Python 3. (`76 `_) [`mharista `_] 19 | * Updated add_image method to use cvp_client object. (`76 `_) [`mharista `_] 20 | * Updated system tests to put CVP node under test back into its original state. (`76 `_) [`mharista `_] 21 | 22 | Fixed 23 | ^^^^^ 24 | 25 | * Fixed usages of urllib quote_plus method to support Python 3. (`815ec47 `_) [`mharista `_] 26 | Python 2 vs Python 3 require importing the method from different modules. 27 | * Fixed formatting issues with client _make_request that were causing systests ran with Python 3 to fail. (`76 `_) [`mharista `_] 28 | When running systests with Python 3 the old _make_request was running UnboundLocalError. 29 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/get_configlets.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | # 5 | # Get list of configlets in parallel 6 | 7 | from cvprac.cvp_client import CvpClient 8 | import ssl 9 | from concurrent.futures import ThreadPoolExecutor 10 | ssl._create_default_https_context = ssl._create_unverified_context 11 | import requests.packages.urllib3 12 | requests.packages.urllib3.disable_warnings() 13 | 14 | # Create connection to CloudVision 15 | clnt = CvpClient() 16 | 17 | clnt.connect(nodes=['cvp1'], username="username",password="password") 18 | 19 | import time 20 | from functools import wraps 21 | 22 | def get_list_of_configlets(configlets): 23 | """ 24 | Create a thread pool and download specified urls 25 | """ 26 | 27 | futures_list = [] 28 | results = [] 29 | 30 | with ThreadPoolExecutor(max_workers=40) as executor: 31 | for configlet in configlets: 32 | futures = executor.submit(clnt.api.get_configlet_by_name, configlet) 33 | futures_list.append(futures) 34 | 35 | for future in futures_list: 36 | try: 37 | result = future.result(timeout=60) 38 | results.append(result) 39 | except Exception: 40 | results.append(None) 41 | return results 42 | 43 | if __name__ == "__main__": 44 | # Example with pre-defined list 45 | configlets = ["tp-avd_tp-avd-leaf1","tp-avd_tp-avd-leaf2","tp-avd_tp-avd-leaf3","tp-avd_tp-avd-leaf4"] 46 | 47 | # Example with loading list of configlets from a file 48 | # with open("configlet_list.txt") as f: 49 | # configlets = f.read().splitlines() 50 | 51 | results = get_list_of_configlets(configlets) 52 | for result in results: 53 | print(result) 54 | -------------------------------------------------------------------------------- /docs/release-notes-0.7.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v0.7.0 3 | ###### 4 | 5 | 2017-03-30 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | 11 | Enhancements 12 | ^^^^^^^^^^^^ 13 | 14 | * Add image upgrade feature to api. (`19 `_) [`mharista `_] 15 | Added functions for applying image bundles to devices and adding or removing image bundles from containers. Also added container and image information gathering functions. 16 | * Add deploy_device functionality (`23 `_) [`mharista `_] 17 | Added function deploy_device and helper functions for automated deploying of a device from the a container (for example the Undefined container), to the proper end container. Applies any necessary configlets and optionally an image in the process. Also added new functionality for getting information related to containers and devices (for example getting a devices parent container, or a list of devices in a container). Made task creation optional for calls that would normally create a task. Default behavior is to create the task, but if the user wants to take multiple actions for execution in one task (as done in deploy_device) the user can tell the function not to create the task (essentially what this does is delay the call to saveTopology while adding multiple tempActions). 18 | * Add ability for user to configure log level. (`26 `_) [`mharista `_] 19 | Logging level can now we set during client initialization or changed at any time using a client setter. 20 | 21 | Fixed 22 | ^^^^^ 23 | 24 | * Make REST call to addTempAction before saveTopology. (`17 `_) 25 | New CVP version requires calls to addTempAction before saveTopology or the save will not do anything. In the CVP UI creating a container is the equivalent of addTempAction and then clicking save at the bottom to confirm the container remains is the equivalent of saveTopology. 26 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.6.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.6 3 | ###### 4 | 5 | 2021-5-17 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added API method update_configlet_builder and test. (`a32dd7a `_) [`dbm79 `_] 11 | * Added function and test for API endpoint updateReconcileConfiglet.do. (`7e90de9 `_) [`mharista `_] 12 | 13 | Enhancements 14 | ^^^^^^^^^^^^ 15 | 16 | * Add client handling for new resource API REST bindings that return multiple objects in response data. (`bea2d28 `_) [`mharista `_] 17 | 18 | Fixed 19 | ^^^^^ 20 | 21 | * Fix client logout function to use cvprac client post function instead of session post function. (`abaf257 `_) [`mharista `_] 22 | * Mask localhost/127.0.0.1 with node IP for configlet builder scripts. (`d45ac6e `_) [`Rajat Bajaj `_] 23 | * Updating info string to tackle backend inconsistent state when moving devices from the Undefined container. (`82ea8b9 `_) [`noredistribution `_] 24 | * Remove CVaaS un/pw login. Only API tokens for CVaaS now. (`f9fd6b5 `_) [`mharista `_] 25 | * Update redundant functions to self reference. (`0095b00 `_) [`mharista `_] 26 | * Add exception when attempting to delete container with children for CVP versions 2020.1 and beyond. (`35bb566 `_) [`mharista `_] 27 | -------------------------------------------------------------------------------- /docs/release-notes-0.8.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v0.8.0 3 | ###### 4 | 5 | 2017-09-12 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added/updated API function for validating a devices config. (`27 `_) (`updates `_) 11 | New function in API that will return True if the passed in device is running a valid config. Will return False and log error messages if the device validation check fails. 12 | * Add Jenkins integration and pre-commit hook. (`31 `_) [`jerearista `_] 13 | * Update client protocol default to https with http fallback. (`33 `_) [`mharista `_] 14 | With new versions of CVP only supporting https we have changed the default protocol used to https. The protocol parameter passed to the client is no longer used. The client connect will attempt to use https with a fallback of http if it fails to connect via https. A user can force https with no http fallback by providing a path to a valid certificate to the connect method for new parameter cert. Default https connection with no cert provided will use unverified https requests. 15 | 16 | Enhancements 17 | ^^^^^^^^^^^^ 18 | 19 | * Added property to CvpClient that stores last node a request was made to. (`34 `_) [`mharista `_] 20 | New last_used_node function will return the last CVP node a request was sent to. 21 | 22 | Fixed 23 | ^^^^^ 24 | 25 | * Fixed functions for removing images from devices and containers. (`33 `_) [`mharista `_] 26 | Bugs in the code for removing images was causing system tests to fail due to the system under tests to get out of compliance. 27 | * Removed non-default python logging module. (`38 `_) [`grybak-arista `_] 28 | Using default logging built into python. The non-default module was causing problems in some cases when installing with pip. 29 | -------------------------------------------------------------------------------- /docs/labs/lab03-configlet-management/config_search.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(nodes=['cvp1'], username="username",password="password") 14 | 15 | def main(): 16 | 17 | print('Retrieving configlets ...') 18 | 19 | inventory = clnt.api.get_inventory() 20 | data = clnt.api.get_configlets_and_mappers()['data'] 21 | print(data) 22 | 23 | print('Number of configlets:', len(data['configlets'])) 24 | 25 | searchAgain = True 26 | while searchAgain: 27 | try: 28 | search = input( "\nEnter Config Line: " ) 29 | print(f"\n\n\'{search}\' has been found in following configlets:\n\n") 30 | print(f"{'Hostname':<30}{'Serial number':<50}{'MAC address':<30}{'Configlets':<40}") 31 | print("=" * 150) 32 | for i in inventory: 33 | device = i['hostname'] 34 | device_sn = i['serialNumber'] 35 | device_mac = i['systemMacAddress'] 36 | configlet_list = [] 37 | for c in data['configlets']: 38 | for g in data['generatedConfigletMappers']: 39 | if device_mac == g['netElementId'] and c['key'] == g['configletBuilderId'] and search in c['config']: 40 | configlet_list.append(c['name']) 41 | for k in data['configletMappers']: 42 | if device_mac == k['objectId'] and c['key'] == k['configletId'] and search in c['config']: 43 | configlet_list.append(c['name']) 44 | configlet_list_final = ",".join(configlet_list) 45 | if len(configlet_list) > 0: 46 | print(f"{device:<30}{device_sn:<50}{device_mac:<30}{configlet_list_final:<30}") 47 | 48 | except KeyboardInterrupt: 49 | print('\nExiting... \n') 50 | return 51 | 52 | if __name__ == '__main__': 53 | main() 54 | 55 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/gen_builder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | # Create connection to CloudVision 12 | clnt = CvpClient() 13 | clnt.connect(['cvp1'],'username', 'password') 14 | 15 | container_id = clnt.api.get_container_by_name("TP_LEAFS")['key'] 16 | builder_name = 'SYS_TelemetryBuilderV3' 17 | configletBuilderID = clnt.api.get_configlet_by_name(builder_name)['key'] 18 | 19 | payload = {"previewValues":[{ 20 | "fieldId":"vrf", 21 | "value":"red"}], 22 | "configletBuilderId":configletBuilderID, 23 | "netElementIds":[], 24 | "pageType":"container", 25 | "containerId":container_id, 26 | "containerToId":"", 27 | "mode":"assign"} 28 | 29 | preview = clnt.post('/configlet/configletBuilderPreview.do', data=payload) 30 | 31 | generated_names_list = [] 32 | generated_keys_list = [] 33 | 34 | for i in preview['data']: 35 | generated_names_list.append(i['configlet']['name']) 36 | generated_keys_list.append(i['configlet']['key']) 37 | 38 | clnt.get("/configlet/searchConfiglets.do?objectId={}&objectType=container&type=ignoreDraft&queryparam={}&startIndex=0&endIndex=22&sortByColumn=&sortOrder=".format(container_id, builder_name.lower())) 39 | 40 | tempData = {"data":[{ 41 | "info":"Configlet Assign: to container TP_LEAFS", 42 | "infoPreview":"Configlet Assign: to container TP_LEAFS", 43 | "action":"associate", 44 | "nodeType":"configlet", 45 | "nodeId":"", 46 | "toId":container_id, 47 | "fromId":"","nodeName":"","fromName":"", 48 | "toName":"TP_LEAFS", 49 | "toIdType":"container", 50 | "configletList":generated_keys_list, 51 | "configletNamesList":generated_names_list, 52 | "ignoreConfigletList":[], 53 | "ignoreConfigletNamesList":[], 54 | "configletBuilderList":[configletBuilderID], 55 | "configletBuilderNamesList":[builder_name], 56 | "ignoreConfigletBuilderList":[], 57 | "ignoreConfigletBuilderNamesList":[] 58 | } 59 | ] 60 | } 61 | 62 | clnt.api._add_temp_action(tempData) 63 | clnt.api._save_topology_v2([]) 64 | -------------------------------------------------------------------------------- /docs/release-notes-0.9.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v0.9.0 3 | ###### 4 | 5 | 2018-04-17 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added Inventory handling methods. (`51 `_) [`cheynearista `_] 11 | add_device_to_inventory, retry_add_to_inventory, delete_device, delete_devices, get_non_connected_device_count, save_inventory. 12 | * Add basic logout api function. (`d52f4a0 `_) [`mharista `_] 13 | Added for future enhanced handling of session logout. 14 | 15 | Enhancements 16 | ^^^^^^^^^^^^ 17 | 18 | * Add ability to provide a query parameter to get_inventory. (`423d555 `_) [`mharista `_] 19 | Query string can be used as a match to filter returned inventory list. For example you can filter on a specific version of EOS. 20 | * Add contact info to docs. (`693d3ba `_) [`mharista `_] 21 | 22 | Fixed 23 | ^^^^^ 24 | 25 | * Fix get_device_by_name to return only the device with the given FQDN. (`41 `_) 26 | This was returning all devices that contained the provided name as a string in their data. Now verify the FQDN matches the name before returning. 27 | * Fix get_devices_in_container to only return devices in the specified container. (`2b26d0c `_) [`mharista `_] 28 | This was previously returning all devices from get_inventory that matched a query string anywhere in their data instead of specific to the parent container. 29 | * Remove 'id': 1 from data structs in requests. (`52 `_) [`grybak-arista `_] 30 | This key:value being included int he request data causes an error in 2018 versions of CVP. 31 | * Add fix for special characters in object names in url. (`53 `_) [`mharista `_] 32 | Special characters in request parameters (for example a container name Rack2+_DC11) would cause unexpected results. This are now properly escaped for HTTP. 33 | -------------------------------------------------------------------------------- /cvprac/cvp_client_errors.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017, Arista Networks, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # Neither the name of Arista Networks nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS 24 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 27 | # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 30 | # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | # 32 | 33 | ''' CVP Restful API client exception classes 34 | ''' 35 | 36 | class CvpClientError(Exception): 37 | ''' CVP Restful API client error 38 | ''' 39 | def __init__(self, msg): 40 | Exception.__init__(self) 41 | self.msg = msg 42 | 43 | def __str__(self): 44 | return self.msg 45 | 46 | class CvpApiError(CvpClientError): 47 | ''' Error encountered related to the CVP API request. 48 | ''' 49 | def __init__(self, msg): 50 | CvpClientError.__init__(self, msg) 51 | 52 | class CvpLoginError(CvpClientError): 53 | ''' Error encountered trying to login into CVP. 54 | ''' 55 | def __init__(self, msg): 56 | CvpClientError.__init__(self, msg) 57 | 58 | class CvpRequestError(CvpClientError): 59 | ''' CVP request not properly constructed. 60 | ''' 61 | def __init__(self, msg): 62 | CvpClientError.__init__(self, msg) 63 | 64 | class CvpSessionLogOutError(CvpClientError): 65 | ''' Current CVP session has been logged out. 66 | ''' 67 | def __init__(self, msg): 68 | CvpClientError.__init__(self, msg) 69 | -------------------------------------------------------------------------------- /docs/labs/lab02-inventory-operations/compliance_check.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import ssl 7 | ssl._create_default_https_context = ssl._create_unverified_context 8 | import requests.packages.urllib3 9 | requests.packages.urllib3.disable_warnings() 10 | 11 | ### Compliance Code description 12 | compliance = {"0000":"Configuration is in sync", 13 | "0001": "Config is out of sync", 14 | "0002": "Image is out of sync", 15 | "0003": "Config & image out of sync", 16 | "0004": "Config, Image and Device time are in sync", 17 | "0005": "Device is not reachable", 18 | "0006": "The current EOS version on this device is not supported by CVP. Upgrade the device to manage.", 19 | "0007": "Extensions are out of sync", 20 | "0008": "Config, Image and Extensions are out of sync", 21 | "0009": "Config and Extensions are out of sync", 22 | "0010": "Image and Extensions are out of sync", 23 | "0011": "Unauthorized User", 24 | "0012": "Config, Image, Extension and Device time are out of sync", 25 | "0013": "Config, Image and Device time are out of sync", 26 | "0014": "Config, Extensions and Device time are out of sync", 27 | "0015": "Image, Extensions and Device time are out of sync", 28 | "0016": "Config and Device time are out of sync", 29 | "0017": "Image and Device time are out of sync", 30 | "0018": "Extensions and Device time are out of sync", 31 | "0019": "Device time is out of sync" 32 | } 33 | 34 | # Create connection to CloudVision using Service account token 35 | with open("token.tok") as f: 36 | token = f.read().strip('\n') 37 | 38 | clnt = CvpClient() 39 | clnt.connect(nodes=['cvp1'], username='',password='',api_token=token) 40 | 41 | def check_devices_under_container(client, container): 42 | ''' container is the container ID ''' 43 | 44 | nodeId = container['key'] 45 | nodeName = container['name'] 46 | api = '/ztp/getAllNetElementList.do?' 47 | queryParams = "nodeId={}&queryParam=&nodeName={}&startIndex=0&endIndex=0&contextQueryParam=&ignoreAdd=false&useCache=true".format(nodeId, nodeName) 48 | return client.get(api + queryParams) 49 | 50 | 51 | container = clnt.api.get_container_by_name('TP_LEAFS') 52 | 53 | devices = (check_devices_under_container(clnt,container)) 54 | 55 | for device in devices['netElementList']: 56 | code = device['complianceCode'] 57 | print(device['fqdn'], ' ', code,' ', compliance[code]) 58 | -------------------------------------------------------------------------------- /pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Called by "git commit". The hook will run several checks on the 4 | # source-code. If it exits with a non-zero status, the commit will 5 | # be halted. 6 | # 7 | # To enable this hook, link this file to ".git/hooks/pre-commit": 8 | # ln -s ../../pre-commit.sh .git/hooks/pre-commit 9 | # 10 | RED='\033[1;31m' 11 | GREEN='\033[1;32m' 12 | NC='\033[0m' 13 | 14 | if git rev-parse --verify HEAD >/dev/null 2>&1 15 | then 16 | against=HEAD 17 | else 18 | # Initial commit: diff against an empty tree object 19 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 20 | fi 21 | 22 | # If you want to allow non-ASCII filenames set this variable to true. 23 | allownonascii=$(git config --bool hooks.allownonascii) 24 | 25 | # Redirect output to stderr. 26 | exec 1>&2 27 | 28 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 29 | # them from being added to the repository. We exploit the fact that the 30 | # printable range starts at the space character and ends with tilde. 31 | if [ "$allownonascii" != "true" ] && 32 | # Note that the use of brackets around a tr range is ok here, (it's 33 | # even required, for portability to Solaris 10's /usr/bin/tr), since 34 | # the square bracket bytes happen to fall in the designated range. 35 | test $(git diff --cached --name-only --diff-filter=A -z ${against} | 36 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 37 | then 38 | cat <<\EOF 39 | Error: Attempt to add a non-ASCII file name. 40 | 41 | This can cause problems if you want to work with people on other platforms. 42 | 43 | To be portable it is advisable to rename the file. 44 | 45 | If you know what you are doing you can disable this check using: 46 | 47 | git config hooks.allownonascii true 48 | EOF 49 | exit 1 50 | fi 51 | 52 | # If there are whitespace errors, print the offending file names and fail. 53 | git diff-index --check --cached $against -- 54 | 55 | err=0 56 | msg='' 57 | 58 | run(){ 59 | tmp=$(mktemp -t cv-precommit) 60 | echo -n "Running '${*}': " 61 | "$@" >"$tmp" 2>&1 62 | ret=$? 63 | if [ $ret != 0 ]; then 64 | let "err = $err + $ret" 65 | msg="${msg}\n\t'${*}' failed" 66 | echo -e "${RED}FAILED${NC}" 67 | echo "Tried: ${*}" 68 | cat "$tmp" 69 | rm -f "$tmp" 70 | else 71 | echo -e "${GREEN}PASSED${NC}" 72 | fi 73 | return $ret 74 | } 75 | 76 | START_TIME=$SECONDS 77 | run make check 78 | run make pep8 79 | run make pyflakes 80 | run make pylint 81 | 82 | DURATION=$((SECONDS - START_TIME)) 83 | echo "pre-commit checks took ${DURATION} seconds." 84 | echo 85 | 86 | if [ ${err} != 0 ]; then 87 | echo -e "${RED}ERROR: Some checks failed. Commit halted. [${err}]${NC}" 88 | echo -e "${RED}Failed Tests: ${msg}${NC}" 89 | fi 90 | exit ${err} 91 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make 2 | # WARN: gmake syntax 3 | ######################################################## 4 | # Makefile for collector 5 | # 6 | # useful targets: 7 | # make check -- manifest checks 8 | # make clean -- clean up workspace 9 | # make pep8 -- pep8 checks 10 | # make pyflakes -- pyflakes checks 11 | # make pylint -- source code checks 12 | # make rpm -- build RPM package 13 | # make sdist -- build python source distribution 14 | # make systest -- runs the system tests 15 | # make tests -- run all of the tests 16 | # 17 | ######################################################## 18 | # variable section 19 | 20 | NAME = "cvprac" 21 | 22 | PYTHON=python 23 | COVERAGE=coverage 24 | SITELIB = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") 25 | 26 | VERSION := $(shell awk '/__version__/{print $$NF}' cvprac/__init__.py | sed "s/'//g") 27 | 28 | RPMSPECDIR = . 29 | RPMSPEC = $(RPMSPECDIR)/$(NAME).spec 30 | RPMRELEASE = 1 31 | RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)" 32 | 33 | PEP8_IGNORE = E302,E203,E261 34 | ######################################################## 35 | 36 | all: clean check pep8 pyflakes pylint tests 37 | 38 | check: 39 | check-manifest 40 | 41 | clean: 42 | @echo "Cleaning up distutils stuff" 43 | rm -rf rpmbuild build dist MANIFEST *.egg-info .coverage 44 | @echo "Cleaning up byte compiled python stuff" 45 | find . -type f -regex ".*\.py[co]$$" -delete 46 | 47 | coverage_report: 48 | $(COVERAGE) report -m 49 | 50 | pep8: 51 | -pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE) cvprac/ 52 | -pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/lib/ 53 | -pep8 -r --max-line-length=120 --ignore=$(PEP8_IGNORE),E402 test/system/ 54 | -pep8 -r --ignore=$(PEP8_IGNORE),E402,E501 test/unit/ 55 | 56 | pyflakes: 57 | pyflakes cvprac/ test/ 58 | 59 | pylint: 60 | find ./cvprac ./test -name \*.py | xargs pylint --rcfile .pylintrc 61 | 62 | unittest: clean 63 | $(COVERAGE) run --source $(NAME) -m unittest discover test/unit -v 64 | 65 | systest: clean 66 | $(COVERAGE) run --source $(NAME) -m unittest discover test/system -v 67 | 68 | tests: unittest systest coverage_report 69 | 70 | rpmcommon: sdist 71 | @mkdir -p rpmbuild 72 | @cp dist/*.gz rpmbuild/ 73 | @sed -e 's#^Version:.*#Version: $(VERSION)#' -e 's#^Release:.*#Release: $(RPMRELEASE)#' $(RPMSPEC) >rpmbuild/$(NAME).spec 74 | 75 | rpm: rpmcommon 76 | @rpmbuild --define "_topdir %(pwd)/rpmbuild" \ 77 | --define "_builddir %{_topdir}" \ 78 | --define "_rpmdir %{_topdir}" \ 79 | --define "_srcrpmdir %{_topdir}" \ 80 | --define "_specdir $(RPMSPECDIR)" \ 81 | --define "_sourcedir %{_topdir}" \ 82 | --define "_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.rpm" \ 83 | --define "__python /usr/bin/python" \ 84 | -ba rpmbuild/$(NAME).spec 85 | @rm -f rpmbuild/$(NAME).spec 86 | 87 | sdist: clean 88 | $(PYTHON) setup.py sdist 89 | -------------------------------------------------------------------------------- /docs/labs/README.md: -------------------------------------------------------------------------------- 1 | # cvprac labs 2 | 3 | The following lab examples will walk through the most commonly used REST API calls using cvprac 4 | to help users interact with Arista CloudVision easily and automate the provisioning of network devices. 5 | 6 | ## Table of Contents 7 | 8 | - [cvprac labs](#cvprac-labs) 9 | - [Table of Contents](#table-of-contents) 10 | - [Authentication](#authentication) 11 | - [Password Authentication](#password-authentication) 12 | - [Service Account Token Authentication](#service-account-token-authentication) 13 | - [Known Limitations](#known-limitations) 14 | 15 | ## Authentication 16 | 17 | There are two ways to authenticate using the REST APIs: 18 | 19 | - user/password (on-prem only) 20 | - service account token (available on CVP 2020.3.0+ and CVaaS) 21 | 22 | ### Password Authentication 23 | 24 | ```python 25 | from cvprac.cvp_client import CvpClient 26 | clnt = CvpClient() 27 | clnt.connect(['10.83.13.33'],'cvpadmin', 'arastra') 28 | ``` 29 | 30 | ### Service Account Token Authentication 31 | 32 | To access the CloudVision as-a-Service and send API requests, "Service Account Token" is needed. 33 | After obtaining the service account token, it can be used for authentication when sending API requests. 34 | 35 | Service accounts can be created from the Settings page where a service token can be generated as seen below: 36 | 37 | ![serviceaccount1](./static/serviceaccount1.png) 38 | ![serviceaccount2](./static/serviceaccount2.png) 39 | ![serviceaccount3](./static/serviceaccount3.png) 40 | 41 | The token should be copied and saved to a file that can later be referred to. 42 | 43 | ```python 44 | from cvprac.cvp_client import CvpClient 45 | clnt = CvpClient() 46 | with open("token.tok") as f: 47 | token = f.read().strip('\n') 48 | clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token) 49 | ``` 50 | 51 | >NOTE In case of CVaaS the `is_cvaas` parameters has to be set to `True` 52 | 53 | Service accounts are supported on CVP on-prem starting from `2020.3.0`. More details in the [TOI](https://eos.arista.com/toi/cvp-2020-3-0/service-accounts/) and the [CV config guide](https://www.arista.com/en/cg-cv/cv-service-accounts). 54 | 55 | ```python 56 | from cvprac.cvp_client import CvpClient 57 | 58 | with open("token.tok") as f: 59 | token = f.read().strip('\n') 60 | 61 | clnt = CvpClient() 62 | clnt.connect(nodes=['10.83.13.33'], username='',password='',api_token=token) 63 | ``` 64 | 65 | > Note that for CVaaS the correct regional URL must be used including `www.`. Please refer to the main page's [README.md](../../README.md#cvaas) 66 | 67 | ## Known Limitations 68 | 69 | - for any APIs that interact with EOS devices, the service account name must match the name of the username 70 | configured on EOS and CVP 71 | - Support for REST API bindings for the Resource APIs (Lab 8) was added in CVP 2021.1.0 72 | -------------------------------------------------------------------------------- /test/lib/systestlib.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017, Arista Networks, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # Neither the name of Arista Networks nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS 24 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 27 | # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 30 | # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | # 32 | 33 | ''' CVP Restful API Client System Tests 34 | 35 | The DUT is a CVP node. 36 | ''' 37 | import os 38 | import unittest 39 | import yaml 40 | 41 | 42 | def get_fixtures_path(): 43 | ''' Return the path to the fixtures directory. 44 | ''' 45 | return os.path.join(os.path.dirname(__file__), '../fixtures') 46 | 47 | 48 | def get_fixture(filename): 49 | ''' Return a path with the fixtures directory prepended to the filename. 50 | ''' 51 | return os.path.join(get_fixtures_path(), filename) 52 | 53 | 54 | class DutSystemTest(unittest.TestCase): 55 | ''' DutSystemTest class that provides information about the DUTs used. 56 | ''' 57 | def __init__(self, *args, **kwargs): 58 | super().__init__(*args, **kwargs) 59 | 60 | @classmethod 61 | def setUpClass(cls): 62 | ''' Read in the list of CVP node names or IP addresses to use 63 | for testing. 64 | 65 | Format for duts list of dicts: 66 | { node: nodename, user: username, password: password } 67 | ''' 68 | cls.duts = {} 69 | filename = get_fixture('cvp_nodes.yaml') 70 | with open(filename, 'r', encoding="utf-8") as stream: 71 | cls.duts = yaml.safe_load(stream) 72 | stream.close() 73 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/auto_reconcile_on_rc_change.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | # This script can be run as a cronjob to periodically reconcile all devices 6 | # that are out of configuration compliance in environments where the running-config 7 | # is still modified via the CLI often. 8 | from cvprac.cvp_client import CvpClient 9 | import ssl 10 | from datetime import datetime 11 | ssl._create_default_https_context = ssl._create_unverified_context 12 | import requests.packages.urllib3 13 | requests.packages.urllib3.disable_warnings() 14 | clnt = CvpClient() 15 | clnt.set_log_level(log_level='WARNING') 16 | 17 | # Reading the service account token from a file 18 | with open("token.tok") as f: 19 | token = f.read().strip('\n') 20 | 21 | clnt = CvpClient() 22 | clnt.connect(nodes=['cvp1'], username='',password='',api_token=token) 23 | 24 | inventory = clnt.api.get_inventory() 25 | 26 | compliance = {"0001": "Config is out of sync", 27 | "0003": "Config & image out of sync", 28 | "0004": "Config, Image and Device time are in sync", 29 | "0005": "Device is not reachable", 30 | "0008": "Config, Image and Extensions are out of sync", 31 | "0009": "Config and Extensions are out of sync", 32 | "0012": "Config, Image, Extension and Device time are out of sync", 33 | "0013": "Config, Image and Device time are out of sync", 34 | "0014": "Config, Extensions and Device time are out of sync", 35 | "0016": "Config and Device time are out of sync" 36 | } 37 | 38 | non_compliants = [] 39 | taskIds = [] 40 | for device in inventory: 41 | if device['complianceCode'] in compliance.keys(): 42 | # create a list of non-compliant devices for reporting purposes 43 | non_compliants.append(device['hostname']) 44 | dev_mac = device['systemMacAddress'] 45 | # check if device already has reconciled config and save the key if it does 46 | try: 47 | configlets = clnt.api.get_configlets_by_device_id(dev_mac) 48 | for configlet in configlets: 49 | if configlet['reconciled'] == True: 50 | configlet_key = configlet['key'] 51 | break 52 | else: 53 | configlet_key = "" 54 | rc = clnt.api.get_device_configuration(dev_mac) 55 | name = 'RECONCILE_' + device['serialNumber'] 56 | update = clnt.api.update_reconcile_configlet(dev_mac, rc, configlet_key, name, True) 57 | # if the device had no reconciled config, it means we need to append the reconciled 58 | # configlet to the list of applied configlets on the device 59 | if configlet_key == "": 60 | addcfg = clnt.api.apply_configlets_to_device("auto-reconciling",device,[update['data']]) 61 | clnt.api.cancel_task(addcfg['data']['taskIds'][0]) 62 | except Exception as e: 63 | continue 64 | print(f"The non compliant devices were: {str(non_compliants)}") 65 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.2.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.2 3 | ###### 4 | 5 | 2020-2-3 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added API modules for cancel and delete change controls. (`21102ac `_) [`mharista `_] 11 | * Add modules for applying configlets to containers and removing configlets from containers. (`89 `_) [`mharista `_] 12 | * Add module and test for API endpoint /provisioning/v2/validateAndCompareConfiglets.do. (`93 `_) [`mharista `_] 13 | * Updates for CVP 2019. Add search_configlets method. Update how we set the api version. Use pkg_resoureces.parse_version instead of brute force. (`95 `_) [`grybak-arista `_] 14 | * Add method and tests for API endpoint /provisioning/getNetElementInfoById.do. (`d26d69f `_) [`mharista `_] 15 | * Add method and tests for resetting a device back to Undefined container. (`fb9a96b `_) [`mharista `_] 16 | * Add method for adding configletbuilder. (`97 `_) [`networkRob `_] 17 | 18 | Enhancements 19 | ^^^^^^^^^^^^ 20 | 21 | * Add snapshot template key to task list entries for create change control method. (`80 `_) [`mharista `_] 22 | * Update add_devices_to_inventory to support multiple devices in one call. (`84 `_) [`grybak-arista `_] 23 | * Add ability to set API request timeout via client connect method. (`92 `_) [`mharista `_] 24 | * Add return value for cancel_task method and update necessary tests. (`2356d59 `_) [`mharista `_] 25 | * Update existing Change Control APIs and add new ones for 2019.1.0. (`4fddb1e `_) [`mharista `_] 26 | * Update change_control_available_tasks to use standard get_tasks_by_status for 2019.1. (`06eb19a4 `_) [`mharista `_] 27 | 28 | Fixed 29 | ^^^^^ 30 | 31 | * Fix issue where requests fail if response has no json payload. (`81 `_) [`mharista `_] 32 | * Update error handling of unauthorized user for CVP 2019.x. (`f4bf302 `_) [`mharista `_] 33 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.0 3 | ###### 4 | 5 | 2018-12-05 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added method for capturing container level snapshot. (`57 `_) [`brokenpackets `_] 11 | capture_container_level_snapshot. 12 | * Added method for getting event by ID. (`58 `_) [`brokenpackets `_] 13 | get_event_by_id. 14 | * Added method for getting device running config. (`3b97ba3 `_) [`mharista `_] 15 | get_device_configuration. 16 | * Added methods for change control functionality. (`7e49447 `_) [`mharista `_] 17 | get_change_controls, change_control_available_tasks, create_change_control. 18 | * Added method for adding notes to change control. (`e21c0ae `_) [`mharista `_] 19 | add_notes_to_change_control. 20 | * Add assorted methods. (`68 `_) [`grybak-arista `_] 21 | get_configlets, get_configlets_by_container_id, get_configlets_by_netelement_id, add_note_to_configlet, get_all_temp_actions, 22 | get_applied_devices, get_applied_containers, filter_topology, get_default_snapshot_template, capture_container_level_snapshot, 23 | add_image, save_image_bundle, update_image_bundle, execute_change_control, get_change_control_info. 24 | 25 | Enhancements 26 | ^^^^^^^^^^^^ 27 | 28 | * Add flag for create_task to deploy_device. (`59 `_) [`brokenpackets `_] 29 | Flag defaults to True and allows user to decide if they want tasks automatically created when calling deploy_device. 30 | * Allow waiting for task IDs when updating configlets. (`67 `_) [`grybak-arista `_] 31 | Flag for waitForTaskIds parameter added to update_configlet method. Defaults to False. 32 | * Update appropriate APIs and tests to support CVP 2018.2. (`ad7b576 `_) [`mharista `_] 33 | CVP 2018.2 includes large changes to the Restful APIs. Many APIs are deprecated and many APIs return data is slightly different from data returned by 2018.1. 34 | This update does processing to help mitigate some of these changes by fixing return data of 2018.2 to look more like return data from 2018.1 in cases where 35 | fields are removed or have their name key changed. Be aware that though this update catches many of these cases it does not catch all. 36 | 37 | Fixed 38 | ^^^^^ 39 | 40 | * Fix connection reconnect handling for CVP 2018 support. (`e13dc54 `_) [`mharista `_] 41 | Unauthorized handled differently. 42 | -------------------------------------------------------------------------------- /docs/release-notes-1.0.4.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.0.4 3 | ###### 4 | 5 | 2020-8-18 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Add function and tests for getConfigletsAndAssociatedMappers.do API. (`d8e9316 `_) [`mharista `_] 11 | * Add function and tests for getImageBundleByContainerId.do API. (`131783c `_) [`mharista `_] 12 | * Add functions for user management APIs. (`113 `_) [`noredistribution `_] 13 | * Add function for delete user API and update user handling tests. (`681dc06 `_) [`mharista `_] 14 | 15 | Enhancements 16 | ^^^^^^^^^^^^ 17 | 18 | * Add delete() function to client for DELETE REST API requests. (`a8cdc82 `_) [`mharista `_] 19 | * Add new API version (v4) for new CVP version 2020.2. (`546a119 `_) [`mharista `_] 20 | * Update delete_devices() function for new CVP version. (`819c073 `_) [`mharista `_] 21 | * Update get_device_configuration() function for new CVP version. (`9ef992a `_) [`mharista `_] 22 | * Update apiversion variable to be float instead of string for better conditional handling. (`425f395 `_) [`mharista `_] 23 | * Add initial CVaaS support for CVP local users. (`3c28251 `_) [`mharista `_] 24 | * Improve efficiency of get_inventory() by removing extra API calls. (`b9c0e69 `_) [`mharista `_] 25 | * Improve efficiency of get_containers() function and add support for using CVaaS API token. (`82effac `_) [`mharista `_] 26 | * Update documentation with API version handling info and different connection type examples. (`c714262 `_) [`mharista `_] 27 | 28 | Fixed 29 | ^^^^^ 30 | 31 | * Fix issue with different field parameters in image object vs image bundle object. (`7d1b845 `_) [`mharista `_] 32 | * Fix issue with 'errorCode' string being in request response text. (`7ea6570 `_) [`mharista `_] 33 | -------------------------------------------------------------------------------- /docs/release-notes-1.2.0.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | v1.2.0 3 | ###### 4 | 5 | 2022-5-20 6 | 7 | New Modules 8 | ^^^^^^^^^^^ 9 | 10 | * Added new method for creating TerminAttr enrollment token. (`9bf409b `_) [`noredistribution `_] 11 | * Added new methods for managing tags and workspaces. (`e0b8185 `_) [`noredistribution `_] 12 | * Additional methods for managing workspaces. (`71eea87 `_) [`mharista `_] 13 | * Added new methods for change control resource APIs. (`d0b1916 `_) [`noredistribution `_] 14 | * Added new methods for change control scheduling and device decommissioning. (`370d02f `_) [`noredistribution `_] 15 | 16 | Enhancements 17 | ^^^^^^^^^^^^ 18 | 19 | * Added ability to run change control tasks sequentially or in parallel. (`5fd48d5e `_) [`mharista `_] 20 | * Improved system test setup and base classes. (`36029b5 `_) [`KonikaChaurasiya-GSLab `_] 21 | * Do not run getConfigletByName for every configlet to make get_configlets more efficient. (`3c1a3eb `_) [`noredistribution `_] 22 | * Added system tests for new change control resource APIs and more system test infrastructure enhancements. (`4564ef3 `_) [`KonikaChaurasiya-GSLab `_] 23 | * Added system test for new decommission device APIs. (`4fd4b7f `_) [`noredistribution `_] 24 | * Documentation format updates. (`ac4f2ff `_) [`tgodaA `_] 25 | * Assorted system test format updates for various CVP versions support. 26 | * Added support for CVP versions up to CVP 2022.1.0. 27 | 28 | Fixed 29 | ^^^^^ 30 | 31 | * Removed timestamp variable declaration from approve change control function. (`2b1e6ac `_) [`mharista `_] 32 | * Add wrapper function for validate_config that will return the full response data structure. (`0540c7a `_) [`mharista `_] 33 | * Fix request headers for CVaaS. (`f57ea4e `_) [`mharista `_] 34 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/change_control_custom_rapi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | # 5 | # NOTE: The following example is using the new Change Control Resource APIs supported in 2021.2.0 or newer and in CVaaS. 6 | # For CVaaS service-account token based auth has to be used. 7 | 8 | from cvprac.cvp_client import CvpClient 9 | import ssl 10 | import uuid 11 | from datetime import datetime 12 | ssl._create_default_https_context = ssl._create_unverified_context 13 | import requests.packages.urllib3 14 | requests.packages.urllib3.disable_warnings() 15 | 16 | # Create connection to CloudVision 17 | clnt = CvpClient() 18 | clnt.connect(['cvp1'],'username', 'password') 19 | 20 | 21 | cc_id = str(uuid.uuid4()) 22 | name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}" 23 | 24 | # Create custom stage hierarchy 25 | # The below example would result in the following hierarchy: 26 | # root (series) 27 | # |- stages 1-2 (series) 28 | # | |- stage 1ab (parallel) 29 | # | | |- stage 1a 30 | # | | |- stage 1b 31 | # | |- stage 2 32 | # |- stage 3 33 | data = {'key': { 34 | 'id': cc_id 35 | }, 36 | 'change': { 37 | 'name': cc_id, 38 | 'notes': 'cvprac CC', 39 | 'rootStageId': 'root', 40 | 'stages': {'values': {'root': {'name': 'root', 41 | 'rows': {'values': [{'values': ['1-2']}, 42 | {'values': ['3']}] 43 | } 44 | }, 45 | '1-2': {'name': 'stages 1-2', 46 | 'rows': {'values': [{'values': ['1ab']}, 47 | {'values': ['2']}]}}, 48 | '1ab': {'name': 'stage 1ab', 49 | 'rows': {'values': [{'values': ['1a','1b']}] 50 | } 51 | }, 52 | '1a': {'action': {'args': {'values': {'TaskID': '1242'}}, 53 | 'name': 'task', 54 | 'timeout': 3000}, 55 | 'name': 'stage 1a'}, 56 | '1b': {'action': {'args': {'values': {'TaskID': '1243'}}, 57 | 'name': 'task', 58 | 'timeout': 3000}, 59 | 'name': 'stage 1b'}, 60 | '2': {'action': {'args': {'values': {'TaskID': '1240'}}, 61 | 'name': 'task', 62 | 'timeout': 3000}, 63 | 'name': 'stage 2'}, 64 | '3': {'action': {'args': {'values': {'TaskID': '1241'}}, 65 | 'name': 'task', 66 | 'timeout': 3000}, 67 | 'name': 'stage 3'}, 68 | } 69 | } 70 | } 71 | } 72 | # Create change control from custom stage hierarchy data 73 | clnt.api.change_control_create_with_custom_stages(data) 74 | 75 | # Approve the change control 76 | approval_note = "Approve CC via cvprac" # notes are optional 77 | clnt.api.change_control_approve(cc_id, notes=approval_note) 78 | 79 | # Start the change control 80 | start_note = "Starting CC via cvprac" # notes are optional 81 | clnt.api.change_control_start(cc_id, notes=start_note) 82 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/configlets/AVD_spine1.cfg: -------------------------------------------------------------------------------- 1 | !RANCID-CONTENT-TYPE: arista 2 | ! 3 | vlan internal order ascending range 1006 1199 4 | ! 5 | transceiver qsfp default-mode 4x10G 6 | ! 7 | service routing protocols model multi-agent 8 | ! 9 | hostname spine1 10 | ip name-server vrf default 8.8.8.8 11 | ip name-server vrf default 192.168.2.1 12 | dns domain atd.lab 13 | ! 14 | spanning-tree mode none 15 | ! 16 | no enable password 17 | no aaa root 18 | ! 19 | interface Ethernet2 20 | description P2P_LINK_TO_LEAF1_Ethernet2 21 | no shutdown 22 | mtu 1500 23 | no switchport 24 | ip address 172.30.255.0/31 25 | ! 26 | interface Ethernet3 27 | description P2P_LINK_TO_LEAF2_Ethernet2 28 | no shutdown 29 | mtu 1500 30 | no switchport 31 | ip address 172.30.255.4/31 32 | ! 33 | interface Ethernet4 34 | description P2P_LINK_TO_LEAF3_Ethernet2 35 | no shutdown 36 | mtu 1500 37 | no switchport 38 | ip address 172.30.255.8/31 39 | ! 40 | interface Ethernet5 41 | description P2P_LINK_TO_LEAF4_Ethernet2 42 | no shutdown 43 | mtu 1500 44 | no switchport 45 | ip address 172.30.255.12/31 46 | ! 47 | interface Loopback0 48 | description EVPN_Overlay_Peering 49 | no shutdown 50 | ip address 192.0.255.1/32 51 | ! 52 | interface Management1 53 | description oob_management 54 | no shutdown 55 | ip address 192.168.0.10/24 56 | ! 57 | ip routing 58 | ! 59 | ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY 60 | seq 10 permit 192.0.255.0/24 eq 32 61 | ! 62 | ip route 0.0.0.0/0 192.168.0.1 63 | ! 64 | route-map RM-CONN-2-BGP permit 10 65 | match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY 66 | ! 67 | router bfd 68 | multihop interval 1200 min-rx 1200 multiplier 3 69 | ! 70 | router bgp 65001 71 | router-id 192.0.255.1 72 | no bgp default ipv4-unicast 73 | distance bgp 20 200 200 74 | graceful-restart restart-time 300 75 | graceful-restart 76 | maximum-paths 4 ecmp 4 77 | neighbor EVPN-OVERLAY-PEERS peer group 78 | neighbor EVPN-OVERLAY-PEERS next-hop-unchanged 79 | neighbor EVPN-OVERLAY-PEERS update-source Loopback0 80 | neighbor EVPN-OVERLAY-PEERS bfd 81 | neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3 82 | neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA== 83 | neighbor EVPN-OVERLAY-PEERS send-community 84 | neighbor EVPN-OVERLAY-PEERS maximum-routes 0 85 | neighbor IPv4-UNDERLAY-PEERS peer group 86 | neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w== 87 | neighbor IPv4-UNDERLAY-PEERS send-community 88 | neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000 89 | neighbor 172.30.255.1 peer group IPv4-UNDERLAY-PEERS 90 | neighbor 172.30.255.1 remote-as 65101 91 | neighbor 172.30.255.1 description leaf1_Ethernet2 92 | neighbor 172.30.255.5 peer group IPv4-UNDERLAY-PEERS 93 | neighbor 172.30.255.5 remote-as 65101 94 | neighbor 172.30.255.5 description leaf2_Ethernet2 95 | neighbor 172.30.255.9 peer group IPv4-UNDERLAY-PEERS 96 | neighbor 172.30.255.9 remote-as 65102 97 | neighbor 172.30.255.9 description leaf3_Ethernet2 98 | neighbor 172.30.255.13 peer group IPv4-UNDERLAY-PEERS 99 | neighbor 172.30.255.13 remote-as 65102 100 | neighbor 172.30.255.13 description leaf4_Ethernet2 101 | neighbor 192.0.255.3 peer group EVPN-OVERLAY-PEERS 102 | neighbor 192.0.255.3 remote-as 65101 103 | neighbor 192.0.255.3 description leaf1 104 | neighbor 192.0.255.4 peer group EVPN-OVERLAY-PEERS 105 | neighbor 192.0.255.4 remote-as 65101 106 | neighbor 192.0.255.4 description leaf2 107 | neighbor 192.0.255.5 peer group EVPN-OVERLAY-PEERS 108 | neighbor 192.0.255.5 remote-as 65102 109 | neighbor 192.0.255.5 description leaf3 110 | neighbor 192.0.255.6 peer group EVPN-OVERLAY-PEERS 111 | neighbor 192.0.255.6 remote-as 65102 112 | neighbor 192.0.255.6 description leaf4 113 | redistribute connected route-map RM-CONN-2-BGP 114 | ! 115 | address-family evpn 116 | neighbor EVPN-OVERLAY-PEERS activate 117 | ! 118 | address-family ipv4 119 | no neighbor EVPN-OVERLAY-PEERS activate 120 | neighbor IPv4-UNDERLAY-PEERS activate 121 | ! 122 | management api http-commands 123 | protocol https 124 | no shutdown 125 | ! 126 | vrf default 127 | no shutdown 128 | ! 129 | end 130 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/configlets/AVD_spine2.cfg: -------------------------------------------------------------------------------- 1 | !RANCID-CONTENT-TYPE: arista 2 | ! 3 | vlan internal order ascending range 1006 1199 4 | ! 5 | transceiver qsfp default-mode 4x10G 6 | ! 7 | service routing protocols model multi-agent 8 | ! 9 | hostname spine2 10 | ip name-server vrf default 8.8.8.8 11 | ip name-server vrf default 192.168.2.1 12 | dns domain atd.lab 13 | ! 14 | spanning-tree mode none 15 | ! 16 | no enable password 17 | no aaa root 18 | ! 19 | interface Ethernet2 20 | description P2P_LINK_TO_LEAF1_Ethernet3 21 | no shutdown 22 | mtu 1500 23 | no switchport 24 | ip address 172.30.255.2/31 25 | ! 26 | interface Ethernet3 27 | description P2P_LINK_TO_LEAF2_Ethernet3 28 | no shutdown 29 | mtu 1500 30 | no switchport 31 | ip address 172.30.255.6/31 32 | ! 33 | interface Ethernet4 34 | description P2P_LINK_TO_LEAF3_Ethernet3 35 | no shutdown 36 | mtu 1500 37 | no switchport 38 | ip address 172.30.255.10/31 39 | ! 40 | interface Ethernet5 41 | description P2P_LINK_TO_LEAF4_Ethernet3 42 | no shutdown 43 | mtu 1500 44 | no switchport 45 | ip address 172.30.255.14/31 46 | ! 47 | interface Loopback0 48 | description EVPN_Overlay_Peering 49 | no shutdown 50 | ip address 192.0.255.2/32 51 | ! 52 | interface Management1 53 | description oob_management 54 | no shutdown 55 | ip address 192.168.0.11/24 56 | ! 57 | ip routing 58 | ! 59 | ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY 60 | seq 10 permit 192.0.255.0/24 eq 32 61 | ! 62 | ip route 0.0.0.0/0 192.168.0.1 63 | ! 64 | route-map RM-CONN-2-BGP permit 10 65 | match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY 66 | ! 67 | router bfd 68 | multihop interval 1200 min-rx 1200 multiplier 3 69 | ! 70 | router bgp 65001 71 | router-id 192.0.255.2 72 | no bgp default ipv4-unicast 73 | distance bgp 20 200 200 74 | graceful-restart restart-time 300 75 | graceful-restart 76 | maximum-paths 4 ecmp 4 77 | neighbor EVPN-OVERLAY-PEERS peer group 78 | neighbor EVPN-OVERLAY-PEERS next-hop-unchanged 79 | neighbor EVPN-OVERLAY-PEERS update-source Loopback0 80 | neighbor EVPN-OVERLAY-PEERS bfd 81 | neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3 82 | neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA== 83 | neighbor EVPN-OVERLAY-PEERS send-community 84 | neighbor EVPN-OVERLAY-PEERS maximum-routes 0 85 | neighbor IPv4-UNDERLAY-PEERS peer group 86 | neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w== 87 | neighbor IPv4-UNDERLAY-PEERS send-community 88 | neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000 89 | neighbor 172.30.255.3 peer group IPv4-UNDERLAY-PEERS 90 | neighbor 172.30.255.3 remote-as 65101 91 | neighbor 172.30.255.3 description leaf1_Ethernet3 92 | neighbor 172.30.255.7 peer group IPv4-UNDERLAY-PEERS 93 | neighbor 172.30.255.7 remote-as 65101 94 | neighbor 172.30.255.7 description leaf2_Ethernet3 95 | neighbor 172.30.255.11 peer group IPv4-UNDERLAY-PEERS 96 | neighbor 172.30.255.11 remote-as 65102 97 | neighbor 172.30.255.11 description leaf3_Ethernet3 98 | neighbor 172.30.255.15 peer group IPv4-UNDERLAY-PEERS 99 | neighbor 172.30.255.15 remote-as 65102 100 | neighbor 172.30.255.15 description leaf4_Ethernet3 101 | neighbor 192.0.255.3 peer group EVPN-OVERLAY-PEERS 102 | neighbor 192.0.255.3 remote-as 65101 103 | neighbor 192.0.255.3 description leaf1 104 | neighbor 192.0.255.4 peer group EVPN-OVERLAY-PEERS 105 | neighbor 192.0.255.4 remote-as 65101 106 | neighbor 192.0.255.4 description leaf2 107 | neighbor 192.0.255.5 peer group EVPN-OVERLAY-PEERS 108 | neighbor 192.0.255.5 remote-as 65102 109 | neighbor 192.0.255.5 description leaf3 110 | neighbor 192.0.255.6 peer group EVPN-OVERLAY-PEERS 111 | neighbor 192.0.255.6 remote-as 65102 112 | neighbor 192.0.255.6 description leaf4 113 | redistribute connected route-map RM-CONN-2-BGP 114 | ! 115 | address-family evpn 116 | neighbor EVPN-OVERLAY-PEERS activate 117 | ! 118 | address-family ipv4 119 | no neighbor EVPN-OVERLAY-PEERS activate 120 | neighbor IPv4-UNDERLAY-PEERS activate 121 | ! 122 | management api http-commands 123 | protocol https 124 | no shutdown 125 | ! 126 | vrf default 127 | no shutdown 128 | ! 129 | end 130 | -------------------------------------------------------------------------------- /docs/labs/lab05-device-management/add_image_wo_tempaction.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | import json 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Create connection to CloudVision 13 | clnt = CvpClient() 14 | clnt.connect(['cvp1'],'username', 'password') 15 | 16 | image_name = "vEOS-4.26.0.1F" 17 | image = clnt.api.get_image_bundle_by_name(image_name) 18 | 19 | device_name = "tp-avd-leaf2" 20 | device = clnt.api.get_device_by_name(device_name) 21 | 22 | def apply_image_to_element_no_temp(image, element, name, id_type, create_task=True): 23 | ''' Apply an image bundle to a device or container 24 | A copy of the appl_image_to_element() function without creating a tempAction. 25 | Useful in situations where we need to call saveTopology on a per tempAction basis, 26 | which is only possible if the addTempAction function is not used and the data 27 | that we would've passed in the addTempAction call is passed in the 28 | saveTopology call. 29 | Args: 30 | image (dict): The image info. 31 | element (dict): Info about the element to apply an image to. Dict 32 | can contain device info or container info. 33 | name (str): Name of the element the image is being applied to. 34 | id_type (str): - Id type of the element the image is being applied to 35 | - can be 'netelement' or 'container' 36 | create_task (bool): Determines whether or not to execute a save 37 | and create the tasks (if any) 38 | Returns: 39 | response (list): A list that contains the tempAction data 40 | Ex: [{'NetworkRollbackTask': False, 41 | 'taskJson': '[{ 42 | "info": "Apply image: vEOS-4.26.0.1F to netelement tp-avd-leaf2", 43 | "infoPreview": "Apply image: vEOS-4.26.0.1F to netelement tp-avd-leaf2", 44 | "note": "", 45 | "action": "associate", "nodeType": 46 | "imagebundle", 47 | "nodeId": "imagebundle_1622072231719691917", 48 | "toId": "50:08:00:b1:5b:0b", 49 | "toIdType": "netelement", 50 | "fromId": "", 51 | "nodeName": "vEOS-4.26.0.1F", 52 | "fromName": "", " 53 | toName": "tp-avd-leaf2", 54 | "childTasks": [], 55 | "parentTask": ""}]'}] 56 | ''' 57 | 58 | print('Attempt to apply %s to %s %s' % (image['name'], 59 | id_type, name)) 60 | info = 'Apply image: %s to %s %s' % (image['name'], id_type, name) 61 | node_id = '' 62 | if 'imageBundleKeys' in image: 63 | if image['imageBundleKeys']: 64 | node_id = image['imageBundleKeys'][0] 65 | print('Provided image is an image object.' 66 | ' Using first value from imageBundleKeys - %s' 67 | % node_id) 68 | if 'id' in image: 69 | node_id = image['id'] 70 | print('Provided image is an image bundle object.' 71 | ' Found v1 API id field - %s' % node_id) 72 | elif 'key' in image: 73 | node_id = image['key'] 74 | print('Provided image is an image bundle object.' 75 | ' Found v2 API key field - %s' % node_id) 76 | data = [ 77 | { 78 | "NetworkRollbackTask": False, 79 | "taskJson": json.dumps([{'info': info, 80 | 'infoPreview': info, 81 | 'note': '', 82 | 'action': 'associate', 83 | 'nodeType': 'imagebundle', 84 | 'nodeId': node_id, 85 | 'toId': element['key'], 86 | 'toIdType': id_type, 87 | 'fromId': '', 88 | 'nodeName': image['name'], 89 | 'fromName': '', 90 | 'toName': name, 91 | 'childTasks': [], 92 | 'parentTask': ''}]) 93 | } 94 | ] 95 | return data 96 | 97 | create_task = False 98 | tempAction = apply_image_to_element_no_temp(image, device, device['fqdn'], 'netelement', create_task) 99 | 100 | clnt.api._save_topology_v2(tempAction) -------------------------------------------------------------------------------- /docs/labs/lab08-resource-apis/topology_tag_assignment.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | # In this example we are going to assign topology tags using the tags.v2 workspace aware API 6 | # More details on tag.v2 can be found at https://aristanetworks.github.io/cloudvision-apis/models/tag.v2/ 7 | # NOTE: Tag.v2 can be used for assigning both device and interface tags (studios, topology, etc) and it's not 8 | # limited to topology tags only. 9 | # The following are some of the built-in tags that can be used to modify the Topology rendering: 10 | # topology_hint_type: < core | edge | endpoint | management | leaf | spine > 11 | # topology_hint_rack: < rack name as string > 12 | # topology_hint_pod: < pod name as string > 13 | # topology_hint_datacenter: < datacenter name as string > 14 | # topology_hint_building: < building name as string > 15 | # topology_hint_floor: < floor name as string > 16 | # topology_network_type: < datacenter | campus | cloud > 17 | 18 | from cvprac.cvp_client import CvpClient 19 | import uuid 20 | from datetime import datetime 21 | 22 | # Reading the service account token from a file 23 | with open("token.tok") as f: 24 | token = f.read().strip('\n') 25 | 26 | # Create connection to CloudVision 27 | clnt = CvpClient() 28 | clnt.connect(nodes=['cvp1'], username='',password='',api_token=token) 29 | 30 | tags_common = [{"key": "topology_hint_pod", "value": "tp-avd-pod1"}, 31 | {"key": "topology_hint_datacenter", "value": "tp-avd-dc1"}] 32 | tags_leaf1 = [{"key": "topology_hint_rack", "value": "tp-avd-leafs1"}, 33 | {"key": "topology_hint_type", "value": "leaf"}] 34 | tags_leaf2 = [{"key": "topology_hint_rack", "value": "tp-avd-leafs2"}, 35 | {"key": "topology_hint_type", "value": "leaf"}] 36 | tags_spines = [{"key": "topology_hint_rack", "value": "tp-avd-spines"}, 37 | {"key": "topology_hint_type", "value": "spine"}] 38 | 39 | # Create workspace 40 | display_name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}" 41 | workspace_id = str(uuid.uuid4()) 42 | clnt.api.workspace_config(workspace_id,display_name) 43 | 44 | ### Create tags 45 | element_type = "ELEMENT_TYPE_DEVICE" 46 | 47 | for tag in tags_common+tags_leaf1+tags_leaf2+tags_spines: 48 | tag_label = tag['key'] 49 | tag_value = tag['value'] 50 | clnt.api.tag_config(element_type, workspace_id, tag_label, tag_value) 51 | 52 | ### Assign tags 53 | devices = {"leafs1":["BAD032986065E8DC14CBB6472EC314A6","0123F2E4462997EB155B7C50EC148767"], 54 | "leafs2":["8520AF39790A4EC959550166DC5DEADE", "6323DA7D2B542B5D09630F87351BEA41"], 55 | "spines":["CD0EADBEEA126915EA78E0FB4DC776CA", "2568DB4A33177968A78C4FD5A8232159"]} 56 | 57 | for tag in tags_common+tags_leaf1: 58 | tag_label = tag['key'] 59 | tag_value = tag['value'] 60 | interface_id = '' 61 | for leaf in devices['leafs1']: 62 | device_id = leaf 63 | clnt.api.tag_assignment_config(element_type, workspace_id, tag_label, tag_value, device_id, interface_id) 64 | for tag in tags_common+tags_leaf2: 65 | tag_label = tag['key'] 66 | tag_value = tag['value'] 67 | interface_id = '' 68 | for leaf in devices['leafs2']: 69 | device_id = leaf 70 | clnt.api.tag_assignment_config(element_type, workspace_id, tag_label, tag_value, device_id, interface_id) 71 | for tag in tags_common+tags_spines: 72 | tag_label = tag['key'] 73 | tag_value = tag['value'] 74 | interface_id = '' 75 | for spine in devices['spines']: 76 | device_id = spine 77 | clnt.api.tag_assignment_config(element_type, workspace_id, tag_label, tag_value, device_id, interface_id) 78 | 79 | ### Start build 80 | request = 'REQUEST_START_BUILD' 81 | request_id = 'b1' 82 | description='testing cvprac build' 83 | clnt.api.workspace_config(workspace_id=workspace_id, display_name=display_name, 84 | description=description, request=request, request_id=request_id) 85 | 86 | ### Check workspace build status and proceed only after it finishes building 87 | b = 0 88 | while b == 0: 89 | build_id = request_id 90 | # Requesting for the build status too fast might fail if the build start didn't finish creating 91 | # the build with the request_id/build_id 92 | while True: 93 | try: 94 | request = clnt.api.workspace_build_status(workspace_id, build_id) 95 | break 96 | except Exception as e: 97 | continue 98 | if request['value']['state'] == 'BUILD_STATE_SUCCESS': 99 | b = b+1 100 | else: 101 | continue 102 | 103 | ### Submit workspace 104 | request = 'REQUEST_SUBMIT' 105 | request_id = 's1' 106 | clnt.api.workspace_config(workspace_id=workspace_id,display_name=display_name,description=description,request=request,request_id=request_id) 107 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | /** 4 | * Jenkinsfile 5 | */ 6 | pipeline { 7 | agent{ label 'exec'} 8 | options { 9 | buildDiscarder( 10 | // Only keep the 10 most recent builds 11 | logRotator(numToKeepStr:'10')) 12 | } 13 | environment { 14 | projectName = 'CvpRac' 15 | emailTo = 'eosplus-dev@arista.com' 16 | emailFrom = 'eosplus-dev+jenkins@arista.com' 17 | } 18 | 19 | stages { 20 | 21 | stage ('Checkout') { 22 | steps { 23 | checkout scm 24 | } 25 | } 26 | 27 | stage ('Install_Requirements') { 28 | steps { 29 | sh """ 30 | [[ -d venv ]] && rm -rf venv 31 | virtualenv --python=python2.7 venv 32 | source venv/bin/activate 33 | pip install --upgrade pip 34 | pip install -r requirements.txt 35 | pip install -r dev-requirements.txt 36 | pip install codecov 37 | """ 38 | // Stub dummy .cloudvision.yaml file 39 | writeFile file: "test/fixtures/cvp_nodes.yaml", text: "---\n- node: 10.81.111.9\n username: cvpadmin\n password: cvp123\n" 40 | } 41 | } 42 | 43 | stage ('Check_style') { 44 | steps { 45 | sh """ 46 | source venv/bin/activate 47 | make clean 48 | [[ -d report ]] || mkdir report 49 | echo "exclude report" >> MANIFEST.in 50 | echo "exclude htmlcov" >> MANIFEST.in 51 | make check || true 52 | make pep8 | tee report/pep8_report.txt 53 | make pyflakes 54 | make pylint | tee report/pylint.out || true 55 | """ 56 | step([$class: 'WarningsPublisher', 57 | parserConfigurations: [[ 58 | parserName: 'Pep8', 59 | pattern: 'report/pep8_report.txt' 60 | ], 61 | [ 62 | parserName: 'pylint', 63 | pattern: 'report/pylint.out' 64 | ]], 65 | unstableTotalAll: '0', 66 | usePreviousBuildAsReference: true 67 | ]) 68 | } 69 | } 70 | 71 | stage ('System Test') { 72 | steps { 73 | sh """ 74 | source venv/bin/activate 75 | #make tests || true 76 | nosetests --with-xunit --all-modules --traverse-namespace --with-coverage --cover-package=cvprac --cover-inclusive --cover-html test/system/* || true 77 | """ 78 | } 79 | 80 | post { 81 | always { 82 | junit keepLongStdio: true, testResults: 'nosetests.xml' 83 | publishHTML target: [ 84 | reportDir: 'cover', 85 | reportFiles: 'index.html', 86 | reportName: 'Coverage Report' 87 | ] 88 | } 89 | } 90 | } 91 | 92 | stage ('Docs') { 93 | steps { 94 | sh """ 95 | source venv/bin/activate 96 | PYTHONPATH=. pdoc --html --html-dir docs --overwrite cvprac 97 | """ 98 | } 99 | 100 | post { 101 | always { 102 | publishHTML target: [ 103 | reportDir: 'docs/cvprac', 104 | reportFiles: 'index.html', 105 | reportName: 'Module Documentation' 106 | ] 107 | } 108 | } 109 | } 110 | 111 | stage ('Cleanup') { 112 | steps { 113 | sh 'rm -rf venv' 114 | } 115 | } 116 | } 117 | 118 | post { 119 | failure { 120 | mail body: "${env.JOB_NAME} (${env.BUILD_NUMBER}) ${env.projectName} build error " + 121 | "is here: ${env.BUILD_URL}\nStarted by ${env.BUILD_CAUSE}" , 122 | from: env.emailFrom, 123 | //replyTo: env.emailFrom, 124 | subject: "${env.projectName} ${env.JOB_NAME} (${env.BUILD_NUMBER}) build failed", 125 | to: env.emailTo 126 | } 127 | success { 128 | mail body: "${env.JOB_NAME} (${env.BUILD_NUMBER}) ${env.projectName} build successful\n" + 129 | "Started by ${env.BUILD_CAUSE}", 130 | from: env.emailFrom, 131 | //replyTo: env.emailFrom, 132 | subject: "${env.projectName} ${env.JOB_NAME} (${env.BUILD_NUMBER}) build successful", 133 | to: env.emailTo 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/vc_task_retrigger.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | # Example on how to re-trigger task creation if a config push task was previously 6 | # cancelled and the device is still config out of sync 7 | import argparse 8 | import ssl 9 | import sys 10 | from packaging.version import parse 11 | from getpass import getpass 12 | from cvprac.cvp_client import CvpClient 13 | import requests.packages.urllib3 14 | requests.packages.urllib3.disable_warnings() 15 | 16 | 17 | if ((sys.version_info.major == 3) or 18 | (sys.version_info.major == 2 and sys.version_info.minor == 7 and 19 | sys.version_info.micro >= 5)): 20 | ssl._create_default_https_context = ssl._create_unverified_context 21 | 22 | 23 | def main(): 24 | 25 | compliance = {"0001": "Config is out of sync", 26 | "0003": "Config & image out of sync", 27 | "0004": "Config, Image and Device time are in sync", 28 | "0005": "Device is not reachable", 29 | "0008": "Config, Image and Extensions are out of sync", 30 | "0009": "Config and Extensions are out of sync", 31 | "0012": "Config, Image, Extension and Device time are out of sync", 32 | "0013": "Config, Image and Device time are out of sync", 33 | "0014": "Config, Extensions and Device time are out of sync", 34 | "0016": "Config and Device time are out of sync" 35 | } 36 | # Create connection to CloudVision 37 | clnt = CvpClient() 38 | 39 | parser = argparse.ArgumentParser( 40 | description='Script to recreate a task, if a previous config push was cancelled') 41 | parser.add_argument('-u', '--username', default='username') 42 | parser.add_argument('-p', '--password', default=None) 43 | parser.add_argument('-c', '--cvpserver', action='append') 44 | parser.add_argument('-f', '--filter', action='append', default=None) 45 | args = parser.parse_args() 46 | 47 | if args.password is None: 48 | args.password = getpass() 49 | 50 | for cvpserver in args.cvpserver: 51 | print("Connecting to %s" % cvpserver) 52 | try: 53 | clnt.connect(nodes=[cvpserver], username=args.username, password=args.password) 54 | except Exception as e: 55 | print("Unable to connect to CVP: %s" % str(e)) 56 | 57 | # Get the current CVP version 58 | cvp_release = clnt.api.get_cvp_info()['version'] 59 | if parse(cvp_release) < parse('2020.3.0'): 60 | # For older CVP, we manually trigger a compliance check 61 | try: 62 | clnt.api.check_compliance('root', 'container') 63 | except: 64 | # Bad practice, but the check compliance applied to a container can't actually work 65 | # since the complianceIndication key doesn't exist on the container level 66 | pass 67 | else: 68 | # with continuous compliance checks, triggering the check is no longer required 69 | pass 70 | 71 | device_filters = [] 72 | if args.filter is not None: 73 | for entry in args.filter: 74 | device_filters.extend(entry.split(',')) 75 | 76 | # Get inventory 77 | print("Collecting inventory...") 78 | devices = clnt.api.get_inventory() 79 | print("%d devices in inventory" % len(devices) ) 80 | 81 | for switch in devices: 82 | if (switch['status'] == 'Registered' and 83 | switch['parentContainerId'] != 'undefined_container'): 84 | 85 | if len(device_filters) > 0: 86 | # iterate over device filters, and update task for 87 | # any devices not in compliance 88 | 89 | for filter_term in device_filters: 90 | print("Checking device: %s" % switch['hostname']) 91 | if filter_term in switch['hostname']: 92 | # generate configlet list 93 | cl = clnt.api.get_configlets_by_device_id(switch['systemMacAddress']) 94 | # generate a task if config is out of sync 95 | if switch['complianceCode'] in compliance.keys(): 96 | print(clnt.api.apply_configlets_to_device("", switch, cl)) 97 | else: 98 | print("%s is compliant, nothing to do" % switch['hostname']) 99 | else: 100 | print("Skipping %s due to filter" % switch['hostname']) 101 | else: 102 | print("Checking device: %s" % switch['hostname']) 103 | cl = clnt.api.get_configlets_by_device_id(switch['systemMacAddress']) 104 | # generate a task if config is out of sync 105 | if switch['complianceCode'] in compliance.keys(): 106 | print(clnt.api.apply_configlets_to_device("", switch, cl)) 107 | 108 | else: 109 | print("Skipping %s, device is unregistered for provisioning" % switch['hostname']) 110 | 111 | return 0 112 | 113 | 114 | if __name__ == "__main__": 115 | main() 116 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016, Arista Networks, Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # Neither the name of Arista Networks nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS 24 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 27 | # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 30 | # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | """ This module provides a RESTful API client for Cloudvision(R) Portal (CVP) 32 | which can be used for building applications that work with Arista CVP. 33 | """ 34 | import io 35 | from os import path, walk 36 | 37 | try: 38 | from setuptools import setup 39 | except ImportError: 40 | from distutils.core import setup 41 | 42 | from cvprac import __version__, __author__ 43 | 44 | 45 | def find_modules(pkg): 46 | ''' Return all modules from the pkg 47 | ''' 48 | modules = [pkg] 49 | for dirname, dirnames, _ in walk(pkg): 50 | for subdirname in dirnames: 51 | modules.append(path.join(dirname, subdirname)) 52 | return modules 53 | 54 | 55 | def get_long_description(): 56 | ''' Get the long description from README.rst if it exists. 57 | Null string is returned if README.rst is non-existent 58 | ''' 59 | long_description = '' 60 | here = path.abspath(path.dirname(__file__)) 61 | try: 62 | with io.open(path.join(here, 'README.md'), encoding='utf-8') as file_hdl: 63 | long_description = file_hdl.read() 64 | except IOError: 65 | pass 66 | return long_description 67 | 68 | 69 | setup( 70 | name='cvprac', 71 | version=__version__, 72 | description='Arista Cloudvision(R) Portal Rest API Client written in python', 73 | long_description=get_long_description(), 74 | long_description_content_type='text/markdown', 75 | author=__author__, 76 | author_email='eosplus-dev@arista.com', 77 | url='https://github.com/aristanetworks/cvprac', 78 | download_url='https://github.com/aristanetworks/cvprac/tarball/%s' % __version__, 79 | license='BSD-3', 80 | packages=find_modules('cvprac'), 81 | 82 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 83 | classifiers=[ 84 | # How mature is this project? Common values are 85 | # 3 - Alpha 86 | # 4 - Beta 87 | # 5 - Production/Stable 88 | 'Development Status :: 4 - Beta', 89 | 90 | # Indicate who your project is intended for 91 | 'Intended Audience :: Developers', 92 | 'Intended Audience :: System Administrators', 93 | 94 | 'Topic :: Software Development :: Libraries', 95 | 'Topic :: Software Development :: Libraries :: Python Modules', 96 | 'Topic :: System :: Networking', 97 | 98 | # Pick your license as you wish (should match "license" above) 99 | 'License :: OSI Approved :: BSD License', 100 | 101 | # Specify the Python versions you support here. In particular, ensure 102 | # that you indicate whether you support Python 2, Python 3 or both. 103 | 'Programming Language :: Python :: 3 :: Only', 104 | 'Programming Language :: Python :: 3.7', 105 | 'Programming Language :: Python :: 3.8', 106 | 'Programming Language :: Python :: 3.9', 107 | 'Programming Language :: Python :: 3.10', 108 | 'Programming Language :: Python :: 3.11', 109 | 'Programming Language :: Python :: 3.12', 110 | ], 111 | 112 | # What does your project relate to? 113 | keywords='networking CloudVision development rest api', 114 | 115 | # List run-time dependencies here. These will be installed by pip when 116 | # your project is installed. For an analysis of "install_requires" vs pip's 117 | # requirements files see: 118 | # https://packaging.python.org/en/latest/requirements.html 119 | install_requires=['requests[socks]>=2.27.0', 'packaging>=23.2'], 120 | 121 | # List additional groups of dependencies here (e.g. development 122 | # dependencies). You can install these using the following syntax, 123 | # for example: 124 | # $ pip install -e .[dev] 125 | extras_require={ 126 | 'dev': ['check-manifest', 'pep8', 'pyflakes', 'pylint', 'coverage', 127 | 'pyyaml'], 128 | }, 129 | ) 130 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/atd_e2e_provisioning_workflow.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | # This script is an example on provisioning registered devices in CloudVision that is based on 6 | # Arista Test Drive (ATD) and similar to what the ansible playbooks do in 7 | # https://github.com/arista-netdevops-community/atd-avd. 8 | # It does the following: 9 | # - creates and uploads configlets, 10 | # - creates the container hierarchy in Network Provisiong 11 | # - moves the devices to their target containers 12 | # - assigns the configlets to the devices 13 | # - creates a change control from the genereated tasks 14 | # - approves and executes the change control 15 | 16 | import uuid 17 | import time 18 | import ssl 19 | from datetime import datetime 20 | from cvprac.cvp_client import CvpClient 21 | ssl._create_default_https_context = ssl._create_unverified_context 22 | 23 | # Create connection to CloudVision 24 | clnt = CvpClient() 25 | clnt.connect(['cvp1'],'username', 'password') 26 | 27 | # Create container topology 28 | container_name = "DC1_LEAFS" 29 | container_topology = [{"containerName": "ATD_FABRIC", "parentContainerName": 'Tenant'}, 30 | {"containerName": "ATD_LEAFS", "parentContainerName": 'ATD_FABRIC'}, 31 | {"containerName": "pod1", "parentContainerName": 'ATD_LEAFS'}, 32 | {"containerName": "pod2", "parentContainerName": 'ATD_LEAFS'}, 33 | {"containerName": "ATD_SERVERS", "parentContainerName": 'ATD_FABRIC'}, 34 | {"containerName": "ATD_SPINES", "parentContainerName": 'ATD_FABRIC'}, 35 | {"containerName": "ATD_TENANT_NETWORKS", "parentContainerName": 'ATD_FABRIC'}] 36 | for container in container_topology: 37 | try: 38 | container_name = container['containerName'] 39 | # Get parent container information 40 | parent = clnt.api.get_container_by_name(container['parentContainerName']) 41 | print(f'Creating container {container_name}\n') 42 | clnt.api.add_container(container_name,parent["name"],parent["key"]) 43 | except Exception as e: 44 | if "Data already exists in Database" in str(e): 45 | print ("Container already exists, continuing...") 46 | 47 | # Create device mappers 48 | devices = [{'deviceName': "leaf1", 49 | 'configlets': ["BaseIPv4_Leaf1", "AVD_leaf1"], 50 | "parentContainerName": "pod1"}, 51 | {'deviceName': "leaf2", 52 | 'configlets': ["BaseIPv4_Leaf2", "AVD_leaf2"], 53 | "parentContainerName": "pod1"}, 54 | {'deviceName': "leaf3", 55 | 'configlets': ["BaseIPv4_Leaf3", "AVD_leaf3"], 56 | "parentContainerName": "pod2"}, 57 | {'deviceName': "leaf4", 58 | 'configlets': ["BaseIPv4_Leaf4", "AVD_leaf4"], 59 | "parentContainerName": "pod2"}, 60 | {'deviceName': "spine1", 61 | 'configlets': ["BaseIPv4_Spine1", "AVD_spine1"], 62 | "parentContainerName": "ATD_SPINES"}, 63 | {'deviceName': "spine2", 64 | 'configlets': ["BaseIPv4_Spine2", "AVD_spine2"], 65 | "parentContainerName": "ATD_SPINES"}] 66 | 67 | task_list = [] 68 | for device in devices: 69 | # Load the AVD configlets from file 70 | with open("./configlets/AVD_" + device['deviceName'] + ".cfg", "r") as file: 71 | configlet_file = file.read() 72 | avd_configlet_name = device['configlets'][1] 73 | base_configlet_name = device['configlets'][0] # preloaded configlet in an ATD environment 74 | container_name = device['parentContainerName'] 75 | base_configlet = clnt.api.get_configlet_by_name(base_configlet_name) 76 | configlets = [base_configlet] 77 | # Update the AVD configlets if they exist, otherwise upload them from the configlets folder 78 | print (f"Creating configlet {avd_configlet_name} for {device['deviceName']}\n") 79 | try: 80 | configlet = clnt.api.get_configlet_by_name(avd_configlet_name) 81 | clnt.api.update_configlet(configlet_file, configlet['key'], avd_configlet_name) 82 | configlets.append(configlet) 83 | except: 84 | clnt.api.add_configlet(avd_configlet_name, configlet_file) 85 | configlet = clnt.api.get_configlet_by_name(avd_configlet_name) 86 | configlets.append(configlet) 87 | # Get device data 88 | device_data = clnt.api.get_device_by_name(device['deviceName'] + ".atd.lab") 89 | # Get the parent container data for the device 90 | container = clnt.api.get_container_by_name(container_name) 91 | device_name = device['deviceName'] 92 | print(f"Moving device {device_name} to container {container_name}\n") 93 | # The move action will create the task first, however if the devices are already in the target 94 | # container, for instance if the script was run multiple times than the move action will 95 | # not generate a task anymore, therefore it's better to create the task list from the 96 | # Update Config action which will reuse the Move Device action's task if one exists, 97 | # otherwise will create a new one. 98 | move = clnt.api.move_device_to_container("python", device_data, container) 99 | apply_configlets = clnt.api.apply_configlets_to_device("", device_data, configlets) 100 | task_list = task_list + apply_configlets['data']['taskIds'] 101 | 102 | print(f"Generated task IDs are: {task_list}\n") 103 | 104 | # Generate unique ID for the change control 105 | cc_id = str(uuid.uuid4()) 106 | cc_name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}" 107 | 108 | print("Creating Change control with the list of tasks") 109 | clnt.api.change_control_create_for_tasks(cc_id, cc_name, task_list, series=False) 110 | 111 | print("Approving Change Control") 112 | # adding a few seconds sleep to avoid small time diff between the local system and CVP 113 | time.sleep(2) 114 | approve_note = "Approving CC via cvprac" 115 | clnt.api.change_control_approve(cc_id, notes=approve_note) 116 | 117 | # Start the change control 118 | print("Executing Change Control...") 119 | start_note = "Start the CC via cvprac" 120 | clnt.api.change_control_start(cc_id, notes=start_note) 121 | -------------------------------------------------------------------------------- /docs/labs/lab08-resource-apis/resource_cvprac.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Arista Networks, Inc. 2 | # Use of this source code is governed by the Apache License 2.0 3 | # that can be found in the COPYING file. 4 | 5 | from cvprac.cvp_client import CvpClient 6 | from pprint import pprint as pp 7 | import ssl 8 | ssl._create_default_https_context = ssl._create_unverified_context 9 | import requests.packages.urllib3 10 | requests.packages.urllib3.disable_warnings() 11 | 12 | # Reading the service account token from a file 13 | with open("token.tok") as f: 14 | token = f.read().strip('\n') 15 | 16 | clnt = CvpClient() 17 | clnt.connect(nodes=['cvp1'], username='',password='',api_token=token) 18 | 19 | def get_events_all(client): 20 | ''' Get All events ''' 21 | event_url = '/api/resources/event/v1/Event/all' 22 | response = client.get(event_url) 23 | return response['data'] 24 | 25 | def get_event(client, key, ts): 26 | event_url = '/api/resources/event/v1/Event?' 27 | url = event_url + 'key.key=' + key + "&key.timestamp=" + ts 28 | response = client.get(url) 29 | return response 30 | 31 | def get_events_t1_t2(client, t1, t2): 32 | event_url = '/api/resources/event/v1/Event/all?' 33 | url = event_url + 'time.start=' + t1 + "&time.end=" + t2 34 | response = client.get(url) 35 | return response['data'] 36 | 37 | def get_events_by_severity(client, severity): 38 | payload = {"partialEqFilter": [{"severity": severity }]} 39 | event_url = '/api/resources/event/v1/Event/all' 40 | response = client.post(event_url, data=payload) 41 | if 'data' in response.keys(): 42 | return response['data'] 43 | else: 44 | return response 45 | 46 | def get_events_by_type(client, etype): 47 | payload = {"partialEqFilter": [{"eventType": etype }]} 48 | event_url = '/api/resources/event/v1/Event/all' 49 | response = client.post(event_url, data=payload) 50 | if 'data' in response.keys(): 51 | return response['data'] 52 | else: 53 | return response 54 | 55 | def get_active_devices(client): 56 | ''' Get active devices ''' 57 | dev_url = '/api/resources/inventory/v1/Device/all' 58 | devices_data = client.get(dev_url) 59 | devices = [] 60 | for device in devices_data['data']: 61 | try: 62 | if device['result']['value']['streamingStatus'] == "STREAMING_STATUS_ACTIVE": 63 | devices.append(device['result']['value']['hostname']) 64 | # pass on archived datasets 65 | except KeyError as e: 66 | continue 67 | return devices 68 | 69 | def get_all_device_tags(client): 70 | tag_url = '/api/resources/tag/v1/DeviceTag/all' 71 | tag_data = client.get(tag_url) 72 | tags = [] 73 | for tag in tag_data['data']: 74 | tags.append({tag['result']['value']['key']['label']:tag['result']['value']['key']['value']}) 75 | return tags 76 | 77 | def get_all_interface_tags(client): 78 | tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig/all' 79 | tags = client.get(tag_url) 80 | return tags['data'] 81 | 82 | def filter_interface_tag(client, dId=None, ifId=None, label=None, value=None): 83 | tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig/all' 84 | payload = { 85 | "partialEqFilter": [ 86 | {"key": {"deviceId": dId, "interfaceId": ifId, "label": label, "value": value}} 87 | ] 88 | } 89 | response = client.post(tag_url, data=payload) 90 | return response 91 | 92 | def create_itag(client, label, value): 93 | tag_url = '/api/resources/tag/v1/InterfaceTagConfig' 94 | payload = {"key":{"label":label,"value":value}} 95 | response = client.post(tag_url, data=payload) 96 | return response 97 | 98 | def assign_itag(client, dId, ifId, label, value): 99 | tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig' 100 | payload = {"key":{"label":label, "value":value, "deviceId": dId, "interfaceId": ifId}} 101 | response = client.post(tag_url, data=payload) 102 | return response 103 | 104 | def create_dtag(client, label, value): 105 | tag_url = '/api/resources/tag/v1/DeviceTagConfig' 106 | payload = {"key":{"label":label,"value":value}} 107 | response = client.post(tag_url, data=payload) 108 | return response 109 | 110 | def assign_dtag(client, dId, label, value): 111 | tag_url = '/api/resources/tag/v1/DeviceTagAssignmentConfig' 112 | payload = {"key":{"label":label, "value":value, "deviceId": dId}} 113 | response = client.post(tag_url, data=payload) 114 | return response 115 | 116 | ### Uncomment the below functions/print statement to test 117 | 118 | # ### Get all active events 119 | # print ('=== All active events ===') 120 | # cvpevents = get_events_all(clnt) 121 | # for event in cvpevents: 122 | # print(event) 123 | 124 | # ### Get a specific event 125 | # key = "6098ae39e4c8a9d7" 126 | # ts ="2021-04-06T21:53:00Z" 127 | # get_event(clnt, key, ts) 128 | 129 | # ### Get events between two dates 130 | # t1 = "2021-04-06T09:00:00Z" 131 | # t2 = "2021-04-06T14:00:00Z" 132 | # events = get_events_t1_t2(clnt, t1, t2) 133 | # print(f"=== Events between {t1} and {t2} ===") 134 | # pp(events) 135 | 136 | # ### Get all INFO severity events ### 137 | # # EVENT_SEVERITY_UNSPECIFIED = 0 138 | # # EVENT_SEVERITY_INFO = 1 139 | # # EVENT_SEVERITY_WARNING = 2 140 | # # EVENT_SEVERITY_ERROR = 3 141 | # # EVENT_SEVERITY_CRITICAL = 4 142 | # #################################### 143 | 144 | # severity = 1 ## Severity INFO 145 | # info = get_events_by_severity(clnt, severity) 146 | # print('=== Get all INFO severity events ===') 147 | # pp(info) 148 | 149 | # ### Get specific event types 150 | 151 | # etype = "LOW_DEVICE_DISK_SPACE" 152 | # event = get_events_by_type(clnt, etype) 153 | # print('=== Get all Low Disk Space events ===') 154 | # pp(event) 155 | 156 | # ### Get the inventory 157 | # print ('=== Inventory ===') 158 | # print(get_active_devices(clnt)) 159 | 160 | # ### Get all devie tags 161 | # print('=== Device Tags ===' ) 162 | # for tag in get_all_device_tags(clnt): 163 | # print (tag) 164 | 165 | # ### Get all interface tag assignments 166 | # print(get_all_interface_tags(clnt)) 167 | 168 | # ### Get all interfaces that have a tag with a specific value on a device 169 | # print(filter_interface_tag(clnt, dId="JPE14070534", value="speed40Gbps")) 170 | 171 | # ### Get all tags for an interface of a device 172 | # print(filter_interface_tag(clnt, dId="JPE14070534", ifId="Ethernet1")) 173 | 174 | # ### Get all interfaces that have a specific tag assigned 175 | # print(filter_interface_tag(clnt, dId="JPE14070534", label="lldp_hostname")) 176 | 177 | # ### Create an interface tag 178 | # create_itag(clnt, "lldp_chassis", "50:08:00:0d:00:48") 179 | 180 | # ### Assign an interface tag 181 | # assign_itag(clnt, "JPE14070534", "Ethernet4", "lldp_chassis", "50:08:00:0d:00:38") 182 | 183 | # ### Create a device tag 184 | # create_dtag(clnt, "topology_hint_pod", "ire-pod11") 185 | 186 | # ### Assign an interface tag 187 | # assign_dtag(clnt, "JPE14070534", "topology_hint_pod", "ire-pod11" ) 188 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/configlets/AVD_leaf1.cfg: -------------------------------------------------------------------------------- 1 | !RANCID-CONTENT-TYPE: arista 2 | ! 3 | vlan internal order ascending range 1006 1199 4 | ! 5 | transceiver qsfp default-mode 4x10G 6 | ! 7 | service routing protocols model multi-agent 8 | ! 9 | hostname leaf1 10 | ip name-server vrf default 8.8.8.8 11 | ip name-server vrf default 192.168.2.1 12 | dns domain atd.lab 13 | ! 14 | spanning-tree mode mstp 15 | no spanning-tree vlan-id 4093-4094 16 | spanning-tree mst 0 priority 16384 17 | ! 18 | no enable password 19 | no aaa root 20 | ! 21 | vlan 110 22 | name Tenant_A_OP_Zone_1 23 | ! 24 | vlan 160 25 | name Tenant_A_VMOTION 26 | ! 27 | vlan 3009 28 | name MLAG_iBGP_Tenant_A_OP_Zone 29 | trunk group LEAF_PEER_L3 30 | ! 31 | vlan 4093 32 | name LEAF_PEER_L3 33 | trunk group LEAF_PEER_L3 34 | ! 35 | vlan 4094 36 | name MLAG_PEER 37 | trunk group MLAG 38 | ! 39 | vrf instance Tenant_A_OP_Zone 40 | ! 41 | interface Port-Channel1 42 | description MLAG_PEER_leaf2_Po1 43 | no shutdown 44 | switchport 45 | switchport trunk allowed vlan 2-4094 46 | switchport mode trunk 47 | switchport trunk group LEAF_PEER_L3 48 | switchport trunk group MLAG 49 | ! 50 | interface Port-Channel4 51 | description host1_PortChannel 52 | no shutdown 53 | switchport 54 | switchport access vlan 110 55 | mlag 4 56 | ! 57 | interface Ethernet1 58 | description MLAG_PEER_leaf2_Ethernet1 59 | no shutdown 60 | channel-group 1 mode active 61 | ! 62 | interface Ethernet2 63 | description P2P_LINK_TO_SPINE1_Ethernet2 64 | no shutdown 65 | mtu 1500 66 | no switchport 67 | ip address 172.30.255.1/31 68 | ! 69 | interface Ethernet3 70 | description P2P_LINK_TO_SPINE2_Ethernet2 71 | no shutdown 72 | mtu 1500 73 | no switchport 74 | ip address 172.30.255.3/31 75 | ! 76 | interface Ethernet4 77 | description host1_Eth1 78 | no shutdown 79 | channel-group 4 mode active 80 | ! 81 | interface Ethernet5 82 | description host1_Eth2 83 | no shutdown 84 | channel-group 4 mode active 85 | ! 86 | interface Ethernet6 87 | description MLAG_PEER_leaf2_Ethernet6 88 | no shutdown 89 | channel-group 1 mode active 90 | ! 91 | interface Loopback0 92 | description EVPN_Overlay_Peering 93 | no shutdown 94 | ip address 192.0.255.3/32 95 | ! 96 | interface Loopback1 97 | description VTEP_VXLAN_Tunnel_Source 98 | no shutdown 99 | ip address 192.0.254.3/32 100 | ! 101 | interface Loopback100 102 | description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS 103 | no shutdown 104 | vrf Tenant_A_OP_Zone 105 | ip address 10.255.1.3/32 106 | ! 107 | interface Management1 108 | description oob_management 109 | no shutdown 110 | ip address 192.168.0.12/24 111 | ! 112 | interface Vlan110 113 | description Tenant_A_OP_Zone_1 114 | no shutdown 115 | vrf Tenant_A_OP_Zone 116 | ip address virtual 10.1.10.1/24 117 | ! 118 | interface Vlan3009 119 | description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone 120 | no shutdown 121 | mtu 1500 122 | vrf Tenant_A_OP_Zone 123 | ip address 10.255.251.0/31 124 | ! 125 | interface Vlan4093 126 | description MLAG_PEER_L3_PEERING 127 | no shutdown 128 | mtu 1500 129 | ip address 10.255.251.0/31 130 | ! 131 | interface Vlan4094 132 | description MLAG_PEER 133 | no shutdown 134 | mtu 1500 135 | no autostate 136 | ip address 10.255.252.0/31 137 | ! 138 | interface Vxlan1 139 | description leaf1_VTEP 140 | vxlan source-interface Loopback1 141 | vxlan virtual-router encapsulation mac-address mlag-system-id 142 | vxlan udp-port 4789 143 | vxlan vlan 110 vni 10110 144 | vxlan vlan 160 vni 55160 145 | vxlan vrf Tenant_A_OP_Zone vni 10 146 | ! 147 | ip virtual-router mac-address 00:1c:73:00:dc:01 148 | ! 149 | ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.3 150 | ! 151 | ip routing 152 | ip routing vrf Tenant_A_OP_Zone 153 | ! 154 | ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY 155 | seq 10 permit 192.0.255.0/24 eq 32 156 | seq 20 permit 192.0.254.0/24 eq 32 157 | ! 158 | mlag configuration 159 | domain-id pod1 160 | local-interface Vlan4094 161 | peer-address 10.255.252.1 162 | peer-link Port-Channel1 163 | reload-delay mlag 300 164 | reload-delay non-mlag 330 165 | ! 166 | ip route 0.0.0.0/0 192.168.0.1 167 | ! 168 | route-map RM-CONN-2-BGP permit 10 169 | match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY 170 | ! 171 | route-map RM-MLAG-PEER-IN permit 10 172 | description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing 173 | set origin incomplete 174 | ! 175 | router bfd 176 | multihop interval 1200 min-rx 1200 multiplier 3 177 | ! 178 | router bgp 65101 179 | router-id 192.0.255.3 180 | no bgp default ipv4-unicast 181 | distance bgp 20 200 200 182 | graceful-restart restart-time 300 183 | graceful-restart 184 | maximum-paths 4 ecmp 4 185 | neighbor EVPN-OVERLAY-PEERS peer group 186 | neighbor EVPN-OVERLAY-PEERS update-source Loopback0 187 | neighbor EVPN-OVERLAY-PEERS bfd 188 | neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3 189 | neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA== 190 | neighbor EVPN-OVERLAY-PEERS send-community 191 | neighbor EVPN-OVERLAY-PEERS maximum-routes 0 192 | neighbor IPv4-UNDERLAY-PEERS peer group 193 | neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w== 194 | neighbor IPv4-UNDERLAY-PEERS send-community 195 | neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000 196 | neighbor MLAG-IPv4-UNDERLAY-PEER peer group 197 | neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65101 198 | neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self 199 | neighbor MLAG-IPv4-UNDERLAY-PEER description leaf2 200 | neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ== 201 | neighbor MLAG-IPv4-UNDERLAY-PEER send-community 202 | neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000 203 | neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in 204 | neighbor 10.255.251.1 peer group MLAG-IPv4-UNDERLAY-PEER 205 | neighbor 10.255.251.1 description leaf2 206 | neighbor 172.30.255.0 peer group IPv4-UNDERLAY-PEERS 207 | neighbor 172.30.255.0 remote-as 65001 208 | neighbor 172.30.255.0 description spine1_Ethernet2 209 | neighbor 172.30.255.2 peer group IPv4-UNDERLAY-PEERS 210 | neighbor 172.30.255.2 remote-as 65001 211 | neighbor 172.30.255.2 description spine2_Ethernet2 212 | neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS 213 | neighbor 192.0.255.1 remote-as 65001 214 | neighbor 192.0.255.1 description spine1 215 | neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS 216 | neighbor 192.0.255.2 remote-as 65001 217 | neighbor 192.0.255.2 description spine2 218 | redistribute connected route-map RM-CONN-2-BGP 219 | ! 220 | vlan-aware-bundle Tenant_A_OP_Zone 221 | rd 192.0.255.3:10 222 | route-target both 10:10 223 | redistribute learned 224 | vlan 110 225 | ! 226 | vlan-aware-bundle Tenant_A_VMOTION 227 | rd 192.0.255.3:55160 228 | route-target both 55160:55160 229 | redistribute learned 230 | vlan 160 231 | ! 232 | address-family evpn 233 | neighbor EVPN-OVERLAY-PEERS activate 234 | ! 235 | address-family ipv4 236 | no neighbor EVPN-OVERLAY-PEERS activate 237 | neighbor IPv4-UNDERLAY-PEERS activate 238 | neighbor MLAG-IPv4-UNDERLAY-PEER activate 239 | ! 240 | vrf Tenant_A_OP_Zone 241 | rd 192.0.255.3:10 242 | route-target import evpn 10:10 243 | route-target export evpn 10:10 244 | router-id 192.0.255.3 245 | neighbor 10.255.251.1 peer group MLAG-IPv4-UNDERLAY-PEER 246 | redistribute connected 247 | ! 248 | management api http-commands 249 | protocol https 250 | no shutdown 251 | ! 252 | vrf default 253 | no shutdown 254 | ! 255 | end 256 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/configlets/AVD_leaf2.cfg: -------------------------------------------------------------------------------- 1 | !RANCID-CONTENT-TYPE: arista 2 | ! 3 | vlan internal order ascending range 1006 1199 4 | ! 5 | transceiver qsfp default-mode 4x10G 6 | ! 7 | service routing protocols model multi-agent 8 | ! 9 | hostname leaf2 10 | ip name-server vrf default 8.8.8.8 11 | ip name-server vrf default 192.168.2.1 12 | dns domain atd.lab 13 | ! 14 | spanning-tree mode mstp 15 | no spanning-tree vlan-id 4093-4094 16 | spanning-tree mst 0 priority 16384 17 | ! 18 | no enable password 19 | no aaa root 20 | ! 21 | vlan 110 22 | name Tenant_A_OP_Zone_1 23 | ! 24 | vlan 160 25 | name Tenant_A_VMOTION 26 | ! 27 | vlan 3009 28 | name MLAG_iBGP_Tenant_A_OP_Zone 29 | trunk group LEAF_PEER_L3 30 | ! 31 | vlan 4093 32 | name LEAF_PEER_L3 33 | trunk group LEAF_PEER_L3 34 | ! 35 | vlan 4094 36 | name MLAG_PEER 37 | trunk group MLAG 38 | ! 39 | vrf instance Tenant_A_OP_Zone 40 | ! 41 | interface Port-Channel1 42 | description MLAG_PEER_leaf1_Po1 43 | no shutdown 44 | switchport 45 | switchport trunk allowed vlan 2-4094 46 | switchport mode trunk 47 | switchport trunk group LEAF_PEER_L3 48 | switchport trunk group MLAG 49 | ! 50 | interface Port-Channel4 51 | description host1_PortChannel 52 | no shutdown 53 | switchport 54 | switchport access vlan 110 55 | mlag 4 56 | ! 57 | interface Ethernet1 58 | description MLAG_PEER_leaf1_Ethernet1 59 | no shutdown 60 | channel-group 1 mode active 61 | ! 62 | interface Ethernet2 63 | description P2P_LINK_TO_SPINE1_Ethernet3 64 | no shutdown 65 | mtu 1500 66 | no switchport 67 | ip address 172.30.255.5/31 68 | ! 69 | interface Ethernet3 70 | description P2P_LINK_TO_SPINE2_Ethernet3 71 | no shutdown 72 | mtu 1500 73 | no switchport 74 | ip address 172.30.255.7/31 75 | ! 76 | interface Ethernet4 77 | description host1_Eth3 78 | no shutdown 79 | channel-group 4 mode active 80 | ! 81 | interface Ethernet5 82 | description host1_Eth4 83 | no shutdown 84 | channel-group 4 mode active 85 | ! 86 | interface Ethernet6 87 | description MLAG_PEER_leaf1_Ethernet6 88 | no shutdown 89 | channel-group 1 mode active 90 | ! 91 | interface Loopback0 92 | description EVPN_Overlay_Peering 93 | no shutdown 94 | ip address 192.0.255.4/32 95 | ! 96 | interface Loopback1 97 | description VTEP_VXLAN_Tunnel_Source 98 | no shutdown 99 | ip address 192.0.254.3/32 100 | ! 101 | interface Loopback100 102 | description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS 103 | no shutdown 104 | vrf Tenant_A_OP_Zone 105 | ip address 10.255.1.4/32 106 | ! 107 | interface Management1 108 | description oob_management 109 | no shutdown 110 | ip address 192.168.0.13/24 111 | ! 112 | interface Vlan110 113 | description Tenant_A_OP_Zone_1 114 | no shutdown 115 | vrf Tenant_A_OP_Zone 116 | ip address virtual 10.1.10.1/24 117 | ! 118 | interface Vlan3009 119 | description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone 120 | no shutdown 121 | mtu 1500 122 | vrf Tenant_A_OP_Zone 123 | ip address 10.255.251.1/31 124 | ! 125 | interface Vlan4093 126 | description MLAG_PEER_L3_PEERING 127 | no shutdown 128 | mtu 1500 129 | ip address 10.255.251.1/31 130 | ! 131 | interface Vlan4094 132 | description MLAG_PEER 133 | no shutdown 134 | mtu 1500 135 | no autostate 136 | ip address 10.255.252.1/31 137 | ! 138 | interface Vxlan1 139 | description leaf2_VTEP 140 | vxlan source-interface Loopback1 141 | vxlan virtual-router encapsulation mac-address mlag-system-id 142 | vxlan udp-port 4789 143 | vxlan vlan 110 vni 10110 144 | vxlan vlan 160 vni 55160 145 | vxlan vrf Tenant_A_OP_Zone vni 10 146 | ! 147 | ip virtual-router mac-address 00:1c:73:00:dc:01 148 | ! 149 | ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.4 150 | ! 151 | ip routing 152 | ip routing vrf Tenant_A_OP_Zone 153 | ! 154 | ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY 155 | seq 10 permit 192.0.255.0/24 eq 32 156 | seq 20 permit 192.0.254.0/24 eq 32 157 | ! 158 | mlag configuration 159 | domain-id pod1 160 | local-interface Vlan4094 161 | peer-address 10.255.252.0 162 | peer-link Port-Channel1 163 | reload-delay mlag 300 164 | reload-delay non-mlag 330 165 | ! 166 | ip route 0.0.0.0/0 192.168.0.1 167 | ! 168 | route-map RM-CONN-2-BGP permit 10 169 | match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY 170 | ! 171 | route-map RM-MLAG-PEER-IN permit 10 172 | description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing 173 | set origin incomplete 174 | ! 175 | router bfd 176 | multihop interval 1200 min-rx 1200 multiplier 3 177 | ! 178 | router bgp 65101 179 | router-id 192.0.255.4 180 | no bgp default ipv4-unicast 181 | distance bgp 20 200 200 182 | graceful-restart restart-time 300 183 | graceful-restart 184 | maximum-paths 4 ecmp 4 185 | neighbor EVPN-OVERLAY-PEERS peer group 186 | neighbor EVPN-OVERLAY-PEERS update-source Loopback0 187 | neighbor EVPN-OVERLAY-PEERS bfd 188 | neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3 189 | neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA== 190 | neighbor EVPN-OVERLAY-PEERS send-community 191 | neighbor EVPN-OVERLAY-PEERS maximum-routes 0 192 | neighbor IPv4-UNDERLAY-PEERS peer group 193 | neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w== 194 | neighbor IPv4-UNDERLAY-PEERS send-community 195 | neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000 196 | neighbor MLAG-IPv4-UNDERLAY-PEER peer group 197 | neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65101 198 | neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self 199 | neighbor MLAG-IPv4-UNDERLAY-PEER description leaf1 200 | neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ== 201 | neighbor MLAG-IPv4-UNDERLAY-PEER send-community 202 | neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000 203 | neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in 204 | neighbor 10.255.251.0 peer group MLAG-IPv4-UNDERLAY-PEER 205 | neighbor 10.255.251.0 description leaf1 206 | neighbor 172.30.255.4 peer group IPv4-UNDERLAY-PEERS 207 | neighbor 172.30.255.4 remote-as 65001 208 | neighbor 172.30.255.4 description spine1_Ethernet3 209 | neighbor 172.30.255.6 peer group IPv4-UNDERLAY-PEERS 210 | neighbor 172.30.255.6 remote-as 65001 211 | neighbor 172.30.255.6 description spine2_Ethernet3 212 | neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS 213 | neighbor 192.0.255.1 remote-as 65001 214 | neighbor 192.0.255.1 description spine1 215 | neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS 216 | neighbor 192.0.255.2 remote-as 65001 217 | neighbor 192.0.255.2 description spine2 218 | redistribute connected route-map RM-CONN-2-BGP 219 | ! 220 | vlan-aware-bundle Tenant_A_OP_Zone 221 | rd 192.0.255.4:10 222 | route-target both 10:10 223 | redistribute learned 224 | vlan 110 225 | ! 226 | vlan-aware-bundle Tenant_A_VMOTION 227 | rd 192.0.255.4:55160 228 | route-target both 55160:55160 229 | redistribute learned 230 | vlan 160 231 | ! 232 | address-family evpn 233 | neighbor EVPN-OVERLAY-PEERS activate 234 | ! 235 | address-family ipv4 236 | no neighbor EVPN-OVERLAY-PEERS activate 237 | neighbor IPv4-UNDERLAY-PEERS activate 238 | neighbor MLAG-IPv4-UNDERLAY-PEER activate 239 | ! 240 | vrf Tenant_A_OP_Zone 241 | rd 192.0.255.4:10 242 | route-target import evpn 10:10 243 | route-target export evpn 10:10 244 | router-id 192.0.255.4 245 | neighbor 10.255.251.0 peer group MLAG-IPv4-UNDERLAY-PEER 246 | redistribute connected 247 | ! 248 | management api http-commands 249 | protocol https 250 | no shutdown 251 | ! 252 | vrf default 253 | no shutdown 254 | ! 255 | end 256 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/configlets/AVD_leaf3.cfg: -------------------------------------------------------------------------------- 1 | !RANCID-CONTENT-TYPE: arista 2 | ! 3 | vlan internal order ascending range 1006 1199 4 | ! 5 | transceiver qsfp default-mode 4x10G 6 | ! 7 | service routing protocols model multi-agent 8 | ! 9 | hostname leaf3 10 | ip name-server vrf default 8.8.8.8 11 | ip name-server vrf default 192.168.2.1 12 | dns domain atd.lab 13 | ! 14 | spanning-tree mode mstp 15 | no spanning-tree vlan-id 4093-4094 16 | spanning-tree mst 0 priority 16384 17 | ! 18 | no enable password 19 | no aaa root 20 | ! 21 | vlan 110 22 | name Tenant_A_OP_Zone_1 23 | ! 24 | vlan 160 25 | name Tenant_A_VMOTION 26 | ! 27 | vlan 3009 28 | name MLAG_iBGP_Tenant_A_OP_Zone 29 | trunk group LEAF_PEER_L3 30 | ! 31 | vlan 4093 32 | name LEAF_PEER_L3 33 | trunk group LEAF_PEER_L3 34 | ! 35 | vlan 4094 36 | name MLAG_PEER 37 | trunk group MLAG 38 | ! 39 | vrf instance Tenant_A_OP_Zone 40 | ! 41 | interface Port-Channel1 42 | description MLAG_PEER_leaf4_Po1 43 | no shutdown 44 | switchport 45 | switchport trunk allowed vlan 2-4094 46 | switchport mode trunk 47 | switchport trunk group LEAF_PEER_L3 48 | switchport trunk group MLAG 49 | ! 50 | interface Port-Channel4 51 | description host2_PortChannel 52 | no shutdown 53 | switchport 54 | switchport access vlan 110 55 | mlag 4 56 | ! 57 | interface Ethernet1 58 | description MLAG_PEER_leaf4_Ethernet1 59 | no shutdown 60 | channel-group 1 mode active 61 | ! 62 | interface Ethernet2 63 | description P2P_LINK_TO_SPINE1_Ethernet4 64 | no shutdown 65 | mtu 1500 66 | no switchport 67 | ip address 172.30.255.9/31 68 | ! 69 | interface Ethernet3 70 | description P2P_LINK_TO_SPINE2_Ethernet4 71 | no shutdown 72 | mtu 1500 73 | no switchport 74 | ip address 172.30.255.11/31 75 | ! 76 | interface Ethernet4 77 | description host2_Eth1 78 | no shutdown 79 | channel-group 4 mode active 80 | ! 81 | interface Ethernet5 82 | description host2_Eth2 83 | no shutdown 84 | channel-group 4 mode active 85 | ! 86 | interface Ethernet6 87 | description MLAG_PEER_leaf4_Ethernet6 88 | no shutdown 89 | channel-group 1 mode active 90 | ! 91 | interface Loopback0 92 | description EVPN_Overlay_Peering 93 | no shutdown 94 | ip address 192.0.255.5/32 95 | ! 96 | interface Loopback1 97 | description VTEP_VXLAN_Tunnel_Source 98 | no shutdown 99 | ip address 192.0.254.5/32 100 | ! 101 | interface Loopback100 102 | description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS 103 | no shutdown 104 | vrf Tenant_A_OP_Zone 105 | ip address 10.255.1.5/32 106 | ! 107 | interface Management1 108 | description oob_management 109 | no shutdown 110 | ip address 192.168.0.14/24 111 | ! 112 | interface Vlan110 113 | description Tenant_A_OP_Zone_1 114 | no shutdown 115 | vrf Tenant_A_OP_Zone 116 | ip address virtual 10.1.10.1/24 117 | ! 118 | interface Vlan3009 119 | description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone 120 | no shutdown 121 | mtu 1500 122 | vrf Tenant_A_OP_Zone 123 | ip address 10.255.251.4/31 124 | ! 125 | interface Vlan4093 126 | description MLAG_PEER_L3_PEERING 127 | no shutdown 128 | mtu 1500 129 | ip address 10.255.251.4/31 130 | ! 131 | interface Vlan4094 132 | description MLAG_PEER 133 | no shutdown 134 | mtu 1500 135 | no autostate 136 | ip address 10.255.252.4/31 137 | ! 138 | interface Vxlan1 139 | description leaf3_VTEP 140 | vxlan source-interface Loopback1 141 | vxlan virtual-router encapsulation mac-address mlag-system-id 142 | vxlan udp-port 4789 143 | vxlan vlan 110 vni 10110 144 | vxlan vlan 160 vni 55160 145 | vxlan vrf Tenant_A_OP_Zone vni 10 146 | ! 147 | ip virtual-router mac-address 00:1c:73:00:dc:01 148 | ! 149 | ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.5 150 | ! 151 | ip routing 152 | ip routing vrf Tenant_A_OP_Zone 153 | ! 154 | ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY 155 | seq 10 permit 192.0.255.0/24 eq 32 156 | seq 20 permit 192.0.254.0/24 eq 32 157 | ! 158 | mlag configuration 159 | domain-id pod2 160 | local-interface Vlan4094 161 | peer-address 10.255.252.5 162 | peer-link Port-Channel1 163 | reload-delay mlag 300 164 | reload-delay non-mlag 330 165 | ! 166 | ip route 0.0.0.0/0 192.168.0.1 167 | ! 168 | route-map RM-CONN-2-BGP permit 10 169 | match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY 170 | ! 171 | route-map RM-MLAG-PEER-IN permit 10 172 | description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing 173 | set origin incomplete 174 | ! 175 | router bfd 176 | multihop interval 1200 min-rx 1200 multiplier 3 177 | ! 178 | router bgp 65102 179 | router-id 192.0.255.5 180 | no bgp default ipv4-unicast 181 | distance bgp 20 200 200 182 | graceful-restart restart-time 300 183 | graceful-restart 184 | maximum-paths 4 ecmp 4 185 | neighbor EVPN-OVERLAY-PEERS peer group 186 | neighbor EVPN-OVERLAY-PEERS update-source Loopback0 187 | neighbor EVPN-OVERLAY-PEERS bfd 188 | neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3 189 | neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA== 190 | neighbor EVPN-OVERLAY-PEERS send-community 191 | neighbor EVPN-OVERLAY-PEERS maximum-routes 0 192 | neighbor IPv4-UNDERLAY-PEERS peer group 193 | neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w== 194 | neighbor IPv4-UNDERLAY-PEERS send-community 195 | neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000 196 | neighbor MLAG-IPv4-UNDERLAY-PEER peer group 197 | neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65102 198 | neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self 199 | neighbor MLAG-IPv4-UNDERLAY-PEER description leaf4 200 | neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ== 201 | neighbor MLAG-IPv4-UNDERLAY-PEER send-community 202 | neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000 203 | neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in 204 | neighbor 10.255.251.5 peer group MLAG-IPv4-UNDERLAY-PEER 205 | neighbor 10.255.251.5 description leaf4 206 | neighbor 172.30.255.8 peer group IPv4-UNDERLAY-PEERS 207 | neighbor 172.30.255.8 remote-as 65001 208 | neighbor 172.30.255.8 description spine1_Ethernet4 209 | neighbor 172.30.255.10 peer group IPv4-UNDERLAY-PEERS 210 | neighbor 172.30.255.10 remote-as 65001 211 | neighbor 172.30.255.10 description spine2_Ethernet4 212 | neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS 213 | neighbor 192.0.255.1 remote-as 65001 214 | neighbor 192.0.255.1 description spine1 215 | neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS 216 | neighbor 192.0.255.2 remote-as 65001 217 | neighbor 192.0.255.2 description spine2 218 | redistribute connected route-map RM-CONN-2-BGP 219 | ! 220 | vlan-aware-bundle Tenant_A_OP_Zone 221 | rd 192.0.255.5:10 222 | route-target both 10:10 223 | redistribute learned 224 | vlan 110 225 | ! 226 | vlan-aware-bundle Tenant_A_VMOTION 227 | rd 192.0.255.5:55160 228 | route-target both 55160:55160 229 | redistribute learned 230 | vlan 160 231 | ! 232 | address-family evpn 233 | neighbor EVPN-OVERLAY-PEERS activate 234 | ! 235 | address-family ipv4 236 | no neighbor EVPN-OVERLAY-PEERS activate 237 | neighbor IPv4-UNDERLAY-PEERS activate 238 | neighbor MLAG-IPv4-UNDERLAY-PEER activate 239 | ! 240 | vrf Tenant_A_OP_Zone 241 | rd 192.0.255.5:10 242 | route-target import evpn 10:10 243 | route-target export evpn 10:10 244 | router-id 192.0.255.5 245 | neighbor 10.255.251.5 peer group MLAG-IPv4-UNDERLAY-PEER 246 | redistribute connected 247 | ! 248 | management api http-commands 249 | protocol https 250 | no shutdown 251 | ! 252 | vrf default 253 | no shutdown 254 | ! 255 | end 256 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/configlets/AVD_leaf4.cfg: -------------------------------------------------------------------------------- 1 | !RANCID-CONTENT-TYPE: arista 2 | ! 3 | vlan internal order ascending range 1006 1199 4 | ! 5 | transceiver qsfp default-mode 4x10G 6 | ! 7 | service routing protocols model multi-agent 8 | ! 9 | hostname leaf4 10 | ip name-server vrf default 8.8.8.8 11 | ip name-server vrf default 192.168.2.1 12 | dns domain atd.lab 13 | ! 14 | spanning-tree mode mstp 15 | no spanning-tree vlan-id 4093-4094 16 | spanning-tree mst 0 priority 16384 17 | ! 18 | no enable password 19 | no aaa root 20 | ! 21 | vlan 110 22 | name Tenant_A_OP_Zone_1 23 | ! 24 | vlan 160 25 | name Tenant_A_VMOTION 26 | ! 27 | vlan 3009 28 | name MLAG_iBGP_Tenant_A_OP_Zone 29 | trunk group LEAF_PEER_L3 30 | ! 31 | vlan 4093 32 | name LEAF_PEER_L3 33 | trunk group LEAF_PEER_L3 34 | ! 35 | vlan 4094 36 | name MLAG_PEER 37 | trunk group MLAG 38 | ! 39 | vrf instance Tenant_A_OP_Zone 40 | ! 41 | interface Port-Channel1 42 | description MLAG_PEER_leaf3_Po1 43 | no shutdown 44 | switchport 45 | switchport trunk allowed vlan 2-4094 46 | switchport mode trunk 47 | switchport trunk group LEAF_PEER_L3 48 | switchport trunk group MLAG 49 | ! 50 | interface Port-Channel4 51 | description host2_PortChannel 52 | no shutdown 53 | switchport 54 | switchport access vlan 110 55 | mlag 4 56 | ! 57 | interface Ethernet1 58 | description MLAG_PEER_leaf3_Ethernet1 59 | no shutdown 60 | channel-group 1 mode active 61 | ! 62 | interface Ethernet2 63 | description P2P_LINK_TO_SPINE1_Ethernet5 64 | no shutdown 65 | mtu 1500 66 | no switchport 67 | ip address 172.30.255.13/31 68 | ! 69 | interface Ethernet3 70 | description P2P_LINK_TO_SPINE2_Ethernet5 71 | no shutdown 72 | mtu 1500 73 | no switchport 74 | ip address 172.30.255.15/31 75 | ! 76 | interface Ethernet4 77 | description host2_Eth3 78 | no shutdown 79 | channel-group 4 mode active 80 | ! 81 | interface Ethernet5 82 | description host2_Eth4 83 | no shutdown 84 | channel-group 4 mode active 85 | ! 86 | interface Ethernet6 87 | description MLAG_PEER_leaf3_Ethernet6 88 | no shutdown 89 | channel-group 1 mode active 90 | ! 91 | interface Loopback0 92 | description EVPN_Overlay_Peering 93 | no shutdown 94 | ip address 192.0.255.6/32 95 | ! 96 | interface Loopback1 97 | description VTEP_VXLAN_Tunnel_Source 98 | no shutdown 99 | ip address 192.0.254.5/32 100 | ! 101 | interface Loopback100 102 | description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS 103 | no shutdown 104 | vrf Tenant_A_OP_Zone 105 | ip address 10.255.1.6/32 106 | ! 107 | interface Management1 108 | description oob_management 109 | no shutdown 110 | ip address 192.168.0.15/24 111 | ! 112 | interface Vlan110 113 | description Tenant_A_OP_Zone_1 114 | no shutdown 115 | vrf Tenant_A_OP_Zone 116 | ip address virtual 10.1.10.1/24 117 | ! 118 | interface Vlan3009 119 | description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone 120 | no shutdown 121 | mtu 1500 122 | vrf Tenant_A_OP_Zone 123 | ip address 10.255.251.5/31 124 | ! 125 | interface Vlan4093 126 | description MLAG_PEER_L3_PEERING 127 | no shutdown 128 | mtu 1500 129 | ip address 10.255.251.5/31 130 | ! 131 | interface Vlan4094 132 | description MLAG_PEER 133 | no shutdown 134 | mtu 1500 135 | no autostate 136 | ip address 10.255.252.5/31 137 | ! 138 | interface Vxlan1 139 | description leaf4_VTEP 140 | vxlan source-interface Loopback1 141 | vxlan virtual-router encapsulation mac-address mlag-system-id 142 | vxlan udp-port 4789 143 | vxlan vlan 110 vni 10110 144 | vxlan vlan 160 vni 55160 145 | vxlan vrf Tenant_A_OP_Zone vni 10 146 | ! 147 | ip virtual-router mac-address 00:1c:73:00:dc:01 148 | ! 149 | ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.6 150 | ! 151 | ip routing 152 | ip routing vrf Tenant_A_OP_Zone 153 | ! 154 | ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY 155 | seq 10 permit 192.0.255.0/24 eq 32 156 | seq 20 permit 192.0.254.0/24 eq 32 157 | ! 158 | mlag configuration 159 | domain-id pod2 160 | local-interface Vlan4094 161 | peer-address 10.255.252.4 162 | peer-link Port-Channel1 163 | reload-delay mlag 300 164 | reload-delay non-mlag 330 165 | ! 166 | ip route 0.0.0.0/0 192.168.0.1 167 | ! 168 | route-map RM-CONN-2-BGP permit 10 169 | match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY 170 | ! 171 | route-map RM-MLAG-PEER-IN permit 10 172 | description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing 173 | set origin incomplete 174 | ! 175 | router bfd 176 | multihop interval 1200 min-rx 1200 multiplier 3 177 | ! 178 | router bgp 65102 179 | router-id 192.0.255.6 180 | no bgp default ipv4-unicast 181 | distance bgp 20 200 200 182 | graceful-restart restart-time 300 183 | graceful-restart 184 | maximum-paths 4 ecmp 4 185 | neighbor EVPN-OVERLAY-PEERS peer group 186 | neighbor EVPN-OVERLAY-PEERS update-source Loopback0 187 | neighbor EVPN-OVERLAY-PEERS bfd 188 | neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3 189 | neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA== 190 | neighbor EVPN-OVERLAY-PEERS send-community 191 | neighbor EVPN-OVERLAY-PEERS maximum-routes 0 192 | neighbor IPv4-UNDERLAY-PEERS peer group 193 | neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w== 194 | neighbor IPv4-UNDERLAY-PEERS send-community 195 | neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000 196 | neighbor MLAG-IPv4-UNDERLAY-PEER peer group 197 | neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65102 198 | neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self 199 | neighbor MLAG-IPv4-UNDERLAY-PEER description leaf3 200 | neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ== 201 | neighbor MLAG-IPv4-UNDERLAY-PEER send-community 202 | neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000 203 | neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in 204 | neighbor 10.255.251.4 peer group MLAG-IPv4-UNDERLAY-PEER 205 | neighbor 10.255.251.4 description leaf3 206 | neighbor 172.30.255.12 peer group IPv4-UNDERLAY-PEERS 207 | neighbor 172.30.255.12 remote-as 65001 208 | neighbor 172.30.255.12 description spine1_Ethernet5 209 | neighbor 172.30.255.14 peer group IPv4-UNDERLAY-PEERS 210 | neighbor 172.30.255.14 remote-as 65001 211 | neighbor 172.30.255.14 description spine2_Ethernet5 212 | neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS 213 | neighbor 192.0.255.1 remote-as 65001 214 | neighbor 192.0.255.1 description spine1 215 | neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS 216 | neighbor 192.0.255.2 remote-as 65001 217 | neighbor 192.0.255.2 description spine2 218 | redistribute connected route-map RM-CONN-2-BGP 219 | ! 220 | vlan-aware-bundle Tenant_A_OP_Zone 221 | rd 192.0.255.6:10 222 | route-target both 10:10 223 | redistribute learned 224 | vlan 110 225 | ! 226 | vlan-aware-bundle Tenant_A_VMOTION 227 | rd 192.0.255.6:55160 228 | route-target both 55160:55160 229 | redistribute learned 230 | vlan 160 231 | ! 232 | address-family evpn 233 | neighbor EVPN-OVERLAY-PEERS activate 234 | ! 235 | address-family ipv4 236 | no neighbor EVPN-OVERLAY-PEERS activate 237 | neighbor IPv4-UNDERLAY-PEERS activate 238 | neighbor MLAG-IPv4-UNDERLAY-PEER activate 239 | ! 240 | vrf Tenant_A_OP_Zone 241 | rd 192.0.255.6:10 242 | route-target import evpn 10:10 243 | route-target export evpn 10:10 244 | router-id 192.0.255.6 245 | neighbor 10.255.251.4 peer group MLAG-IPv4-UNDERLAY-PEER 246 | redistribute connected 247 | ! 248 | management api http-commands 249 | protocol https 250 | no shutdown 251 | ! 252 | vrf default 253 | no shutdown 254 | ! 255 | end 256 | -------------------------------------------------------------------------------- /test/system/test_cvp_base.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=wrong-import-position 2 | 3 | ''' Base class for TestCvpClient and TestCvpClientCC class. 4 | ''' 5 | from pprint import pprint 6 | import os 7 | import sys 8 | import re 9 | import time 10 | import uuid 11 | import urllib3 12 | 13 | sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) 14 | from systestlib import DutSystemTest 15 | from cvprac.cvp_client import CvpClient 16 | 17 | 18 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 19 | 20 | 21 | class TestCvpClientBase(DutSystemTest): 22 | ''' Base class for TestCvpClient and TestCvpClientCC class. 23 | ''' 24 | # pylint: disable=too-many-public-methods 25 | # pylint: disable=invalid-name 26 | @classmethod 27 | def setUpClass(cls): 28 | ''' Instantiate the CvpClient class and connect to the CVP node. 29 | Log messages to the /tmp/TestCvpClient.log 30 | ''' 31 | super(TestCvpClientBase, cls).setUpClass() 32 | cls.clnt = CvpClient(filename='/tmp/TestCvpClient.log') 33 | assert cls.clnt is not None 34 | assert cls.clnt.last_used_node is None 35 | dut = cls.duts[0] 36 | cert = dut.get("cert", False) 37 | username = dut.get("username", "") 38 | password = dut.get("password", "") 39 | api_token = dut.get("api_token", None) 40 | is_cvaas = dut.get("is_cvaas", False) 41 | connect_timeout = dut.get("connect_timeout", 10) 42 | request_timeout = dut.get("request_timeout", 30) 43 | 44 | cls.clnt.connect([dut['node']], username, password, 45 | connect_timeout, request_timeout, 46 | cert=cert, is_cvaas=is_cvaas, api_token=api_token) 47 | 48 | # Store user provided request_timeout for use during 49 | # test_api_request_timeout testcase 50 | cls.request_timeout = request_timeout 51 | cls.api = cls.clnt.api 52 | assert cls.api is not None 53 | 54 | # Verify that there is at least one device in the inventory 55 | err_msg = 'CVP node must contain at least one device' 56 | result = cls.api.get_inventory() 57 | assert result is not None 58 | if len(result) < 1: 59 | raise AssertionError(err_msg) 60 | assert len(result) >= 1 61 | device = [res for res in result if res['hostname'] == 62 | dut.get("device", "")] 63 | cls.device = device[0] 64 | # Get the container for the device on the list and 65 | # use that container as the parent container. 66 | result = cls.api.search_topology(cls.device['fqdn']) 67 | assert result is not None 68 | dev_container = result['netElementContainerList'] 69 | assert len(dev_container) >= 1 70 | info = dev_container[0] 71 | result = cls.api.search_topology(info['containerName']) 72 | assert result is not None 73 | cls.container = result['containerList'][0] 74 | 75 | # Get the configlets assigned to the device. There must be at least 1. 76 | err_msg = 'CVP node device must have at least one configlet assigned' 77 | key = info['netElementKey'] 78 | result = cls.api.get_configlets_by_device_id(key) 79 | if len(result) < 1: 80 | raise AssertionError(err_msg) 81 | cls.dev_configlets = result 82 | 83 | @classmethod 84 | def tearDownClass(cls): 85 | ''' Destroy the CvpClient class. 86 | ''' 87 | super(TestCvpClientBase, cls).tearDownClass() 88 | cls.api = None 89 | cls.clnt = None 90 | 91 | def cancel_task(self, task_id): 92 | """ Cancel task 93 | """ 94 | pprint('CANCELING TASK...') 95 | data = {'data': [task_id]} 96 | self.clnt.post('/task/cancelTask.do', data=data) 97 | 98 | def setUp(self): 99 | ''' 100 | Set the task_id, cc_id and cc_name. It executes before each test case 101 | ''' 102 | self.task_id = None 103 | self.cc_id = str(uuid.uuid4()) 104 | # self.cc_name = 'test_api_%d %s' % time.time() 105 | self.cc_name = f'test_api_{time.time()}' 106 | 107 | def tearDown(self): 108 | ''' 109 | Delete the change control if its present and cancel the task 110 | if it exists 111 | ''' 112 | chg_ctrl_get_one = self.api.change_control_get_one(self.cc_id) 113 | if chg_ctrl_get_one: 114 | # Delete CC 115 | self.delete_change_control(self.cc_id) 116 | # Cancel task 117 | if self.task_id: 118 | task = self.api.get_task_by_id(self.task_id) 119 | if task and task['currentTaskName'] != "Cancelled": 120 | self.cancel_task(self.task_id) 121 | 122 | def _get_next_task_id(self): 123 | ''' Return the next task id. 124 | 125 | Returns: 126 | task_id (str): Task ID 127 | ''' 128 | # Get all the tasks and the task id of the next task created is 129 | # the length + 1. 130 | results = self.api.get_tasks() 131 | self.assertIsNotNone(results) 132 | latest_task = str(int(results['data'][0]['workOrderId']) + 1) 133 | return latest_task 134 | 135 | def _create_task(self): 136 | ''' Create a task by making a simple change to a configlet assigned 137 | to the device. 138 | 139 | Returns: 140 | (task_id, config) 141 | task_id (str): Task ID 142 | config (str): Previous configlets contents 143 | ''' # pylint: disable=duplicate-code 144 | task_id = self._get_next_task_id() 145 | # Update the lldp time in the first configlet in the list. 146 | configlet = None 147 | for conf in self.dev_configlets: 148 | if conf['netElementCount'] == 1: 149 | configlet = conf 150 | break 151 | if configlet is None: 152 | configlet = self.dev_configlets[0] 153 | 154 | config = configlet['config'] 155 | org_config = config 156 | 157 | match = re.match(r'lldp timer (\d+)', config) 158 | if match is not None: 159 | value = int(match.group(1)) + 1 160 | repl = f'lldp timer {value}' 161 | config = re.sub(match.group(0), repl, config) 162 | else: 163 | value = 13 164 | config = f'lldp timer {value}\n' + config 165 | configlet['config'] = config 166 | 167 | # Updating the configlet will cause a task to be created to apply 168 | # the change to the device. 169 | self.api.update_configlet(config, configlet['key'], configlet['name']) 170 | 171 | # Wait 30 seconds for task to get created 172 | cnt = 30 173 | if self.clnt.apiversion is None: 174 | self.api.get_cvp_info() 175 | if self.clnt.apiversion >= 2.0: 176 | # Increase timeout by 30 sec for CVP 2018.2 and beyond 177 | cnt += 30 178 | while cnt > 0: 179 | time.sleep(1) 180 | result = self.api.get_task_by_id(task_id) 181 | if result is not None: 182 | break 183 | cnt -= 1 184 | err_msg = f'Timeout waiting for task id {task_id} to be created' 185 | self.assertGreater(cnt, 0, msg=err_msg) 186 | self.task_id = task_id 187 | return task_id, org_config 188 | 189 | def delete_change_control(self, cc_id): 190 | """ Delete change control 191 | """ 192 | pprint('DELETING CHANGE CONTROL...') 193 | delete_chg_ctrl = self.api.change_control_delete( 194 | cc_id) 195 | assert delete_chg_ctrl is not None 196 | assert delete_chg_ctrl['key']['id'] == self.cc_id 197 | return delete_chg_ctrl 198 | -------------------------------------------------------------------------------- /docs/labs/lab06-provisioning/mlag_issu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # python3 mlag_issu " 4 | # 5 | # # Example of upgrade inventory file (YAML) 6 | # cvp_hosts: 7 | # - 192.168.0.191 8 | # - 192.168.0.192 9 | # - 192.168.0.193 10 | # cvp_username: cvpadmin 11 | # target_eos_version: 4.25.4M 12 | # target_terminattr_version: 1.13.6 13 | # mlag_couples: 14 | # - peer1: leaf101-1 15 | # peer2: leaf101-2 16 | # - peer1: leaf102-1 17 | # peer2: leaf102-2 18 | # 19 | # Note: upgrades are performed in parallel 20 | 21 | import sys 22 | import time 23 | import string 24 | import random 25 | from getpass import getpass 26 | import requests 27 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 28 | from datetime import datetime 29 | from cvprac.cvp_client import CvpClient 30 | from cvprac.cvp_client_errors import CvpLoginError, CvpApiError 31 | from pprint import pprint 32 | from operator import itemgetter 33 | import yaml 34 | 35 | class CvpDeviceUpgrader(object): 36 | def __init__(self, hosts, username, password): 37 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 38 | self.cvp_hosts = hosts 39 | self.cvp_user = username 40 | self.cvp_password = password 41 | self.session = self._open_cvp_session() 42 | 43 | def _open_cvp_session(self): 44 | try: 45 | client = CvpClient() 46 | client.connect( 47 | nodes=self.cvp_hosts, 48 | username=self.cvp_user, 49 | password=self.cvp_password, 50 | request_timeout=300, 51 | connect_timeout=30 52 | ) 53 | return(client) 54 | except CvpLoginError as e: 55 | print(f"Cannot connect to CVP API: {e}") 56 | exit() 57 | 58 | def create_mlag_issu_change_control(self, taskIDs, deviceIDs): 59 | cc_id = f"CC_{datetime.now().strftime('%Y%m%d_%H%M%S')}" 60 | pre_upgrade_stage = {'stage': [{ 61 | 'id': f"preU_{cc_id}", 62 | 'name': 'pre_upgrade', 63 | 'stage_row':[{'stage': [{ 64 | 'id': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(9)), 65 | 'action': { 66 | 'name': 'mlaghealthcheck', 67 | 'timeout': 0, 68 | 'args': { 69 | 'DeviceID': device_id 70 | } 71 | } 72 | } for device_id in deviceIDs]}] 73 | }]} 74 | upgrade_stage = {'stage': [{ 75 | 'id': f"U_{cc_id}", 76 | 'name': 'upgrade', 77 | 'stage_row': [{'stage': [{ 78 | 'id': task_id, 79 | 'action': { 80 | 'name': 'task', 81 | 'args': { 82 | 'TaskID': task_id 83 | } 84 | } 85 | } for task_id in taskIDs]}] 86 | }]} 87 | post_upgrade_stage = {'stage': [{ 88 | 'id': f"postU_{cc_id}", 89 | 'name': 'post_upgrade', 90 | 'stage_row': [{'stage': [{ 91 | 'id': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(9)), 92 | 'action': { 93 | 'name': 'mlaghealthcheck', 94 | 'timeout': 0, 95 | 'args': { 96 | 'DeviceID': device_id 97 | } 98 | } 99 | } for device_id in deviceIDs]}] 100 | }]} 101 | cc_data = {'config': { 102 | 'id': cc_id, 103 | 'name': f"Change Control {cc_id}", 104 | 'root_stage': { 105 | 'id': 'root', 106 | 'name': f"Change Control {cc_id} root", 107 | 'stage_row': [pre_upgrade_stage, upgrade_stage, post_upgrade_stage], 108 | } 109 | }} 110 | try: 111 | res = self.session.post('/api/v3/services/ccapi.ChangeControl/Update', 112 | data=cc_data, 113 | timeout=self.session.api.request_timeout 114 | ) 115 | except Exception as e: 116 | print(str(e)) 117 | return(None) 118 | print(f"Change control {res[0]['id']} created at {res[0]['update_timestamp']}") 119 | return(res[0]['id']) 120 | 121 | def get_mlag_issu_change_control_logs(self, ccID, startTime): 122 | end_time = int(time.time() * 1000) 123 | cc_logs_data = {'category': 'ChangeControl', 124 | 'objectKey': ccID, 125 | 'dataSize': 15000, 126 | 'startTime': startTime, 127 | 'endTime': end_time 128 | } 129 | logs = self.session.post('/cvpservice/audit/getLogs.do', 130 | data=cc_logs_data, 131 | timeout=self.session.api.request_timeout 132 | ) 133 | for log in sorted(logs['data'], key=itemgetter('dateTimeInLongFormat')): 134 | if log['subObjectName'] and 'Command(s)' not in log['activity']: 135 | log_date = datetime.fromtimestamp(log['dateTimeInLongFormat']/1000) 136 | print(f"{log_date} {log['subObjectName']}: {log['activity']}") 137 | return(end_time + 1) 138 | 139 | def run_mlag_issu_change_control(self, ccID): 140 | print(f"Automatic approval of change control {ccID}") 141 | self.session.api.approve_change_control(ccID, datetime.utcnow().isoformat() + 'Z') 142 | time.sleep(2) 143 | print(f"Starting the execution of change control {ccID}") 144 | start_time = int(time.time() * 1000) 145 | self.session.api.execute_change_controls([ccID]) 146 | time.sleep(2) 147 | cc_status = self.session.api.get_change_control_status(ccID)[0]['status'] 148 | start_time = self.get_mlag_issu_change_control_logs(ccID, start_time) 149 | while cc_status['state'] == 'Running': 150 | time.sleep(30) 151 | cc_status = self.session.api.get_change_control_status(ccID)[0]['status'] 152 | start_time = self.get_mlag_issu_change_control_logs(ccID, start_time) 153 | print(f"Change control {ccID} final status: {cc_status['state']}") 154 | if cc_status['error']: 155 | print(f"Change control {ccID} had the following errors: {cc_status['error']}") 156 | else: 157 | print(f"Change control {ccID} completed without errors") 158 | 159 | def main(): 160 | if len(sys.argv) != 3: 161 | print(f"Usage: python3 {sys.argv[0]} ") 162 | exit() 163 | try: 164 | with open(sys.argv[1], 'r') as yf: 165 | params = yaml.safe_load(yf) 166 | except Exception as e: 167 | print(e) 168 | exit() 169 | cvp_password = getpass(prompt=f"CVP password for user {params['cvp_username']}: ") 170 | cvpdu = CvpDeviceUpgrader( 171 | hosts=params['cvp_hosts'], 172 | username=params['cvp_username'], 173 | password=cvp_password 174 | ) 175 | image_bundle = None 176 | for bundle in cvpdu.session.api.get_image_bundles()['data']: 177 | eos_match = False 178 | terminattr_match = False 179 | for img in bundle['imageIds']: 180 | if params['target_eos_version'] in img: 181 | eos_match = True 182 | elif params['target_terminattr_version'] in img: 183 | terminattr_match = True 184 | if eos_match and terminattr_match: 185 | image_bundle = bundle 186 | break 187 | if image_bundle is None: 188 | print(f"Cannot find an image bundle with EOS {params['target_eos_version']} and TerminAttr {params['target_terminattr_version']}") 189 | exit() 190 | hostnames = [couple[sys.argv[2]] for couple in params['mlag_couples']] 191 | devices_to_upgrade = list() 192 | inventory = cvpdu.session.api.get_inventory() 193 | for hostname in hostnames: 194 | provisioned = False 195 | for dev in inventory: 196 | if dev['hostname'] == hostname: 197 | provisioned = True 198 | devices_to_upgrade.append(dev) 199 | break 200 | if not provisioned: 201 | print(f"Device with hostname {hostname} is not provisioned in CVP") 202 | if not devices_to_upgrade: 203 | print('none of the mentioned devices is provisioned in CVP') 204 | exit() 205 | print(f"Devices to upgrade: {', '.join([dev['hostname'] for dev in devices_to_upgrade])}") 206 | task_ids = list() 207 | for device in devices_to_upgrade: 208 | response = cvpdu.session.api.apply_image_to_device(image_bundle, device)['data'] 209 | if response['status'] == 'success': 210 | task_ids.extend(response['taskIds']) 211 | device_ids = [device['serialNumber'] for device in devices_to_upgrade] 212 | cc_id = cvpdu.create_mlag_issu_change_control(task_ids, device_ids) 213 | if cc_id is None: 214 | print('Failed to create the MLAG ISSU change control') 215 | exit() 216 | time.sleep(2) 217 | cvpdu.run_mlag_issu_change_control(cc_id) 218 | 219 | if __name__ == '__main__': 220 | main() 221 | --------------------------------------------------------------------------------