summaryrefslogtreecommitdiffstats
path: root/docs/labs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-05-11 09:04:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-05-13 09:34:21 +0000
commit47553c43d71b7b1144f912ab9679f5b60e858fa2 (patch)
tree08378beaeeea8f9bb2686d3037c7b6f5062bb948 /docs/labs
parentInitial commit. (diff)
downloadcvprac-47553c43d71b7b1144f912ab9679f5b60e858fa2.tar.xz
cvprac-47553c43d71b7b1144f912ab9679f5b60e858fa2.zip
Adding upstream version 1.3.1+dfsg.upstream/1.3.1+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'docs/labs')
-rw-r--r--docs/labs/README.md67
-rw-r--r--docs/labs/lab01-cvp-info/get_cvp_info.py17
-rw-r--r--docs/labs/lab02-inventory-operations/compliance_check.py57
-rw-r--r--docs/labs/lab02-inventory-operations/get_running_configs.py31
-rw-r--r--docs/labs/lab02-inventory-operations/get_running_configs_by_time.py34
-rw-r--r--docs/labs/lab02-inventory-operations/remove_all_devices_legacy.py30
-rw-r--r--docs/labs/lab02-inventory-operations/remove_and_decommission_device.py32
-rw-r--r--docs/labs/lab02-inventory-operations/remove_devices_from_container_legacy.py32
-rw-r--r--docs/labs/lab02-inventory-operations/remove_devices_legacy.py30
-rw-r--r--docs/labs/lab03-configlet-management/assign_configlet_to_device.py23
-rw-r--r--docs/labs/lab03-configlet-management/backup_configlets.py20
-rw-r--r--docs/labs/lab03-configlet-management/backup_configletsV2.py48
-rw-r--r--docs/labs/lab03-configlet-management/common.cfg6
-rw-r--r--docs/labs/lab03-configlet-management/config_search.py54
-rw-r--r--docs/labs/lab03-configlet-management/configlet_list.txt6
-rw-r--r--docs/labs/lab03-configlet-management/create_configlet.py24
-rw-r--r--docs/labs/lab03-configlet-management/create_configlet_from_file.py19
-rw-r--r--docs/labs/lab03-configlet-management/get_applied_netelements.py36
-rw-r--r--docs/labs/lab03-configlet-management/get_configlets.py53
-rw-r--r--docs/labs/lab03-configlet-management/reorder_configlet_on_device.py26
-rw-r--r--docs/labs/lab03-configlet-management/update_configlet.py28
-rw-r--r--docs/labs/lab04-container-management/add_image_to_container.py21
-rw-r--r--docs/labs/lab04-container-management/assign_configlet_to_container.py23
-rw-r--r--docs/labs/lab04-container-management/create_container.py20
-rw-r--r--docs/labs/lab04-container-management/remove_image_from_container.py21
-rw-r--r--docs/labs/lab04-container-management/rename_container.py32
-rw-r--r--docs/labs/lab05-device-management/add_image_to_devices.py21
-rw-r--r--docs/labs/lab05-device-management/add_image_wo_tempaction.py100
-rw-r--r--docs/labs/lab05-device-management/remove_image_from_device.py21
-rw-r--r--docs/labs/lab05-device-management/set_mgmt_ip.py34
-rw-r--r--docs/labs/lab06-provisioning/atd_e2e_provisioning_workflow.py120
-rw-r--r--docs/labs/lab06-provisioning/auto_reconcile_on_rc_change.py64
-rw-r--r--docs/labs/lab06-provisioning/change_control_custom_rapi.py81
-rw-r--r--docs/labs/lab06-provisioning/change_control_workflow.py27
-rw-r--r--docs/labs/lab06-provisioning/change_control_workflow_rapi.py40
-rw-r--r--docs/labs/lab06-provisioning/configlets/AVD_leaf1.cfg255
-rw-r--r--docs/labs/lab06-provisioning/configlets/AVD_leaf2.cfg255
-rw-r--r--docs/labs/lab06-provisioning/configlets/AVD_leaf3.cfg255
-rw-r--r--docs/labs/lab06-provisioning/configlets/AVD_leaf4.cfg255
-rw-r--r--docs/labs/lab06-provisioning/configlets/AVD_spine1.cfg129
-rw-r--r--docs/labs/lab06-provisioning/configlets/AVD_spine2.cfg129
-rw-r--r--docs/labs/lab06-provisioning/gen_builder.py63
-rw-r--r--docs/labs/lab06-provisioning/mlag_issu.py220
-rw-r--r--docs/labs/lab06-provisioning/move_device.py24
-rw-r--r--docs/labs/lab06-provisioning/vc_task_retrigger.py115
-rw-r--r--docs/labs/lab07-aaa/aaa_users.csv5
-rw-r--r--docs/labs/lab07-aaa/add_new_user_cvaas.py32
-rw-r--r--docs/labs/lab07-aaa/add_new_user_onprem.py29
-rw-r--r--docs/labs/lab07-aaa/add_users_from_csv_cvaas.py29
-rw-r--r--docs/labs/lab07-aaa/create_svc_account.py20
-rw-r--r--docs/labs/lab07-aaa/create_svc_account_token.py23
-rw-r--r--docs/labs/lab07-aaa/create_terminattr_tokens.py32
-rw-r--r--docs/labs/lab07-aaa/cvaas.tok1
-rw-r--r--docs/labs/lab07-aaa/delete_all_expired_svc_account_tokens.py16
-rw-r--r--docs/labs/lab07-aaa/delete_svc_account.py17
-rw-r--r--docs/labs/lab07-aaa/delete_svc_account_created_by_user.py22
-rw-r--r--docs/labs/lab07-aaa/get_user_info.py20
-rw-r--r--docs/labs/lab07-aaa/svc_account_misc.py34
-rw-r--r--docs/labs/lab08-resource-apis/resource_cvprac.py187
-rw-r--r--docs/labs/lab08-resource-apis/topology_tag_assignment.py106
-rw-r--r--docs/labs/static/serviceaccount1.pngbin0 -> 117691 bytes
-rw-r--r--docs/labs/static/serviceaccount2.pngbin0 -> 123315 bytes
-rw-r--r--docs/labs/static/serviceaccount3.pngbin0 -> 106538 bytes
63 files changed, 3571 insertions, 0 deletions
diff --git a/docs/labs/README.md b/docs/labs/README.md
new file mode 100644
index 0000000..132ee64
--- /dev/null
+++ b/docs/labs/README.md
@@ -0,0 +1,67 @@
+# cvprac labs
+
+The following lab examples will walk through the most commonly used REST API calls using cvprac
+to help users interact with Arista CloudVision easily and automate the provisioning of network devices.
+
+## Table of Contents
+
+1. [Authentication](#authentication)
+ - [Password Authentication](#password-authentication)
+ - [Service Account Token Authentication](#service-account-token-authentication)
+1. [Known Limitations](#known-limitations)
+
+## Authentication
+
+There are two ways to authenticate using the REST APIs:
+
+- user/password (on-prem only)
+- service account token (available on CVP 2020.3.0+ and CVaaS)
+
+### Password Authentication
+
+```python
+from cvprac.cvp_client import CvpClient
+clnt = CvpClient()
+clnt.connect(['10.83.13.33'],'cvpadmin', 'arastra')
+```
+
+### Service Account Token Authentication
+
+To access the CloudVision as-a-Service and send API requests, "Service Account Token" is needed.
+After obtaining the service account token, it can be used for authentication when sending API requests.
+
+Service accounts can be created from the Settings page where a service token can be generated as seen below:
+
+![serviceaccount1](./static/serviceaccount1.png)
+![serviceaccount2](./static/serviceaccount2.png)
+![serviceaccount3](./static/serviceaccount3.png)
+
+The token should be copied and saved to a file that can later be referred to.
+
+```python
+from cvprac.cvp_client import CvpClient
+clnt = CvpClient()
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+```
+
+>NOTE In case of CVaaS the `is_cvaas` parameters has to be set to `True`
+
+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).
+
+```python
+from cvprac.cvp_client import CvpClient
+
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['10.83.13.33'], username='',password='',api_token=token)
+```
+
+## Known Limitations
+
+- for any APIs that interact with EOS devices, the service account name must match the name of the username
+ configured on EOS and CVP
+- Support for REST API bindings for the Resource APIs (Lab 8) was added in CVP 2021.1.0
diff --git a/docs/labs/lab01-cvp-info/get_cvp_info.py b/docs/labs/lab01-cvp-info/get_cvp_info.py
new file mode 100644
index 0000000..0e002d0
--- /dev/null
+++ b/docs/labs/lab01-cvp-info/get_cvp_info.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+result = clnt.api.get_cvp_info()
+print(result)
diff --git a/docs/labs/lab02-inventory-operations/compliance_check.py b/docs/labs/lab02-inventory-operations/compliance_check.py
new file mode 100644
index 0000000..306b407
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/compliance_check.py
@@ -0,0 +1,57 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+### Compliance Code description
+compliance = {"0000":"Configuration is in sync",
+ "0001": "Config is out of sync",
+ "0002": "Image is out of sync",
+ "0003": "Config & image out of sync",
+ "0004": "Config, Image and Device time are in sync",
+ "0005": "Device is not reachable",
+ "0006": "The current EOS version on this device is not supported by CVP. Upgrade the device to manage.",
+ "0007": "Extensions are out of sync",
+ "0008": "Config, Image and Extensions are out of sync",
+ "0009": "Config and Extensions are out of sync",
+ "0010": "Image and Extensions are out of sync",
+ "0011": "Unauthorized User",
+ "0012": "Config, Image, Extension and Device time are out of sync",
+ "0013": "Config, Image and Device time are out of sync",
+ "0014": "Config, Extensions and Device time are out of sync",
+ "0015": "Image, Extensions and Device time are out of sync",
+ "0016": "Config and Device time are out of sync",
+ "0017": "Image and Device time are out of sync",
+ "0018": "Extensions and Device time are out of sync",
+ "0019": "Device time is out of sync"
+}
+
+# Create connection to CloudVision using Service account token
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+def check_devices_under_container(client, container):
+ ''' container is the container ID '''
+
+ nodeId = container['key']
+ nodeName = container['name']
+ api = '/ztp/getAllNetElementList.do?'
+ queryParams = "nodeId={}&queryParam=&nodeName={}&startIndex=0&endIndex=0&contextQueryParam=&ignoreAdd=false&useCache=true".format(nodeId, nodeName)
+ return client.get(api + queryParams)
+
+
+container = clnt.api.get_container_by_name('TP_LEAFS')
+
+devices = (check_devices_under_container(clnt,container))
+
+for device in devices['netElementList']:
+ code = device['complianceCode']
+ print(device['fqdn'], ' ', code,' ', compliance[code])
diff --git a/docs/labs/lab02-inventory-operations/get_running_configs.py b/docs/labs/lab02-inventory-operations/get_running_configs.py
new file mode 100644
index 0000000..5478f2e
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/get_running_configs.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+# Get the full inventory
+inventory = clnt.api.get_inventory()
+
+# Create a list of MAC addresses
+device_macs = []
+for i in inventory:
+ device_macs.append(i['systemMacAddress'])
+
+# Create a dictionary with MAC to running-config mapping
+running_configs = {}
+for i in device_macs:
+ running_configs[i] = clnt.api.get_device_configuration(i)
+
+# Write the running-configs of each device using the hostname as the filename
+for i in inventory:
+ with open(i['fqdn']+'.cfg', 'w') as f:
+ f.write(running_configs[i['systemMacAddress']]) \ No newline at end of file
diff --git a/docs/labs/lab02-inventory-operations/get_running_configs_by_time.py b/docs/labs/lab02-inventory-operations/get_running_configs_by_time.py
new file mode 100644
index 0000000..7bbc294
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/get_running_configs_by_time.py
@@ -0,0 +1,34 @@
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+ts = "2021-11-19T15:04:05.0Z" # rfc3339 time
+uri = "/api/v3/services/compliancecheck.Compliance/GetConfig"
+
+# Fetch the inventory
+inventory = clnt.api.get_inventory()
+
+# Iterate through all devices and get the running-config at the specified time for each device
+for device in inventory:
+ sn = device['serialNumber']
+ data = {"request":{
+ "device_id": sn,
+ "timestamp": ts,
+ "type":"RUNNING_CONFIG"
+ }
+ }
+ try:
+ resultRunningConfig = clnt.post(uri, data=data)
+ for idx in resultRunningConfig:
+ if 'config' in idx:
+ result = idx['config']
+ break
+ with open(device['hostname']+'.cfg','w') as f:
+ f.write(result)
+ except Exception as e:
+ print("Not able to get configuration for device {} - exception {}".format(device['fqdn'], e))
diff --git a/docs/labs/lab02-inventory-operations/remove_all_devices_legacy.py b/docs/labs/lab02-inventory-operations/remove_all_devices_legacy.py
new file mode 100644
index 0000000..f8ca8cb
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_all_devices_legacy.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+inventory = clnt.api.get_inventory()
+
+devices = []
+for netelement in inventory:
+ devices.append(netelement['systemMacAddress'])
+
+# Remove devices from provisioning
+# This is a legacy API call that removes the devices from Network Provisioning
+# in CVP versions older than 2021.3.0, however it does not remove them from
+# the Device Inventory as that requires the streaming agent (TerminAttr) to be shutdown,
+# which this API does not support.
+# To fully decommission a device the device_decommissioning() API can be used, which is
+# supported from 2021.3.0+.
+# Note that using the delete_devices() function post CVP 2021.3.0 the device will be
+# automatically added back to the Undefined container.
+clnt.api.delete_devices(devices)
diff --git a/docs/labs/lab02-inventory-operations/remove_and_decommission_device.py b/docs/labs/lab02-inventory-operations/remove_and_decommission_device.py
new file mode 100644
index 0000000..16e783a
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_and_decommission_device.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2022 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+import uuid
+import time
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username", password="password")
+
+device_id = input("Serial number of the device to be decommissioned: ")
+request_id = str(uuid.uuid4())
+clnt.api.device_decommissioning(device_id, request_id)
+
+# This API call will fully decommission the device, ie remove it from both
+# Network Provisioning and Device Inventory (telemetry). It send an eAPI request
+# to EOS to shutdown the TerminAttr daemon, waits for streaming to stop and then removes
+# the device from provisioning and finally decommissions it. This operation can take a few minutes.
+# Supported from CVP 2021.3.0+ and CVaaS.
+decomm_status = "DECOMMISSIONING_STATUS_SUCCESS"
+decomm_result = ""
+while decomm_result != decomm_status:
+ decomm_result = clnt.api.device_decommissioning_status_get_one(request_id)['value']['status']
+ time.sleep(10)
+
+print(decomm_result)
diff --git a/docs/labs/lab02-inventory-operations/remove_devices_from_container_legacy.py b/docs/labs/lab02-inventory-operations/remove_devices_from_container_legacy.py
new file mode 100644
index 0000000..93e0e19
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_devices_from_container_legacy.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+# Get devices in a specific container
+inventory = clnt.api.get_devices_in_container("Undefined")
+
+# Create device list
+devices = []
+for netelement in inventory:
+ devices.append(netelement['systemMacAddress'])
+
+# Remove devices from provisioning
+# This is a legacy API call that removes the devices from Network Provisioning
+# in CVP versions older than 2021.3.0, however it does not remove them from
+# the Device Inventory as that requires the streaming agent (TerminAttr) to be shutdown,
+# which this API does not support.
+# To fully decommission a device the device_decommissioning() API can be used, which is
+# supported from 2021.3.0+.
+# Note that using the delete_devices() function post CVP 2021.3.0 the device will be
+# automatically added back to the Undefined container.
+clnt.api.delete_devices(devices)
diff --git a/docs/labs/lab02-inventory-operations/remove_devices_legacy.py b/docs/labs/lab02-inventory-operations/remove_devices_legacy.py
new file mode 100644
index 0000000..1f274f0
--- /dev/null
+++ b/docs/labs/lab02-inventory-operations/remove_devices_legacy.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using Service account token
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='', password='', api_token=token)
+
+devices = ["50:08:00:a7:ca:c3","50:08:00:b1:5b:0b","50:08:00:60:c6:76",
+ "50:08:00:25:9d:36","50:08:00:8b:ee:b1","50:08:00:8c:22:49"]
+
+# Remove devices from provisioning
+# This is a legacy API call that removes the devices from Network Provisioning
+# in CVP versions older than 2021.3.0, however it does not remove them from
+# the Device Inventory as that requires the streaming agent (TerminAttr) to be shutdown,
+# which this API does not support.
+# To fully decommission a device the device_decommissioning() API can be used, which is
+# supported from 2021.3.0+.
+# Note that using the delete_devices() function post CVP 2021.3.0 the device will be
+# automatically added back to the Undefined container.
+clnt.api.delete_devices(devices)
diff --git a/docs/labs/lab03-configlet-management/assign_configlet_to_device.py b/docs/labs/lab03-configlet-management/assign_configlet_to_device.py
new file mode 100644
index 0000000..2973408
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/assign_configlet_to_device.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletName = 'cvprac_example2'
+
+device_name = "tp-avd-leaf1"
+device = clnt.api.get_device_by_name(device_name)
+
+configlet = clnt.api.get_configlet_by_name(configletName)
+
+clnt.api.apply_configlets_to_device("", device, [configlet])
diff --git a/docs/labs/lab03-configlet-management/backup_configlets.py b/docs/labs/lab03-configlet-management/backup_configlets.py
new file mode 100644
index 0000000..6543a6b
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/backup_configlets.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Get configlets and save them to individual files
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+configlets = clnt.api.get_configlets(start=0,end=0)
+
+for configlet in configlets['data']:
+ with open(configlet['name'],'w') as f:
+ f.write(configlet['config'])
diff --git a/docs/labs/lab03-configlet-management/backup_configletsV2.py b/docs/labs/lab03-configlet-management/backup_configletsV2.py
new file mode 100644
index 0000000..0247f19
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/backup_configletsV2.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Get configlets and save them to individual files using multi threading
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from concurrent.futures import ThreadPoolExecutor
+from functools import wraps
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+total = clnt.api.get_configlets(start=0,end=1)['total']
+
+def get_list_of_configlets():
+ """
+ Create a thread pool and download specified urls
+ """
+
+ futures_list = []
+ results = []
+
+ with ThreadPoolExecutor(max_workers=40) as executor:
+ for i in range(0,total+1,10):
+ futures = executor.submit(clnt.api.get_configlets, start=i,end=i+10)
+ futures_list.append(futures)
+
+ for future in futures_list:
+ try:
+ result = future.result(timeout=60)
+ results.append(result)
+ except Exception:
+ results.append(None)
+ print(future.result())
+ return results
+
+if __name__ == "__main__":
+
+ results = get_list_of_configlets()
+ for future in results:
+ for configlet in future['data']:
+ with open(configlet['name'],'w') as f:
+ f.write(configlet['config'])
diff --git a/docs/labs/lab03-configlet-management/common.cfg b/docs/labs/lab03-configlet-management/common.cfg
new file mode 100644
index 0000000..7620ae1
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/common.cfg
@@ -0,0 +1,6 @@
+!
+ip name-server vrf management 1.1.1.1
+ip name-server vrf management 8.8.8.8
+!
+ntp server vrf management time.google.com
+!
diff --git a/docs/labs/lab03-configlet-management/config_search.py b/docs/labs/lab03-configlet-management/config_search.py
new file mode 100644
index 0000000..4c3ad27
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/config_search.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2022 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+def main():
+
+ print('Retrieving configlets ...')
+
+ inventory = clnt.api.get_inventory()
+ data = clnt.api.get_configlets_and_mappers()['data']
+ print(data)
+
+ print('Number of configlets:', len(data['configlets']))
+
+ searchAgain = True
+ while searchAgain:
+ try:
+ search = input( "\nEnter Config Line: " )
+ print(f"\n\n\'{search}\' has been found in following configlets:\n\n")
+ print(f"{'Hostname':<30}{'Serial number':<50}{'MAC address':<30}{'Configlets':<40}")
+ print("=" * 150)
+ for i in inventory:
+ device = i['hostname']
+ device_sn = i['serialNumber']
+ device_mac = i['systemMacAddress']
+ configlet_list = []
+ for c in data['configlets']:
+ for g in data['generatedConfigletMappers']:
+ if device_mac == g['netElementId'] and c['key'] == g['configletBuilderId'] and search in c['config']:
+ configlet_list.append(c['name'])
+ for k in data['configletMappers']:
+ if device_mac == k['objectId'] and c['key'] == k['configletId'] and search in c['config']:
+ configlet_list.append(c['name'])
+ configlet_list_final = ",".join(configlet_list)
+ if len(configlet_list) > 0:
+ print(f"{device:<30}{device_sn:<50}{device_mac:<30}{configlet_list_final:<30}")
+
+ except KeyboardInterrupt:
+ print('\nExiting... \n')
+ return
+
+if __name__ == '__main__':
+ main()
+
diff --git a/docs/labs/lab03-configlet-management/configlet_list.txt b/docs/labs/lab03-configlet-management/configlet_list.txt
new file mode 100644
index 0000000..645252c
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/configlet_list.txt
@@ -0,0 +1,6 @@
+tp-avd_tp-avd-leaf1
+tp-avd_tp-avd-leaf2
+tp-avd_tp-avd-leaf3
+tp-avd_tp-avd-leaf4
+tp-avd_tp-avd-spine1
+tp-avd_tp-avd-spine2 \ No newline at end of file
diff --git a/docs/labs/lab03-configlet-management/create_configlet.py b/docs/labs/lab03-configlet-management/create_configlet.py
new file mode 100644
index 0000000..8579fbf
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/create_configlet.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletName = "cvprac_example"
+
+configlet = """!
+interface Ethernet10
+ description test
+ ip address 10.144.144.1/24
+!
+"""
+
+clnt.api.add_configlet(configletName,configlet)
diff --git a/docs/labs/lab03-configlet-management/create_configlet_from_file.py b/docs/labs/lab03-configlet-management/create_configlet_from_file.py
new file mode 100644
index 0000000..d6d07c6
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/create_configlet_from_file.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletName = "cvprac_example2"
+
+with open("common.cfg") as file:
+ configlet = file.read()
+clnt.api.add_configlet(configletName, configlet)
diff --git a/docs/labs/lab03-configlet-management/get_applied_netelements.py b/docs/labs/lab03-configlet-management/get_applied_netelements.py
new file mode 100644
index 0000000..71e020e
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/get_applied_netelements.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2023 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+import argparse
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username", password="password")
+
+parser = argparse.ArgumentParser(
+ description='Get the list of devices and containers a configlet is attached to')
+parser.add_argument('-c', '--configlet', required=True, help='The name of the configlet')
+args = parser.parse_args()
+
+configlet_name = args.configlet
+devices = clnt.api.get_applied_devices(configlet_name)
+
+containers = clnt.api.get_applied_containers(configlet_name)
+print(f"Total number of devices {configlet_name} is attached to: {devices['total']}\n")
+print(f"Total number of containers {configlet_name} is attached to: {containers['total']}\n")
+col1 = "Device FQDN/hostname"
+col2 = "IP Address"
+print(f"{col1:<40}{col2:<40}")
+print("="*80)
+for device in devices['data']:
+ print(f"{device['hostName']:<40}{device['ipAddress']}")
+
+print("\nList of containers:\n")
+for container in containers['data']:
+ print(container['containerName'])
diff --git a/docs/labs/lab03-configlet-management/get_configlets.py b/docs/labs/lab03-configlet-management/get_configlets.py
new file mode 100644
index 0000000..fc3dc2d
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/get_configlets.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Get list of configlets in parallel
+
+from cvprac.cvp_client import CvpClient
+import ssl
+from concurrent.futures import ThreadPoolExecutor
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+import time
+from functools import wraps
+
+def get_list_of_configlets(configlets):
+ """
+ Create a thread pool and download specified urls
+ """
+
+ futures_list = []
+ results = []
+
+ with ThreadPoolExecutor(max_workers=40) as executor:
+ for configlet in configlets:
+ futures = executor.submit(clnt.api.get_configlet_by_name, configlet)
+ futures_list.append(futures)
+
+ for future in futures_list:
+ try:
+ result = future.result(timeout=60)
+ results.append(result)
+ except Exception:
+ results.append(None)
+ return results
+
+if __name__ == "__main__":
+ # Example with pre-defined list
+ configlets = ["tp-avd_tp-avd-leaf1","tp-avd_tp-avd-leaf2","tp-avd_tp-avd-leaf3","tp-avd_tp-avd-leaf4"]
+
+ # Example with loading list of configlets from a file
+ # with open("configlet_list.txt") as f:
+ # configlets = f.read().splitlines()
+
+ results = get_list_of_configlets(configlets)
+ for result in results:
+ print(result)
diff --git a/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py b/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py
new file mode 100644
index 0000000..3a5bb6f
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/reorder_configlet_on_device.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+configletNames = ['tp-avd_tp-avd-leaf1','vlan144','api_models']
+
+device_name = "tp-avd-leaf1"
+device = clnt.api.get_device_by_name(device_name)
+
+configlets = []
+
+for name in configletNames:
+ configlets.append(clnt.api.get_configlet_by_name(name))
+
+# Apply configlets in the order specified in the list
+clnt.api.apply_configlets_to_device("", device, configlets, reorder_configlets=True)
diff --git a/docs/labs/lab03-configlet-management/update_configlet.py b/docs/labs/lab03-configlet-management/update_configlet.py
new file mode 100644
index 0000000..b3eb9c0
--- /dev/null
+++ b/docs/labs/lab03-configlet-management/update_configlet.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+# Modify existing configlet
+
+configletName = "cvprac_example"
+
+configlet = """!
+interface Ethernet10
+ description DUB_R04
+ ip address 10.144.144.2/24
+!
+"""
+
+configletID = clnt.api.get_configlet_by_name(configletName)['key']
+
+clnt.api.update_configlet( configlet, configletID, configletName)
diff --git a/docs/labs/lab04-container-management/add_image_to_container.py b/docs/labs/lab04-container-management/add_image_to_container.py
new file mode 100644
index 0000000..99fc05e
--- /dev/null
+++ b/docs/labs/lab04-container-management/add_image_to_container.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+container_name = "TP_FABRIC"
+container = clnt.api.get_container_by_name(container_name)
+
+clnt.api.apply_image_to_container(image, container)
diff --git a/docs/labs/lab04-container-management/assign_configlet_to_container.py b/docs/labs/lab04-container-management/assign_configlet_to_container.py
new file mode 100644
index 0000000..a6e8828
--- /dev/null
+++ b/docs/labs/lab04-container-management/assign_configlet_to_container.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username="username",password="password")
+
+container_name = "TP_LEAFS"
+
+configletName = 'cvprac_example2'
+
+container = clnt.api.get_container_by_name(container_name)
+
+configlet = clnt.api.get_configlet_by_name(configletName)
+
+clnt.api.apply_configlets_to_container("", container, [configlet])
diff --git a/docs/labs/lab04-container-management/create_container.py b/docs/labs/lab04-container-management/create_container.py
new file mode 100644
index 0000000..6e5b8dc
--- /dev/null
+++ b/docs/labs/lab04-container-management/create_container.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+# Get parent container information
+parent = clnt.api.get_container_by_name("ContainerA")
+
+# Create new container ContainerB under ContainerA
+
+clnt.api.add_container("ContainerB",parent["name"],parent["key"])
diff --git a/docs/labs/lab04-container-management/remove_image_from_container.py b/docs/labs/lab04-container-management/remove_image_from_container.py
new file mode 100644
index 0000000..8a48be8
--- /dev/null
+++ b/docs/labs/lab04-container-management/remove_image_from_container.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+container_name = "TP_FABRIC"
+container = clnt.api.get_container_by_name(container_name)
+
+clnt.api.remove_image_from_container(image, container)
diff --git a/docs/labs/lab04-container-management/rename_container.py b/docs/labs/lab04-container-management/rename_container.py
new file mode 100644
index 0000000..74f4562
--- /dev/null
+++ b/docs/labs/lab04-container-management/rename_container.py
@@ -0,0 +1,32 @@
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+oldName = "test"
+newName = "test121"
+
+container_id = clnt.api.get_container_by_name(oldName)['key']
+
+data = {"data":[{"info": "Container {} renamed from {}".format(newName, oldName),
+ "infoPreview": "Container {} renamed from {}".format(newName, oldName),
+ "action": "update",
+ "nodeType": "container",
+ "nodeId": container_id,
+ "toId":"",
+ "fromId":"",
+ "nodeName": newName,
+ "fromName": "",
+ "toName": "",
+ "toIdType": "container",
+ "oldNodeName": oldName
+ }
+ ]
+ }
+
+clnt.api._add_temp_action(data)
+clnt.api._save_topology_v2([])
diff --git a/docs/labs/lab05-device-management/add_image_to_devices.py b/docs/labs/lab05-device-management/add_image_to_devices.py
new file mode 100644
index 0000000..9150a3c
--- /dev/null
+++ b/docs/labs/lab05-device-management/add_image_to_devices.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+device_name = "tp-avd-leaf2"
+device = clnt.api.get_device_by_name(device_name)
+
+clnt.api.apply_image_to_device(image, device)
diff --git a/docs/labs/lab05-device-management/add_image_wo_tempaction.py b/docs/labs/lab05-device-management/add_image_wo_tempaction.py
new file mode 100644
index 0000000..1100a5a
--- /dev/null
+++ b/docs/labs/lab05-device-management/add_image_wo_tempaction.py
@@ -0,0 +1,100 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import json
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+device_name = "tp-avd-leaf2"
+device = clnt.api.get_device_by_name(device_name)
+
+def apply_image_to_element_no_temp(image, element, name, id_type, create_task=True):
+ ''' Apply an image bundle to a device or container
+ A copy of the appl_image_to_element() function without creating a tempAction.
+ Useful in situations where we need to call saveTopology on a per tempAction basis,
+ which is only possible if the addTempAction function is not used and the data
+ that we would've passed in the addTempAction call is passed in the
+ saveTopology call.
+ Args:
+ image (dict): The image info.
+ element (dict): Info about the element to apply an image to. Dict
+ can contain device info or container info.
+ name (str): Name of the element the image is being applied to.
+ id_type (str): - Id type of the element the image is being applied to
+ - can be 'netelement' or 'container'
+ create_task (bool): Determines whether or not to execute a save
+ and create the tasks (if any)
+ Returns:
+ response (list): A list that contains the tempAction data
+ Ex: [{'NetworkRollbackTask': False,
+ 'taskJson': '[{
+ "info": "Apply image: vEOS-4.26.0.1F to netelement tp-avd-leaf2",
+ "infoPreview": "Apply image: vEOS-4.26.0.1F to netelement tp-avd-leaf2",
+ "note": "",
+ "action": "associate", "nodeType":
+ "imagebundle",
+ "nodeId": "imagebundle_1622072231719691917",
+ "toId": "50:08:00:b1:5b:0b",
+ "toIdType": "netelement",
+ "fromId": "",
+ "nodeName": "vEOS-4.26.0.1F",
+ "fromName": "", "
+ toName": "tp-avd-leaf2",
+ "childTasks": [],
+ "parentTask": ""}]'}]
+ '''
+
+ print('Attempt to apply %s to %s %s' % (image['name'],
+ id_type, name))
+ info = 'Apply image: %s to %s %s' % (image['name'], id_type, name)
+ node_id = ''
+ if 'imageBundleKeys' in image:
+ if image['imageBundleKeys']:
+ node_id = image['imageBundleKeys'][0]
+ print('Provided image is an image object.'
+ ' Using first value from imageBundleKeys - %s'
+ % node_id)
+ if 'id' in image:
+ node_id = image['id']
+ print('Provided image is an image bundle object.'
+ ' Found v1 API id field - %s' % node_id)
+ elif 'key' in image:
+ node_id = image['key']
+ print('Provided image is an image bundle object.'
+ ' Found v2 API key field - %s' % node_id)
+ data = [
+ {
+ "NetworkRollbackTask": False,
+ "taskJson": json.dumps([{'info': info,
+ 'infoPreview': info,
+ 'note': '',
+ 'action': 'associate',
+ 'nodeType': 'imagebundle',
+ 'nodeId': node_id,
+ 'toId': element['key'],
+ 'toIdType': id_type,
+ 'fromId': '',
+ 'nodeName': image['name'],
+ 'fromName': '',
+ 'toName': name,
+ 'childTasks': [],
+ 'parentTask': ''}])
+ }
+ ]
+ return data
+
+create_task = False
+tempAction = apply_image_to_element_no_temp(image, device, device['fqdn'], 'netelement', create_task)
+
+clnt.api._save_topology_v2(tempAction) \ No newline at end of file
diff --git a/docs/labs/lab05-device-management/remove_image_from_device.py b/docs/labs/lab05-device-management/remove_image_from_device.py
new file mode 100644
index 0000000..5e9910c
--- /dev/null
+++ b/docs/labs/lab05-device-management/remove_image_from_device.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+image_name = "vEOS-4.26.0.1F"
+image = clnt.api.get_image_bundle_by_name(image_name)
+
+device_name = "tp-avd-leaf2"
+device = clnt.api.get_device_by_name(device_name)
+
+clnt.api.remove_image_from_device(image, device)
diff --git a/docs/labs/lab05-device-management/set_mgmt_ip.py b/docs/labs/lab05-device-management/set_mgmt_ip.py
new file mode 100644
index 0000000..f0c33f1
--- /dev/null
+++ b/docs/labs/lab05-device-management/set_mgmt_ip.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+
+data = {"data":[{"info":"Device IP Address Changed",
+ "infoPreview":"<b> Device IP Address Changed to 10.83.13.214</b>",
+ "action":"associate",
+ "nodeType":"ipaddress",
+ "nodeId":"",
+ "toId":"50:08:00:a7:ca:c3", # MAC of the device
+ "fromId":"",
+ "nodeName":"",
+ "fromName":"",
+ "toName":"tp-avd-leaf1", # hostname
+ "toIdType":"netelement",
+ "nodeIpAddress":"10.83.13.219", # the temporary management IP Address
+ "nodeTargetIpAddress":"10.83.13.214" # the final management IP address
+ }
+ ]
+ }
+clnt.api._add_temp_action(data)
+
+clnt.api._save_topology_v2([])
diff --git a/docs/labs/lab06-provisioning/atd_e2e_provisioning_workflow.py b/docs/labs/lab06-provisioning/atd_e2e_provisioning_workflow.py
new file mode 100644
index 0000000..8d4445f
--- /dev/null
+++ b/docs/labs/lab06-provisioning/atd_e2e_provisioning_workflow.py
@@ -0,0 +1,120 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+# This script is an example on provisioning registered devices in CloudVision that is based on
+# Arista Test Drive (ATD) and similar to what the ansible playbooks do in
+# https://github.com/arista-netdevops-community/atd-avd.
+# It does the following:
+# - creates and uploads configlets,
+# - creates the container hierarchy in Network Provisiong
+# - moves the devices to their target containers
+# - assigns the configlets to the devices
+# - creates a change control from the genereated tasks
+# - approves and executes the change control
+
+import uuid
+import time
+import ssl
+from datetime import datetime
+from cvprac.cvp_client import CvpClient
+ssl._create_default_https_context = ssl._create_unverified_context
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+# Create container topology
+container_name = "DC1_LEAFS"
+container_topology = [{"containerName": "ATD_FABRIC", "parentContainerName": 'Tenant'},
+ {"containerName": "ATD_LEAFS", "parentContainerName": 'ATD_FABRIC'},
+ {"containerName": "pod1", "parentContainerName": 'ATD_LEAFS'},
+ {"containerName": "pod2", "parentContainerName": 'ATD_LEAFS'},
+ {"containerName": "ATD_SERVERS", "parentContainerName": 'ATD_FABRIC'},
+ {"containerName": "ATD_SPINES", "parentContainerName": 'ATD_FABRIC'},
+ {"containerName": "ATD_TENANT_NETWORKS", "parentContainerName": 'ATD_FABRIC'}]
+for container in container_topology:
+ try:
+ container_name = container['containerName']
+ # Get parent container information
+ parent = clnt.api.get_container_by_name(container['parentContainerName'])
+ print(f'Creating container {container_name}\n')
+ clnt.api.add_container(container_name,parent["name"],parent["key"])
+ except Exception as e:
+ if "Data already exists in Database" in str(e):
+ print ("Container already exists, continuing...")
+
+# Create device mappers
+devices = [{'deviceName': "leaf1",
+ 'configlets': ["BaseIPv4_Leaf1", "AVD_leaf1"],
+ "parentContainerName": "pod1"},
+ {'deviceName': "leaf2",
+ 'configlets': ["BaseIPv4_Leaf2", "AVD_leaf2"],
+ "parentContainerName": "pod1"},
+ {'deviceName': "leaf3",
+ 'configlets': ["BaseIPv4_Leaf3", "AVD_leaf3"],
+ "parentContainerName": "pod2"},
+ {'deviceName': "leaf4",
+ 'configlets': ["BaseIPv4_Leaf4", "AVD_leaf4"],
+ "parentContainerName": "pod2"},
+ {'deviceName': "spine1",
+ 'configlets': ["BaseIPv4_Spine1", "AVD_spine1"],
+ "parentContainerName": "ATD_SPINES"},
+ {'deviceName': "spine2",
+ 'configlets': ["BaseIPv4_Spine2", "AVD_spine2"],
+ "parentContainerName": "ATD_SPINES"}]
+
+task_list = []
+for device in devices:
+ # Load the AVD configlets from file
+ with open("./configlets/AVD_" + device['deviceName'] + ".cfg", "r") as file:
+ configlet_file = file.read()
+ avd_configlet_name = device['configlets'][1]
+ base_configlet_name = device['configlets'][0] # preloaded configlet in an ATD environment
+ container_name = device['parentContainerName']
+ base_configlet = clnt.api.get_configlet_by_name(base_configlet_name)
+ configlets = [base_configlet]
+ # Update the AVD configlets if they exist, otherwise upload them from the configlets folder
+ print (f"Creating configlet {avd_configlet_name} for {device['deviceName']}\n")
+ try:
+ configlet = clnt.api.get_configlet_by_name(avd_configlet_name)
+ clnt.api.update_configlet(configlet_file, configlet['key'], avd_configlet_name)
+ configlets.append(configlet)
+ except:
+ clnt.api.add_configlet(avd_configlet_name, configlet_file)
+ configlet = clnt.api.get_configlet_by_name(avd_configlet_name)
+ configlets.append(configlet)
+ # Get device data
+ device_data = clnt.api.get_device_by_name(device['deviceName'] + ".atd.lab")
+ # Get the parent container data for the device
+ container = clnt.api.get_container_by_name(container_name)
+ device_name = device['deviceName']
+ print(f"Moving device {device_name} to container {container_name}\n")
+ # The move action will create the task first, however if the devices are already in the target
+ # container, for instance if the script was run multiple times than the move action will
+ # not generate a task anymore, therefore it's better to create the task list from the
+ # Update Config action which will reuse the Move Device action's task if one exists,
+ # otherwise will create a new one.
+ move = clnt.api.move_device_to_container("python", device_data, container)
+ apply_configlets = clnt.api.apply_configlets_to_device("", device_data, configlets)
+ task_list = task_list + apply_configlets['data']['taskIds']
+
+print(f"Generated task IDs are: {task_list}\n")
+
+# Generate unique ID for the change control
+cc_id = str(uuid.uuid4())
+cc_name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+
+print("Creating Change control with the list of tasks")
+clnt.api.change_control_create_for_tasks(cc_id, cc_name, task_list, series=False)
+
+print("Approving Change Control")
+# adding a few seconds sleep to avoid small time diff between the local system and CVP
+time.sleep(2)
+approve_note = "Approving CC via cvprac"
+clnt.api.change_control_approve(cc_id, notes=approve_note)
+
+# Start the change control
+print("Executing Change Control...")
+start_note = "Start the CC via cvprac"
+clnt.api.change_control_start(cc_id, notes=start_note)
diff --git a/docs/labs/lab06-provisioning/auto_reconcile_on_rc_change.py b/docs/labs/lab06-provisioning/auto_reconcile_on_rc_change.py
new file mode 100644
index 0000000..cff820d
--- /dev/null
+++ b/docs/labs/lab06-provisioning/auto_reconcile_on_rc_change.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2022 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+# This script can be run as a cronjob to periodically reconcile all devices
+# that are out of configuration compliance in environments where the running-config
+# is still modified via the CLI often.
+from cvprac.cvp_client import CvpClient
+import ssl
+from datetime import datetime
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+clnt = CvpClient()
+clnt.set_log_level(log_level='WARNING')
+
+# Reading the service account token from a file
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+inventory = clnt.api.get_inventory()
+
+compliance = {"0001": "Config is out of sync",
+ "0003": "Config & image out of sync",
+ "0004": "Config, Image and Device time are in sync",
+ "0005": "Device is not reachable",
+ "0008": "Config, Image and Extensions are out of sync",
+ "0009": "Config and Extensions are out of sync",
+ "0012": "Config, Image, Extension and Device time are out of sync",
+ "0013": "Config, Image and Device time are out of sync",
+ "0014": "Config, Extensions and Device time are out of sync",
+ "0016": "Config and Device time are out of sync"
+ }
+
+non_compliants = []
+taskIds = []
+for device in inventory:
+ if device['complianceCode'] in compliance.keys():
+ # create a list of non-compliant devices for reporting purposes
+ non_compliants.append(device['hostname'])
+ dev_mac = device['systemMacAddress']
+ # check if device already has reconciled config and save the key if it does
+ try:
+ configlets = clnt.api.get_configlets_by_device_id(dev_mac)
+ for configlet in configlets:
+ if configlet['reconciled'] == True:
+ configlet_key = configlet['key']
+ break
+ else:
+ configlet_key = ""
+ rc = clnt.api.get_device_configuration(dev_mac)
+ name = 'RECONCILE_' + device['serialNumber']
+ update = clnt.api.update_reconcile_configlet(dev_mac, rc, configlet_key, name, True)
+ # if the device had no reconciled config, it means we need to append the reconciled
+ # configlet to the list of applied configlets on the device
+ if configlet_key == "":
+ addcfg = clnt.api.apply_configlets_to_device("auto-reconciling",device,[update['data']])
+ clnt.api.cancel_task(addcfg['data']['taskIds'][0])
+ except Exception as e:
+ continue
+print(f"The non compliant devices were: {str(non_compliants)}")
diff --git a/docs/labs/lab06-provisioning/change_control_custom_rapi.py b/docs/labs/lab06-provisioning/change_control_custom_rapi.py
new file mode 100644
index 0000000..0290af7
--- /dev/null
+++ b/docs/labs/lab06-provisioning/change_control_custom_rapi.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# NOTE: The following example is using the new Change Control Resource APIs supported in 2021.2.0 or newer and in CVaaS.
+# For CVaaS service-account token based auth has to be used.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+import uuid
+from datetime import datetime
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+
+cc_id = str(uuid.uuid4())
+name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+
+# Create custom stage hierarchy
+# The below example would result in the following hierarchy:
+# root (series)
+# |- stages 1-2 (series)
+# | |- stage 1ab (parallel)
+# | | |- stage 1a
+# | | |- stage 1b
+# | |- stage 2
+# |- stage 3
+data = {'key': {
+ 'id': cc_id
+ },
+ 'change': {
+ 'name': cc_id,
+ 'notes': 'cvprac CC',
+ 'rootStageId': 'root',
+ 'stages': {'values': {'root': {'name': 'root',
+ 'rows': {'values': [{'values': ['1-2']},
+ {'values': ['3']}]
+ }
+ },
+ '1-2': {'name': 'stages 1-2',
+ 'rows': {'values': [{'values': ['1ab']},
+ {'values': ['2']}]}},
+ '1ab': {'name': 'stage 1ab',
+ 'rows': {'values': [{'values': ['1a','1b']}]
+ }
+ },
+ '1a': {'action': {'args': {'values': {'TaskID': '1242'}},
+ 'name': 'task',
+ 'timeout': 3000},
+ 'name': 'stage 1a'},
+ '1b': {'action': {'args': {'values': {'TaskID': '1243'}},
+ 'name': 'task',
+ 'timeout': 3000},
+ 'name': 'stage 1b'},
+ '2': {'action': {'args': {'values': {'TaskID': '1240'}},
+ 'name': 'task',
+ 'timeout': 3000},
+ 'name': 'stage 2'},
+ '3': {'action': {'args': {'values': {'TaskID': '1241'}},
+ 'name': 'task',
+ 'timeout': 3000},
+ 'name': 'stage 3'},
+ }
+ }
+ }
+ }
+# Create change control from custom stage hierarchy data
+clnt.api.change_control_create_with_custom_stages(data)
+
+# Approve the change control
+approval_note = "Approve CC via cvprac" # notes are optional
+clnt.api.change_control_approve(cc_id, notes=approval_note)
+
+# Start the change control
+start_note = "Starting CC via cvprac" # notes are optional
+clnt.api.change_control_start(cc_id, notes=start_note)
diff --git a/docs/labs/lab06-provisioning/change_control_workflow.py b/docs/labs/lab06-provisioning/change_control_workflow.py
new file mode 100644
index 0000000..c374edf
--- /dev/null
+++ b/docs/labs/lab06-provisioning/change_control_workflow.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from datetime import datetime
+
+# Note API token auth method is not yet supported with Change Controls
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+ccid = 'cvprac0904211418'
+name = "cvprac CC test"
+tlist = ['1021','1020','1019','1018']
+
+### Create Change control with the list of tasks
+clnt.api.create_change_control_v3(ccid, name, tlist)
+
+### Approve CC
+clnt.api.approve_change_control('cvprac0904211418', timestamp=datetime.utcnow().isoformat() + 'Z')
+
+### Execute CC
+clnt.api.execute_change_controls(['cvprac0904211418'])
diff --git a/docs/labs/lab06-provisioning/change_control_workflow_rapi.py b/docs/labs/lab06-provisioning/change_control_workflow_rapi.py
new file mode 100644
index 0000000..299d16a
--- /dev/null
+++ b/docs/labs/lab06-provisioning/change_control_workflow_rapi.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# NOTE: The following example is using the new Change Control Resource APIs supported in 2021.2.0 or newer and in CVaaS.
+# For CVaaS service-account token based auth has to be used.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+import uuid
+from datetime import datetime
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+# Generate change control id and change control name
+cc_id = str(uuid.uuid4())
+name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+
+# Select the tasks and create a CC where all tasks will be run in parallel
+tasks = ["1249","1250","1251","1252"]
+clnt.api.change_control_create_for_tasks(cc_id, name, tasks, series=False)
+
+# Approve the change control
+approve_note = "Approving CC via cvprac"
+clnt.api.change_control_approve(cc_id, notes=approve_note)
+
+# # Schedule the change control
+# # Executing scheduled CCs might only work post 2021.3.0+
+# schedule_note = "Scheduling CC via cvprac"
+# schedule_time = "2021-12-23T03:17:00Z"
+# clnt.api.change_control_schedule(cc_id,schedule_time,notes=schedule_note)
+
+# Start the change control
+start_note = "Start the CC via cvprac"
+clnt.api.change_control_start(cc_id, notes=start_note) \ No newline at end of file
diff --git a/docs/labs/lab06-provisioning/configlets/AVD_leaf1.cfg b/docs/labs/lab06-provisioning/configlets/AVD_leaf1.cfg
new file mode 100644
index 0000000..1339d6f
--- /dev/null
+++ b/docs/labs/lab06-provisioning/configlets/AVD_leaf1.cfg
@@ -0,0 +1,255 @@
+!RANCID-CONTENT-TYPE: arista
+!
+vlan internal order ascending range 1006 1199
+!
+transceiver qsfp default-mode 4x10G
+!
+service routing protocols model multi-agent
+!
+hostname leaf1
+ip name-server vrf default 8.8.8.8
+ip name-server vrf default 192.168.2.1
+dns domain atd.lab
+!
+spanning-tree mode mstp
+no spanning-tree vlan-id 4093-4094
+spanning-tree mst 0 priority 16384
+!
+no enable password
+no aaa root
+!
+vlan 110
+ name Tenant_A_OP_Zone_1
+!
+vlan 160
+ name Tenant_A_VMOTION
+!
+vlan 3009
+ name MLAG_iBGP_Tenant_A_OP_Zone
+ trunk group LEAF_PEER_L3
+!
+vlan 4093
+ name LEAF_PEER_L3
+ trunk group LEAF_PEER_L3
+!
+vlan 4094
+ name MLAG_PEER
+ trunk group MLAG
+!
+vrf instance Tenant_A_OP_Zone
+!
+interface Port-Channel1
+ description MLAG_PEER_leaf2_Po1
+ no shutdown
+ switchport
+ switchport trunk allowed vlan 2-4094
+ switchport mode trunk
+ switchport trunk group LEAF_PEER_L3
+ switchport trunk group MLAG
+!
+interface Port-Channel4
+ description host1_PortChannel
+ no shutdown
+ switchport
+ switchport access vlan 110
+ mlag 4
+!
+interface Ethernet1
+ description MLAG_PEER_leaf2_Ethernet1
+ no shutdown
+ channel-group 1 mode active
+!
+interface Ethernet2
+ description P2P_LINK_TO_SPINE1_Ethernet2
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.1/31
+!
+interface Ethernet3
+ description P2P_LINK_TO_SPINE2_Ethernet2
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.3/31
+!
+interface Ethernet4
+ description host1_Eth1
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet5
+ description host1_Eth2
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet6
+ description MLAG_PEER_leaf2_Ethernet6
+ no shutdown
+ channel-group 1 mode active
+!
+interface Loopback0
+ description EVPN_Overlay_Peering
+ no shutdown
+ ip address 192.0.255.3/32
+!
+interface Loopback1
+ description VTEP_VXLAN_Tunnel_Source
+ no shutdown
+ ip address 192.0.254.3/32
+!
+interface Loopback100
+ description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.1.3/32
+!
+interface Management1
+ description oob_management
+ no shutdown
+ ip address 192.168.0.12/24
+!
+interface Vlan110
+ description Tenant_A_OP_Zone_1
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address virtual 10.1.10.1/24
+!
+interface Vlan3009
+ description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone
+ no shutdown
+ mtu 1500
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.251.0/31
+!
+interface Vlan4093
+ description MLAG_PEER_L3_PEERING
+ no shutdown
+ mtu 1500
+ ip address 10.255.251.0/31
+!
+interface Vlan4094
+ description MLAG_PEER
+ no shutdown
+ mtu 1500
+ no autostate
+ ip address 10.255.252.0/31
+!
+interface Vxlan1
+ description leaf1_VTEP
+ vxlan source-interface Loopback1
+ vxlan virtual-router encapsulation mac-address mlag-system-id
+ vxlan udp-port 4789
+ vxlan vlan 110 vni 10110
+ vxlan vlan 160 vni 55160
+ vxlan vrf Tenant_A_OP_Zone vni 10
+!
+ip virtual-router mac-address 00:1c:73:00:dc:01
+!
+ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.3
+!
+ip routing
+ip routing vrf Tenant_A_OP_Zone
+!
+ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+ seq 10 permit 192.0.255.0/24 eq 32
+ seq 20 permit 192.0.254.0/24 eq 32
+!
+mlag configuration
+ domain-id pod1
+ local-interface Vlan4094
+ peer-address 10.255.252.1
+ peer-link Port-Channel1
+ reload-delay mlag 300
+ reload-delay non-mlag 330
+!
+ip route 0.0.0.0/0 192.168.0.1
+!
+route-map RM-CONN-2-BGP permit 10
+ match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+!
+route-map RM-MLAG-PEER-IN permit 10
+ description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing
+ set origin incomplete
+!
+router bfd
+ multihop interval 1200 min-rx 1200 multiplier 3
+!
+router bgp 65101
+ router-id 192.0.255.3
+ no bgp default ipv4-unicast
+ distance bgp 20 200 200
+ graceful-restart restart-time 300
+ graceful-restart
+ maximum-paths 4 ecmp 4
+ neighbor EVPN-OVERLAY-PEERS peer group
+ neighbor EVPN-OVERLAY-PEERS update-source Loopback0
+ neighbor EVPN-OVERLAY-PEERS bfd
+ neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3
+ neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA==
+ neighbor EVPN-OVERLAY-PEERS send-community
+ neighbor EVPN-OVERLAY-PEERS maximum-routes 0
+ neighbor IPv4-UNDERLAY-PEERS peer group
+ neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w==
+ neighbor IPv4-UNDERLAY-PEERS send-community
+ neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER peer group
+ neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65101
+ neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self
+ neighbor MLAG-IPv4-UNDERLAY-PEER description leaf2
+ neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ==
+ neighbor MLAG-IPv4-UNDERLAY-PEER send-community
+ neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in
+ neighbor 10.255.251.1 peer group MLAG-IPv4-UNDERLAY-PEER
+ neighbor 10.255.251.1 description leaf2
+ neighbor 172.30.255.0 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.0 remote-as 65001
+ neighbor 172.30.255.0 description spine1_Ethernet2
+ neighbor 172.30.255.2 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.2 remote-as 65001
+ neighbor 172.30.255.2 description spine2_Ethernet2
+ neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.1 remote-as 65001
+ neighbor 192.0.255.1 description spine1
+ neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.2 remote-as 65001
+ neighbor 192.0.255.2 description spine2
+ redistribute connected route-map RM-CONN-2-BGP
+ !
+ vlan-aware-bundle Tenant_A_OP_Zone
+ rd 192.0.255.3:10
+ route-target both 10:10
+ redistribute learned
+ vlan 110
+ !
+ vlan-aware-bundle Tenant_A_VMOTION
+ rd 192.0.255.3:55160
+ route-target both 55160:55160
+ redistribute learned
+ vlan 160
+ !
+ address-family evpn
+ neighbor EVPN-OVERLAY-PEERS activate
+ !
+ address-family ipv4
+ no neighbor EVPN-OVERLAY-PEERS activate
+ neighbor IPv4-UNDERLAY-PEERS activate
+ neighbor MLAG-IPv4-UNDERLAY-PEER activate
+ !
+ vrf Tenant_A_OP_Zone
+ rd 192.0.255.3:10
+ route-target import evpn 10:10
+ route-target export evpn 10:10
+ router-id 192.0.255.3
+ neighbor 10.255.251.1 peer group MLAG-IPv4-UNDERLAY-PEER
+ redistribute connected
+!
+management api http-commands
+ protocol https
+ no shutdown
+ !
+ vrf default
+ no shutdown
+!
+end
diff --git a/docs/labs/lab06-provisioning/configlets/AVD_leaf2.cfg b/docs/labs/lab06-provisioning/configlets/AVD_leaf2.cfg
new file mode 100644
index 0000000..7305516
--- /dev/null
+++ b/docs/labs/lab06-provisioning/configlets/AVD_leaf2.cfg
@@ -0,0 +1,255 @@
+!RANCID-CONTENT-TYPE: arista
+!
+vlan internal order ascending range 1006 1199
+!
+transceiver qsfp default-mode 4x10G
+!
+service routing protocols model multi-agent
+!
+hostname leaf2
+ip name-server vrf default 8.8.8.8
+ip name-server vrf default 192.168.2.1
+dns domain atd.lab
+!
+spanning-tree mode mstp
+no spanning-tree vlan-id 4093-4094
+spanning-tree mst 0 priority 16384
+!
+no enable password
+no aaa root
+!
+vlan 110
+ name Tenant_A_OP_Zone_1
+!
+vlan 160
+ name Tenant_A_VMOTION
+!
+vlan 3009
+ name MLAG_iBGP_Tenant_A_OP_Zone
+ trunk group LEAF_PEER_L3
+!
+vlan 4093
+ name LEAF_PEER_L3
+ trunk group LEAF_PEER_L3
+!
+vlan 4094
+ name MLAG_PEER
+ trunk group MLAG
+!
+vrf instance Tenant_A_OP_Zone
+!
+interface Port-Channel1
+ description MLAG_PEER_leaf1_Po1
+ no shutdown
+ switchport
+ switchport trunk allowed vlan 2-4094
+ switchport mode trunk
+ switchport trunk group LEAF_PEER_L3
+ switchport trunk group MLAG
+!
+interface Port-Channel4
+ description host1_PortChannel
+ no shutdown
+ switchport
+ switchport access vlan 110
+ mlag 4
+!
+interface Ethernet1
+ description MLAG_PEER_leaf1_Ethernet1
+ no shutdown
+ channel-group 1 mode active
+!
+interface Ethernet2
+ description P2P_LINK_TO_SPINE1_Ethernet3
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.5/31
+!
+interface Ethernet3
+ description P2P_LINK_TO_SPINE2_Ethernet3
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.7/31
+!
+interface Ethernet4
+ description host1_Eth3
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet5
+ description host1_Eth4
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet6
+ description MLAG_PEER_leaf1_Ethernet6
+ no shutdown
+ channel-group 1 mode active
+!
+interface Loopback0
+ description EVPN_Overlay_Peering
+ no shutdown
+ ip address 192.0.255.4/32
+!
+interface Loopback1
+ description VTEP_VXLAN_Tunnel_Source
+ no shutdown
+ ip address 192.0.254.3/32
+!
+interface Loopback100
+ description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.1.4/32
+!
+interface Management1
+ description oob_management
+ no shutdown
+ ip address 192.168.0.13/24
+!
+interface Vlan110
+ description Tenant_A_OP_Zone_1
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address virtual 10.1.10.1/24
+!
+interface Vlan3009
+ description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone
+ no shutdown
+ mtu 1500
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.251.1/31
+!
+interface Vlan4093
+ description MLAG_PEER_L3_PEERING
+ no shutdown
+ mtu 1500
+ ip address 10.255.251.1/31
+!
+interface Vlan4094
+ description MLAG_PEER
+ no shutdown
+ mtu 1500
+ no autostate
+ ip address 10.255.252.1/31
+!
+interface Vxlan1
+ description leaf2_VTEP
+ vxlan source-interface Loopback1
+ vxlan virtual-router encapsulation mac-address mlag-system-id
+ vxlan udp-port 4789
+ vxlan vlan 110 vni 10110
+ vxlan vlan 160 vni 55160
+ vxlan vrf Tenant_A_OP_Zone vni 10
+!
+ip virtual-router mac-address 00:1c:73:00:dc:01
+!
+ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.4
+!
+ip routing
+ip routing vrf Tenant_A_OP_Zone
+!
+ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+ seq 10 permit 192.0.255.0/24 eq 32
+ seq 20 permit 192.0.254.0/24 eq 32
+!
+mlag configuration
+ domain-id pod1
+ local-interface Vlan4094
+ peer-address 10.255.252.0
+ peer-link Port-Channel1
+ reload-delay mlag 300
+ reload-delay non-mlag 330
+!
+ip route 0.0.0.0/0 192.168.0.1
+!
+route-map RM-CONN-2-BGP permit 10
+ match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+!
+route-map RM-MLAG-PEER-IN permit 10
+ description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing
+ set origin incomplete
+!
+router bfd
+ multihop interval 1200 min-rx 1200 multiplier 3
+!
+router bgp 65101
+ router-id 192.0.255.4
+ no bgp default ipv4-unicast
+ distance bgp 20 200 200
+ graceful-restart restart-time 300
+ graceful-restart
+ maximum-paths 4 ecmp 4
+ neighbor EVPN-OVERLAY-PEERS peer group
+ neighbor EVPN-OVERLAY-PEERS update-source Loopback0
+ neighbor EVPN-OVERLAY-PEERS bfd
+ neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3
+ neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA==
+ neighbor EVPN-OVERLAY-PEERS send-community
+ neighbor EVPN-OVERLAY-PEERS maximum-routes 0
+ neighbor IPv4-UNDERLAY-PEERS peer group
+ neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w==
+ neighbor IPv4-UNDERLAY-PEERS send-community
+ neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER peer group
+ neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65101
+ neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self
+ neighbor MLAG-IPv4-UNDERLAY-PEER description leaf1
+ neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ==
+ neighbor MLAG-IPv4-UNDERLAY-PEER send-community
+ neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in
+ neighbor 10.255.251.0 peer group MLAG-IPv4-UNDERLAY-PEER
+ neighbor 10.255.251.0 description leaf1
+ neighbor 172.30.255.4 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.4 remote-as 65001
+ neighbor 172.30.255.4 description spine1_Ethernet3
+ neighbor 172.30.255.6 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.6 remote-as 65001
+ neighbor 172.30.255.6 description spine2_Ethernet3
+ neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.1 remote-as 65001
+ neighbor 192.0.255.1 description spine1
+ neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.2 remote-as 65001
+ neighbor 192.0.255.2 description spine2
+ redistribute connected route-map RM-CONN-2-BGP
+ !
+ vlan-aware-bundle Tenant_A_OP_Zone
+ rd 192.0.255.4:10
+ route-target both 10:10
+ redistribute learned
+ vlan 110
+ !
+ vlan-aware-bundle Tenant_A_VMOTION
+ rd 192.0.255.4:55160
+ route-target both 55160:55160
+ redistribute learned
+ vlan 160
+ !
+ address-family evpn
+ neighbor EVPN-OVERLAY-PEERS activate
+ !
+ address-family ipv4
+ no neighbor EVPN-OVERLAY-PEERS activate
+ neighbor IPv4-UNDERLAY-PEERS activate
+ neighbor MLAG-IPv4-UNDERLAY-PEER activate
+ !
+ vrf Tenant_A_OP_Zone
+ rd 192.0.255.4:10
+ route-target import evpn 10:10
+ route-target export evpn 10:10
+ router-id 192.0.255.4
+ neighbor 10.255.251.0 peer group MLAG-IPv4-UNDERLAY-PEER
+ redistribute connected
+!
+management api http-commands
+ protocol https
+ no shutdown
+ !
+ vrf default
+ no shutdown
+!
+end
diff --git a/docs/labs/lab06-provisioning/configlets/AVD_leaf3.cfg b/docs/labs/lab06-provisioning/configlets/AVD_leaf3.cfg
new file mode 100644
index 0000000..b71d210
--- /dev/null
+++ b/docs/labs/lab06-provisioning/configlets/AVD_leaf3.cfg
@@ -0,0 +1,255 @@
+!RANCID-CONTENT-TYPE: arista
+!
+vlan internal order ascending range 1006 1199
+!
+transceiver qsfp default-mode 4x10G
+!
+service routing protocols model multi-agent
+!
+hostname leaf3
+ip name-server vrf default 8.8.8.8
+ip name-server vrf default 192.168.2.1
+dns domain atd.lab
+!
+spanning-tree mode mstp
+no spanning-tree vlan-id 4093-4094
+spanning-tree mst 0 priority 16384
+!
+no enable password
+no aaa root
+!
+vlan 110
+ name Tenant_A_OP_Zone_1
+!
+vlan 160
+ name Tenant_A_VMOTION
+!
+vlan 3009
+ name MLAG_iBGP_Tenant_A_OP_Zone
+ trunk group LEAF_PEER_L3
+!
+vlan 4093
+ name LEAF_PEER_L3
+ trunk group LEAF_PEER_L3
+!
+vlan 4094
+ name MLAG_PEER
+ trunk group MLAG
+!
+vrf instance Tenant_A_OP_Zone
+!
+interface Port-Channel1
+ description MLAG_PEER_leaf4_Po1
+ no shutdown
+ switchport
+ switchport trunk allowed vlan 2-4094
+ switchport mode trunk
+ switchport trunk group LEAF_PEER_L3
+ switchport trunk group MLAG
+!
+interface Port-Channel4
+ description host2_PortChannel
+ no shutdown
+ switchport
+ switchport access vlan 110
+ mlag 4
+!
+interface Ethernet1
+ description MLAG_PEER_leaf4_Ethernet1
+ no shutdown
+ channel-group 1 mode active
+!
+interface Ethernet2
+ description P2P_LINK_TO_SPINE1_Ethernet4
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.9/31
+!
+interface Ethernet3
+ description P2P_LINK_TO_SPINE2_Ethernet4
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.11/31
+!
+interface Ethernet4
+ description host2_Eth1
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet5
+ description host2_Eth2
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet6
+ description MLAG_PEER_leaf4_Ethernet6
+ no shutdown
+ channel-group 1 mode active
+!
+interface Loopback0
+ description EVPN_Overlay_Peering
+ no shutdown
+ ip address 192.0.255.5/32
+!
+interface Loopback1
+ description VTEP_VXLAN_Tunnel_Source
+ no shutdown
+ ip address 192.0.254.5/32
+!
+interface Loopback100
+ description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.1.5/32
+!
+interface Management1
+ description oob_management
+ no shutdown
+ ip address 192.168.0.14/24
+!
+interface Vlan110
+ description Tenant_A_OP_Zone_1
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address virtual 10.1.10.1/24
+!
+interface Vlan3009
+ description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone
+ no shutdown
+ mtu 1500
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.251.4/31
+!
+interface Vlan4093
+ description MLAG_PEER_L3_PEERING
+ no shutdown
+ mtu 1500
+ ip address 10.255.251.4/31
+!
+interface Vlan4094
+ description MLAG_PEER
+ no shutdown
+ mtu 1500
+ no autostate
+ ip address 10.255.252.4/31
+!
+interface Vxlan1
+ description leaf3_VTEP
+ vxlan source-interface Loopback1
+ vxlan virtual-router encapsulation mac-address mlag-system-id
+ vxlan udp-port 4789
+ vxlan vlan 110 vni 10110
+ vxlan vlan 160 vni 55160
+ vxlan vrf Tenant_A_OP_Zone vni 10
+!
+ip virtual-router mac-address 00:1c:73:00:dc:01
+!
+ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.5
+!
+ip routing
+ip routing vrf Tenant_A_OP_Zone
+!
+ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+ seq 10 permit 192.0.255.0/24 eq 32
+ seq 20 permit 192.0.254.0/24 eq 32
+!
+mlag configuration
+ domain-id pod2
+ local-interface Vlan4094
+ peer-address 10.255.252.5
+ peer-link Port-Channel1
+ reload-delay mlag 300
+ reload-delay non-mlag 330
+!
+ip route 0.0.0.0/0 192.168.0.1
+!
+route-map RM-CONN-2-BGP permit 10
+ match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+!
+route-map RM-MLAG-PEER-IN permit 10
+ description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing
+ set origin incomplete
+!
+router bfd
+ multihop interval 1200 min-rx 1200 multiplier 3
+!
+router bgp 65102
+ router-id 192.0.255.5
+ no bgp default ipv4-unicast
+ distance bgp 20 200 200
+ graceful-restart restart-time 300
+ graceful-restart
+ maximum-paths 4 ecmp 4
+ neighbor EVPN-OVERLAY-PEERS peer group
+ neighbor EVPN-OVERLAY-PEERS update-source Loopback0
+ neighbor EVPN-OVERLAY-PEERS bfd
+ neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3
+ neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA==
+ neighbor EVPN-OVERLAY-PEERS send-community
+ neighbor EVPN-OVERLAY-PEERS maximum-routes 0
+ neighbor IPv4-UNDERLAY-PEERS peer group
+ neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w==
+ neighbor IPv4-UNDERLAY-PEERS send-community
+ neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER peer group
+ neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65102
+ neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self
+ neighbor MLAG-IPv4-UNDERLAY-PEER description leaf4
+ neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ==
+ neighbor MLAG-IPv4-UNDERLAY-PEER send-community
+ neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in
+ neighbor 10.255.251.5 peer group MLAG-IPv4-UNDERLAY-PEER
+ neighbor 10.255.251.5 description leaf4
+ neighbor 172.30.255.8 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.8 remote-as 65001
+ neighbor 172.30.255.8 description spine1_Ethernet4
+ neighbor 172.30.255.10 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.10 remote-as 65001
+ neighbor 172.30.255.10 description spine2_Ethernet4
+ neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.1 remote-as 65001
+ neighbor 192.0.255.1 description spine1
+ neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.2 remote-as 65001
+ neighbor 192.0.255.2 description spine2
+ redistribute connected route-map RM-CONN-2-BGP
+ !
+ vlan-aware-bundle Tenant_A_OP_Zone
+ rd 192.0.255.5:10
+ route-target both 10:10
+ redistribute learned
+ vlan 110
+ !
+ vlan-aware-bundle Tenant_A_VMOTION
+ rd 192.0.255.5:55160
+ route-target both 55160:55160
+ redistribute learned
+ vlan 160
+ !
+ address-family evpn
+ neighbor EVPN-OVERLAY-PEERS activate
+ !
+ address-family ipv4
+ no neighbor EVPN-OVERLAY-PEERS activate
+ neighbor IPv4-UNDERLAY-PEERS activate
+ neighbor MLAG-IPv4-UNDERLAY-PEER activate
+ !
+ vrf Tenant_A_OP_Zone
+ rd 192.0.255.5:10
+ route-target import evpn 10:10
+ route-target export evpn 10:10
+ router-id 192.0.255.5
+ neighbor 10.255.251.5 peer group MLAG-IPv4-UNDERLAY-PEER
+ redistribute connected
+!
+management api http-commands
+ protocol https
+ no shutdown
+ !
+ vrf default
+ no shutdown
+!
+end
diff --git a/docs/labs/lab06-provisioning/configlets/AVD_leaf4.cfg b/docs/labs/lab06-provisioning/configlets/AVD_leaf4.cfg
new file mode 100644
index 0000000..80e201d
--- /dev/null
+++ b/docs/labs/lab06-provisioning/configlets/AVD_leaf4.cfg
@@ -0,0 +1,255 @@
+!RANCID-CONTENT-TYPE: arista
+!
+vlan internal order ascending range 1006 1199
+!
+transceiver qsfp default-mode 4x10G
+!
+service routing protocols model multi-agent
+!
+hostname leaf4
+ip name-server vrf default 8.8.8.8
+ip name-server vrf default 192.168.2.1
+dns domain atd.lab
+!
+spanning-tree mode mstp
+no spanning-tree vlan-id 4093-4094
+spanning-tree mst 0 priority 16384
+!
+no enable password
+no aaa root
+!
+vlan 110
+ name Tenant_A_OP_Zone_1
+!
+vlan 160
+ name Tenant_A_VMOTION
+!
+vlan 3009
+ name MLAG_iBGP_Tenant_A_OP_Zone
+ trunk group LEAF_PEER_L3
+!
+vlan 4093
+ name LEAF_PEER_L3
+ trunk group LEAF_PEER_L3
+!
+vlan 4094
+ name MLAG_PEER
+ trunk group MLAG
+!
+vrf instance Tenant_A_OP_Zone
+!
+interface Port-Channel1
+ description MLAG_PEER_leaf3_Po1
+ no shutdown
+ switchport
+ switchport trunk allowed vlan 2-4094
+ switchport mode trunk
+ switchport trunk group LEAF_PEER_L3
+ switchport trunk group MLAG
+!
+interface Port-Channel4
+ description host2_PortChannel
+ no shutdown
+ switchport
+ switchport access vlan 110
+ mlag 4
+!
+interface Ethernet1
+ description MLAG_PEER_leaf3_Ethernet1
+ no shutdown
+ channel-group 1 mode active
+!
+interface Ethernet2
+ description P2P_LINK_TO_SPINE1_Ethernet5
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.13/31
+!
+interface Ethernet3
+ description P2P_LINK_TO_SPINE2_Ethernet5
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.15/31
+!
+interface Ethernet4
+ description host2_Eth3
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet5
+ description host2_Eth4
+ no shutdown
+ channel-group 4 mode active
+!
+interface Ethernet6
+ description MLAG_PEER_leaf3_Ethernet6
+ no shutdown
+ channel-group 1 mode active
+!
+interface Loopback0
+ description EVPN_Overlay_Peering
+ no shutdown
+ ip address 192.0.255.6/32
+!
+interface Loopback1
+ description VTEP_VXLAN_Tunnel_Source
+ no shutdown
+ ip address 192.0.254.5/32
+!
+interface Loopback100
+ description Tenant_A_OP_Zone_VTEP_DIAGNOSTICS
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.1.6/32
+!
+interface Management1
+ description oob_management
+ no shutdown
+ ip address 192.168.0.15/24
+!
+interface Vlan110
+ description Tenant_A_OP_Zone_1
+ no shutdown
+ vrf Tenant_A_OP_Zone
+ ip address virtual 10.1.10.1/24
+!
+interface Vlan3009
+ description MLAG_PEER_L3_iBGP: vrf Tenant_A_OP_Zone
+ no shutdown
+ mtu 1500
+ vrf Tenant_A_OP_Zone
+ ip address 10.255.251.5/31
+!
+interface Vlan4093
+ description MLAG_PEER_L3_PEERING
+ no shutdown
+ mtu 1500
+ ip address 10.255.251.5/31
+!
+interface Vlan4094
+ description MLAG_PEER
+ no shutdown
+ mtu 1500
+ no autostate
+ ip address 10.255.252.5/31
+!
+interface Vxlan1
+ description leaf4_VTEP
+ vxlan source-interface Loopback1
+ vxlan virtual-router encapsulation mac-address mlag-system-id
+ vxlan udp-port 4789
+ vxlan vlan 110 vni 10110
+ vxlan vlan 160 vni 55160
+ vxlan vrf Tenant_A_OP_Zone vni 10
+!
+ip virtual-router mac-address 00:1c:73:00:dc:01
+!
+ip address virtual source-nat vrf Tenant_A_OP_Zone address 10.255.1.6
+!
+ip routing
+ip routing vrf Tenant_A_OP_Zone
+!
+ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+ seq 10 permit 192.0.255.0/24 eq 32
+ seq 20 permit 192.0.254.0/24 eq 32
+!
+mlag configuration
+ domain-id pod2
+ local-interface Vlan4094
+ peer-address 10.255.252.4
+ peer-link Port-Channel1
+ reload-delay mlag 300
+ reload-delay non-mlag 330
+!
+ip route 0.0.0.0/0 192.168.0.1
+!
+route-map RM-CONN-2-BGP permit 10
+ match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+!
+route-map RM-MLAG-PEER-IN permit 10
+ description Make routes learned over MLAG Peer-link less preferred on spines to ensure optimal routing
+ set origin incomplete
+!
+router bfd
+ multihop interval 1200 min-rx 1200 multiplier 3
+!
+router bgp 65102
+ router-id 192.0.255.6
+ no bgp default ipv4-unicast
+ distance bgp 20 200 200
+ graceful-restart restart-time 300
+ graceful-restart
+ maximum-paths 4 ecmp 4
+ neighbor EVPN-OVERLAY-PEERS peer group
+ neighbor EVPN-OVERLAY-PEERS update-source Loopback0
+ neighbor EVPN-OVERLAY-PEERS bfd
+ neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3
+ neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA==
+ neighbor EVPN-OVERLAY-PEERS send-community
+ neighbor EVPN-OVERLAY-PEERS maximum-routes 0
+ neighbor IPv4-UNDERLAY-PEERS peer group
+ neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w==
+ neighbor IPv4-UNDERLAY-PEERS send-community
+ neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER peer group
+ neighbor MLAG-IPv4-UNDERLAY-PEER remote-as 65102
+ neighbor MLAG-IPv4-UNDERLAY-PEER next-hop-self
+ neighbor MLAG-IPv4-UNDERLAY-PEER description leaf3
+ neighbor MLAG-IPv4-UNDERLAY-PEER password 7 vnEaG8gMeQf3d3cN6PktXQ==
+ neighbor MLAG-IPv4-UNDERLAY-PEER send-community
+ neighbor MLAG-IPv4-UNDERLAY-PEER maximum-routes 12000
+ neighbor MLAG-IPv4-UNDERLAY-PEER route-map RM-MLAG-PEER-IN in
+ neighbor 10.255.251.4 peer group MLAG-IPv4-UNDERLAY-PEER
+ neighbor 10.255.251.4 description leaf3
+ neighbor 172.30.255.12 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.12 remote-as 65001
+ neighbor 172.30.255.12 description spine1_Ethernet5
+ neighbor 172.30.255.14 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.14 remote-as 65001
+ neighbor 172.30.255.14 description spine2_Ethernet5
+ neighbor 192.0.255.1 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.1 remote-as 65001
+ neighbor 192.0.255.1 description spine1
+ neighbor 192.0.255.2 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.2 remote-as 65001
+ neighbor 192.0.255.2 description spine2
+ redistribute connected route-map RM-CONN-2-BGP
+ !
+ vlan-aware-bundle Tenant_A_OP_Zone
+ rd 192.0.255.6:10
+ route-target both 10:10
+ redistribute learned
+ vlan 110
+ !
+ vlan-aware-bundle Tenant_A_VMOTION
+ rd 192.0.255.6:55160
+ route-target both 55160:55160
+ redistribute learned
+ vlan 160
+ !
+ address-family evpn
+ neighbor EVPN-OVERLAY-PEERS activate
+ !
+ address-family ipv4
+ no neighbor EVPN-OVERLAY-PEERS activate
+ neighbor IPv4-UNDERLAY-PEERS activate
+ neighbor MLAG-IPv4-UNDERLAY-PEER activate
+ !
+ vrf Tenant_A_OP_Zone
+ rd 192.0.255.6:10
+ route-target import evpn 10:10
+ route-target export evpn 10:10
+ router-id 192.0.255.6
+ neighbor 10.255.251.4 peer group MLAG-IPv4-UNDERLAY-PEER
+ redistribute connected
+!
+management api http-commands
+ protocol https
+ no shutdown
+ !
+ vrf default
+ no shutdown
+!
+end
diff --git a/docs/labs/lab06-provisioning/configlets/AVD_spine1.cfg b/docs/labs/lab06-provisioning/configlets/AVD_spine1.cfg
new file mode 100644
index 0000000..df188d7
--- /dev/null
+++ b/docs/labs/lab06-provisioning/configlets/AVD_spine1.cfg
@@ -0,0 +1,129 @@
+!RANCID-CONTENT-TYPE: arista
+!
+vlan internal order ascending range 1006 1199
+!
+transceiver qsfp default-mode 4x10G
+!
+service routing protocols model multi-agent
+!
+hostname spine1
+ip name-server vrf default 8.8.8.8
+ip name-server vrf default 192.168.2.1
+dns domain atd.lab
+!
+spanning-tree mode none
+!
+no enable password
+no aaa root
+!
+interface Ethernet2
+ description P2P_LINK_TO_LEAF1_Ethernet2
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.0/31
+!
+interface Ethernet3
+ description P2P_LINK_TO_LEAF2_Ethernet2
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.4/31
+!
+interface Ethernet4
+ description P2P_LINK_TO_LEAF3_Ethernet2
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.8/31
+!
+interface Ethernet5
+ description P2P_LINK_TO_LEAF4_Ethernet2
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.12/31
+!
+interface Loopback0
+ description EVPN_Overlay_Peering
+ no shutdown
+ ip address 192.0.255.1/32
+!
+interface Management1
+ description oob_management
+ no shutdown
+ ip address 192.168.0.10/24
+!
+ip routing
+!
+ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+ seq 10 permit 192.0.255.0/24 eq 32
+!
+ip route 0.0.0.0/0 192.168.0.1
+!
+route-map RM-CONN-2-BGP permit 10
+ match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+!
+router bfd
+ multihop interval 1200 min-rx 1200 multiplier 3
+!
+router bgp 65001
+ router-id 192.0.255.1
+ no bgp default ipv4-unicast
+ distance bgp 20 200 200
+ graceful-restart restart-time 300
+ graceful-restart
+ maximum-paths 4 ecmp 4
+ neighbor EVPN-OVERLAY-PEERS peer group
+ neighbor EVPN-OVERLAY-PEERS next-hop-unchanged
+ neighbor EVPN-OVERLAY-PEERS update-source Loopback0
+ neighbor EVPN-OVERLAY-PEERS bfd
+ neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3
+ neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA==
+ neighbor EVPN-OVERLAY-PEERS send-community
+ neighbor EVPN-OVERLAY-PEERS maximum-routes 0
+ neighbor IPv4-UNDERLAY-PEERS peer group
+ neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w==
+ neighbor IPv4-UNDERLAY-PEERS send-community
+ neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000
+ neighbor 172.30.255.1 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.1 remote-as 65101
+ neighbor 172.30.255.1 description leaf1_Ethernet2
+ neighbor 172.30.255.5 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.5 remote-as 65101
+ neighbor 172.30.255.5 description leaf2_Ethernet2
+ neighbor 172.30.255.9 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.9 remote-as 65102
+ neighbor 172.30.255.9 description leaf3_Ethernet2
+ neighbor 172.30.255.13 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.13 remote-as 65102
+ neighbor 172.30.255.13 description leaf4_Ethernet2
+ neighbor 192.0.255.3 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.3 remote-as 65101
+ neighbor 192.0.255.3 description leaf1
+ neighbor 192.0.255.4 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.4 remote-as 65101
+ neighbor 192.0.255.4 description leaf2
+ neighbor 192.0.255.5 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.5 remote-as 65102
+ neighbor 192.0.255.5 description leaf3
+ neighbor 192.0.255.6 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.6 remote-as 65102
+ neighbor 192.0.255.6 description leaf4
+ redistribute connected route-map RM-CONN-2-BGP
+ !
+ address-family evpn
+ neighbor EVPN-OVERLAY-PEERS activate
+ !
+ address-family ipv4
+ no neighbor EVPN-OVERLAY-PEERS activate
+ neighbor IPv4-UNDERLAY-PEERS activate
+!
+management api http-commands
+ protocol https
+ no shutdown
+ !
+ vrf default
+ no shutdown
+!
+end
diff --git a/docs/labs/lab06-provisioning/configlets/AVD_spine2.cfg b/docs/labs/lab06-provisioning/configlets/AVD_spine2.cfg
new file mode 100644
index 0000000..0ad7bd1
--- /dev/null
+++ b/docs/labs/lab06-provisioning/configlets/AVD_spine2.cfg
@@ -0,0 +1,129 @@
+!RANCID-CONTENT-TYPE: arista
+!
+vlan internal order ascending range 1006 1199
+!
+transceiver qsfp default-mode 4x10G
+!
+service routing protocols model multi-agent
+!
+hostname spine2
+ip name-server vrf default 8.8.8.8
+ip name-server vrf default 192.168.2.1
+dns domain atd.lab
+!
+spanning-tree mode none
+!
+no enable password
+no aaa root
+!
+interface Ethernet2
+ description P2P_LINK_TO_LEAF1_Ethernet3
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.2/31
+!
+interface Ethernet3
+ description P2P_LINK_TO_LEAF2_Ethernet3
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.6/31
+!
+interface Ethernet4
+ description P2P_LINK_TO_LEAF3_Ethernet3
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.10/31
+!
+interface Ethernet5
+ description P2P_LINK_TO_LEAF4_Ethernet3
+ no shutdown
+ mtu 1500
+ no switchport
+ ip address 172.30.255.14/31
+!
+interface Loopback0
+ description EVPN_Overlay_Peering
+ no shutdown
+ ip address 192.0.255.2/32
+!
+interface Management1
+ description oob_management
+ no shutdown
+ ip address 192.168.0.11/24
+!
+ip routing
+!
+ip prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+ seq 10 permit 192.0.255.0/24 eq 32
+!
+ip route 0.0.0.0/0 192.168.0.1
+!
+route-map RM-CONN-2-BGP permit 10
+ match ip address prefix-list PL-LOOPBACKS-EVPN-OVERLAY
+!
+router bfd
+ multihop interval 1200 min-rx 1200 multiplier 3
+!
+router bgp 65001
+ router-id 192.0.255.2
+ no bgp default ipv4-unicast
+ distance bgp 20 200 200
+ graceful-restart restart-time 300
+ graceful-restart
+ maximum-paths 4 ecmp 4
+ neighbor EVPN-OVERLAY-PEERS peer group
+ neighbor EVPN-OVERLAY-PEERS next-hop-unchanged
+ neighbor EVPN-OVERLAY-PEERS update-source Loopback0
+ neighbor EVPN-OVERLAY-PEERS bfd
+ neighbor EVPN-OVERLAY-PEERS ebgp-multihop 3
+ neighbor EVPN-OVERLAY-PEERS password 7 q+VNViP5i4rVjW1cxFv2wA==
+ neighbor EVPN-OVERLAY-PEERS send-community
+ neighbor EVPN-OVERLAY-PEERS maximum-routes 0
+ neighbor IPv4-UNDERLAY-PEERS peer group
+ neighbor IPv4-UNDERLAY-PEERS password 7 AQQvKeimxJu+uGQ/yYvv9w==
+ neighbor IPv4-UNDERLAY-PEERS send-community
+ neighbor IPv4-UNDERLAY-PEERS maximum-routes 12000
+ neighbor 172.30.255.3 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.3 remote-as 65101
+ neighbor 172.30.255.3 description leaf1_Ethernet3
+ neighbor 172.30.255.7 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.7 remote-as 65101
+ neighbor 172.30.255.7 description leaf2_Ethernet3
+ neighbor 172.30.255.11 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.11 remote-as 65102
+ neighbor 172.30.255.11 description leaf3_Ethernet3
+ neighbor 172.30.255.15 peer group IPv4-UNDERLAY-PEERS
+ neighbor 172.30.255.15 remote-as 65102
+ neighbor 172.30.255.15 description leaf4_Ethernet3
+ neighbor 192.0.255.3 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.3 remote-as 65101
+ neighbor 192.0.255.3 description leaf1
+ neighbor 192.0.255.4 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.4 remote-as 65101
+ neighbor 192.0.255.4 description leaf2
+ neighbor 192.0.255.5 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.5 remote-as 65102
+ neighbor 192.0.255.5 description leaf3
+ neighbor 192.0.255.6 peer group EVPN-OVERLAY-PEERS
+ neighbor 192.0.255.6 remote-as 65102
+ neighbor 192.0.255.6 description leaf4
+ redistribute connected route-map RM-CONN-2-BGP
+ !
+ address-family evpn
+ neighbor EVPN-OVERLAY-PEERS activate
+ !
+ address-family ipv4
+ no neighbor EVPN-OVERLAY-PEERS activate
+ neighbor IPv4-UNDERLAY-PEERS activate
+!
+management api http-commands
+ protocol https
+ no shutdown
+ !
+ vrf default
+ no shutdown
+!
+end
diff --git a/docs/labs/lab06-provisioning/gen_builder.py b/docs/labs/lab06-provisioning/gen_builder.py
new file mode 100644
index 0000000..8cd389d
--- /dev/null
+++ b/docs/labs/lab06-provisioning/gen_builder.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2020 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+container_id = clnt.api.get_container_by_name("TP_LEAFS")['key']
+builder_name = 'SYS_TelemetryBuilderV3'
+configletBuilderID = clnt.api.get_configlet_by_name(builder_name)['key']
+
+payload = {"previewValues":[{
+ "fieldId":"vrf",
+ "value":"red"}],
+ "configletBuilderId":configletBuilderID,
+ "netElementIds":[],
+ "pageType":"container",
+ "containerId":container_id,
+ "containerToId":"",
+ "mode":"assign"}
+
+preview = clnt.post('/configlet/configletBuilderPreview.do', data=payload)
+
+generated_names_list = []
+generated_keys_list = []
+
+for i in preview['data']:
+ generated_names_list.append(i['configlet']['name'])
+ generated_keys_list.append(i['configlet']['key'])
+
+clnt.get("/configlet/searchConfiglets.do?objectId={}&objectType=container&type=ignoreDraft&queryparam={}&startIndex=0&endIndex=22&sortByColumn=&sortOrder=".format(container_id, builder_name.lower()))
+
+tempData = {"data":[{
+ "info":"Configlet Assign: to container TP_LEAFS",
+ "infoPreview":"<b>Configlet Assign:</b> to container TP_LEAFS",
+ "action":"associate",
+ "nodeType":"configlet",
+ "nodeId":"",
+ "toId":container_id,
+ "fromId":"","nodeName":"","fromName":"",
+ "toName":"TP_LEAFS",
+ "toIdType":"container",
+ "configletList":generated_keys_list,
+ "configletNamesList":generated_names_list,
+ "ignoreConfigletList":[],
+ "ignoreConfigletNamesList":[],
+ "configletBuilderList":[configletBuilderID],
+ "configletBuilderNamesList":[builder_name],
+ "ignoreConfigletBuilderList":[],
+ "ignoreConfigletBuilderNamesList":[]
+ }
+ ]
+ }
+
+clnt.api._add_temp_action(tempData)
+clnt.api._save_topology_v2([])
diff --git a/docs/labs/lab06-provisioning/mlag_issu.py b/docs/labs/lab06-provisioning/mlag_issu.py
new file mode 100644
index 0000000..307d418
--- /dev/null
+++ b/docs/labs/lab06-provisioning/mlag_issu.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python3
+#
+# python3 mlag_issu <upgrade inventory file> <MLAG peer to upgrade: 'peer1' or 'peer2'>"
+#
+# # Example of upgrade inventory file (YAML)
+# cvp_hosts:
+# - 192.168.0.191
+# - 192.168.0.192
+# - 192.168.0.193
+# cvp_username: cvpadmin
+# target_eos_version: 4.25.4M
+# target_terminattr_version: 1.13.6
+# mlag_couples:
+# - peer1: leaf101-1
+# peer2: leaf101-2
+# - peer1: leaf102-1
+# peer2: leaf102-2
+#
+# Note: upgrades are performed in parallel
+
+import sys
+import time
+import string
+import random
+from getpass import getpass
+import requests
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+from datetime import datetime
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpLoginError, CvpApiError
+from pprint import pprint
+from operator import itemgetter
+import yaml
+
+class CvpDeviceUpgrader(object):
+ def __init__(self, hosts, username, password):
+ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+ self.cvp_hosts = hosts
+ self.cvp_user = username
+ self.cvp_password = password
+ self.session = self._open_cvp_session()
+
+ def _open_cvp_session(self):
+ try:
+ client = CvpClient()
+ client.connect(
+ nodes=self.cvp_hosts,
+ username=self.cvp_user,
+ password=self.cvp_password,
+ request_timeout=300,
+ connect_timeout=30
+ )
+ return(client)
+ except CvpLoginError as e:
+ print(f"Cannot connect to CVP API: {e}")
+ exit()
+
+ def create_mlag_issu_change_control(self, taskIDs, deviceIDs):
+ cc_id = f"CC_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ pre_upgrade_stage = {'stage': [{
+ 'id': f"preU_{cc_id}",
+ 'name': 'pre_upgrade',
+ 'stage_row':[{'stage': [{
+ 'id': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(9)),
+ 'action': {
+ 'name': 'mlaghealthcheck',
+ 'timeout': 0,
+ 'args': {
+ 'DeviceID': device_id
+ }
+ }
+ } for device_id in deviceIDs]}]
+ }]}
+ upgrade_stage = {'stage': [{
+ 'id': f"U_{cc_id}",
+ 'name': 'upgrade',
+ 'stage_row': [{'stage': [{
+ 'id': task_id,
+ 'action': {
+ 'name': 'task',
+ 'args': {
+ 'TaskID': task_id
+ }
+ }
+ } for task_id in taskIDs]}]
+ }]}
+ post_upgrade_stage = {'stage': [{
+ 'id': f"postU_{cc_id}",
+ 'name': 'post_upgrade',
+ 'stage_row': [{'stage': [{
+ 'id': ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(9)),
+ 'action': {
+ 'name': 'mlaghealthcheck',
+ 'timeout': 0,
+ 'args': {
+ 'DeviceID': device_id
+ }
+ }
+ } for device_id in deviceIDs]}]
+ }]}
+ cc_data = {'config': {
+ 'id': cc_id,
+ 'name': f"Change Control {cc_id}",
+ 'root_stage': {
+ 'id': 'root',
+ 'name': f"Change Control {cc_id} root",
+ 'stage_row': [pre_upgrade_stage, upgrade_stage, post_upgrade_stage],
+ }
+ }}
+ try:
+ res = self.session.post('/api/v3/services/ccapi.ChangeControl/Update',
+ data=cc_data,
+ timeout=self.session.api.request_timeout
+ )
+ except Exception as e:
+ print(str(e))
+ return(None)
+ print(f"Change control {res[0]['id']} created at {res[0]['update_timestamp']}")
+ return(res[0]['id'])
+
+ def get_mlag_issu_change_control_logs(self, ccID, startTime):
+ end_time = int(time.time() * 1000)
+ cc_logs_data = {'category': 'ChangeControl',
+ 'objectKey': ccID,
+ 'dataSize': 15000,
+ 'startTime': startTime,
+ 'endTime': end_time
+ }
+ logs = self.session.post('/cvpservice/audit/getLogs.do',
+ data=cc_logs_data,
+ timeout=self.session.api.request_timeout
+ )
+ for log in sorted(logs['data'], key=itemgetter('dateTimeInLongFormat')):
+ if log['subObjectName'] and 'Command(s)' not in log['activity']:
+ log_date = datetime.fromtimestamp(log['dateTimeInLongFormat']/1000)
+ print(f"{log_date} {log['subObjectName']}: {log['activity']}")
+ return(end_time + 1)
+
+ def run_mlag_issu_change_control(self, ccID):
+ print(f"Automatic approval of change control {ccID}")
+ self.session.api.approve_change_control(ccID, datetime.utcnow().isoformat() + 'Z')
+ time.sleep(2)
+ print(f"Starting the execution of change control {ccID}")
+ start_time = int(time.time() * 1000)
+ self.session.api.execute_change_controls([ccID])
+ time.sleep(2)
+ cc_status = self.session.api.get_change_control_status(ccID)[0]['status']
+ start_time = self.get_mlag_issu_change_control_logs(ccID, start_time)
+ while cc_status['state'] == 'Running':
+ time.sleep(30)
+ cc_status = self.session.api.get_change_control_status(ccID)[0]['status']
+ start_time = self.get_mlag_issu_change_control_logs(ccID, start_time)
+ print(f"Change control {ccID} final status: {cc_status['state']}")
+ if cc_status['error']:
+ print(f"Change control {ccID} had the following errors: {cc_status['error']}")
+ else:
+ print(f"Change control {ccID} completed without errors")
+
+def main():
+ if len(sys.argv) != 3:
+ print(f"Usage: python3 {sys.argv[0]} <input file path> <MLAG peer to upgrade: peer1/peer2>")
+ exit()
+ try:
+ with open(sys.argv[1], 'r') as yf:
+ params = yaml.safe_load(yf)
+ except Exception as e:
+ print(e)
+ exit()
+ cvp_password = getpass(prompt=f"CVP password for user {params['cvp_username']}: ")
+ cvpdu = CvpDeviceUpgrader(
+ hosts=params['cvp_hosts'],
+ username=params['cvp_username'],
+ password=cvp_password
+ )
+ image_bundle = None
+ for bundle in cvpdu.session.api.get_image_bundles()['data']:
+ eos_match = False
+ terminattr_match = False
+ for img in bundle['imageIds']:
+ if params['target_eos_version'] in img:
+ eos_match = True
+ elif params['target_terminattr_version'] in img:
+ terminattr_match = True
+ if eos_match and terminattr_match:
+ image_bundle = bundle
+ break
+ if image_bundle is None:
+ print(f"Cannot find an image bundle with EOS {params['target_eos_version']} and TerminAttr {params['target_terminattr_version']}")
+ exit()
+ hostnames = [couple[sys.argv[2]] for couple in params['mlag_couples']]
+ devices_to_upgrade = list()
+ inventory = cvpdu.session.api.get_inventory()
+ for hostname in hostnames:
+ provisioned = False
+ for dev in inventory:
+ if dev['hostname'] == hostname:
+ provisioned = True
+ devices_to_upgrade.append(dev)
+ break
+ if not provisioned:
+ print(f"Device with hostname {hostname} is not provisioned in CVP")
+ if not devices_to_upgrade:
+ print('none of the mentioned devices is provisioned in CVP')
+ exit()
+ print(f"Devices to upgrade: {', '.join([dev['hostname'] for dev in devices_to_upgrade])}")
+ task_ids = list()
+ for device in devices_to_upgrade:
+ response = cvpdu.session.api.apply_image_to_device(image_bundle, device)['data']
+ if response['status'] == 'success':
+ task_ids.extend(response['taskIds'])
+ device_ids = [device['serialNumber'] for device in devices_to_upgrade]
+ cc_id = cvpdu.create_mlag_issu_change_control(task_ids, device_ids)
+ if cc_id is None:
+ print('Failed to create the MLAG ISSU change control')
+ exit()
+ time.sleep(2)
+ cvpdu.run_mlag_issu_change_control(cc_id)
+
+if __name__ == '__main__':
+ main()
diff --git a/docs/labs/lab06-provisioning/move_device.py b/docs/labs/lab06-provisioning/move_device.py
new file mode 100644
index 0000000..5257f79
--- /dev/null
+++ b/docs/labs/lab06-provisioning/move_device.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+container = clnt.api.get_container_by_name('TP_LEAFS') # container object
+
+app_name = "my app" # can be any string
+
+device = {"key":"00:1c:73:c5:4c:87", "fqdn":"co633.ire.aristanetworks.com"}
+
+move_device_to_container(app_name, device, container)
diff --git a/docs/labs/lab06-provisioning/vc_task_retrigger.py b/docs/labs/lab06-provisioning/vc_task_retrigger.py
new file mode 100644
index 0000000..b5586ee
--- /dev/null
+++ b/docs/labs/lab06-provisioning/vc_task_retrigger.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+# Example on how to re-trigger task creation if a config push task was previously
+# cancelled and the device is still config out of sync
+import argparse
+import ssl
+import sys
+from pkg_resources import parse_version
+from getpass import getpass
+from cvprac.cvp_client import CvpClient
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+
+if ((sys.version_info.major == 3) or
+ (sys.version_info.major == 2 and sys.version_info.minor == 7 and
+ sys.version_info.micro >= 5)):
+ ssl._create_default_https_context = ssl._create_unverified_context
+
+
+def main():
+
+ compliance = {"0001": "Config is out of sync",
+ "0003": "Config & image out of sync",
+ "0004": "Config, Image and Device time are in sync",
+ "0005": "Device is not reachable",
+ "0008": "Config, Image and Extensions are out of sync",
+ "0009": "Config and Extensions are out of sync",
+ "0012": "Config, Image, Extension and Device time are out of sync",
+ "0013": "Config, Image and Device time are out of sync",
+ "0014": "Config, Extensions and Device time are out of sync",
+ "0016": "Config and Device time are out of sync"
+ }
+ # Create connection to CloudVision
+ clnt = CvpClient()
+
+ parser = argparse.ArgumentParser(
+ description='Script to recreate a task, if a previous config push was cancelled')
+ parser.add_argument('-u', '--username', default='username')
+ parser.add_argument('-p', '--password', default=None)
+ parser.add_argument('-c', '--cvpserver', action='append')
+ parser.add_argument('-f', '--filter', action='append', default=None)
+ args = parser.parse_args()
+
+ if args.password is None:
+ args.password = getpass()
+
+ for cvpserver in args.cvpserver:
+ print("Connecting to %s" % cvpserver)
+ try:
+ clnt.connect(nodes=[cvpserver], username=args.username, password=args.password)
+ except Exception as e:
+ print("Unable to connect to CVP: %s" % str(e))
+
+ # Get the current CVP version
+ cvp_release = clnt.api.get_cvp_info()['version']
+ if parse_version(cvp_release) < parse_version('2020.3.0'):
+ # For older CVP, we manually trigger a compliance check
+ try:
+ clnt.api.check_compliance('root', 'container')
+ except:
+ # Bad practice, but the check compliance applied to a container can't actually work
+ # since the complianceIndication key doesn't exist on the container level
+ pass
+ else:
+ # with continuous compliance checks, triggering the check is no longer required
+ pass
+
+ device_filters = []
+ if args.filter is not None:
+ for entry in args.filter:
+ device_filters.extend(entry.split(','))
+
+ # Get inventory
+ print("Collecting inventory...")
+ devices = clnt.api.get_inventory()
+ print("%d devices in inventory" % len(devices) )
+
+ for switch in devices:
+ if (switch['status'] == 'Registered' and
+ switch['parentContainerId'] != 'undefined_container'):
+
+ if len(device_filters) > 0:
+ # iterate over device filters, and update task for
+ # any devices not in compliance
+
+ for filter_term in device_filters:
+ print("Checking device: %s" % switch['hostname'])
+ if filter_term in switch['hostname']:
+ # generate configlet list
+ cl = clnt.api.get_configlets_by_device_id(switch['systemMacAddress'])
+ # generate a task if config is out of sync
+ if switch['complianceCode'] in compliance.keys():
+ print(clnt.api.apply_configlets_to_device("", switch, cl))
+ else:
+ print("%s is compliant, nothing to do" % switch['hostname'])
+ else:
+ print("Skipping %s due to filter" % switch['hostname'])
+ else:
+ print("Checking device: %s" % switch['hostname'])
+ cl = clnt.api.get_configlets_by_device_id(switch['systemMacAddress'])
+ # generate a task if config is out of sync
+ if switch['complianceCode'] in compliance.keys():
+ print(clnt.api.apply_configlets_to_device("", switch, cl))
+
+ else:
+ print("Skipping %s, device is unregistered for provisioning" % switch['hostname'])
+
+ return 0
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs/labs/lab07-aaa/aaa_users.csv b/docs/labs/lab07-aaa/aaa_users.csv
new file mode 100644
index 0000000..14b3706
--- /dev/null
+++ b/docs/labs/lab07-aaa/aaa_users.csv
@@ -0,0 +1,5 @@
+username,first_name,last_name,email,user_type,role,status
+alice,,,alice@abc.xyz,SSO,network-admin,Enabled
+bob,,,bob@abc.xyz,SSO,network-admin,Enabled
+jane,Jane,Smith,jane@abc.xyz,SSO,network-admin,Enabled
+john,John,Smith,john@abc.xyz,SSO,network-admin,Enabled \ No newline at end of file
diff --git a/docs/labs/lab07-aaa/add_new_user_cvaas.py b/docs/labs/lab07-aaa/add_new_user_cvaas.py
new file mode 100644
index 0000000..af2d48e
--- /dev/null
+++ b/docs/labs/lab07-aaa/add_new_user_cvaas.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from cvprac.cvp_client import CvpClient
+
+# Create connection to CloudVision using Service Account token
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+
+username = "john"
+password = ""
+role = "network-admin"
+status = "Enabled"
+first_name = "John"
+last_name = "Smith"
+email = "john.smith@abc.xyz"
+utype = "SSO"
+
+try:
+ clnt.api.add_user(username,password,role,status,first_name,last_name,email,utype)
+except CvpApiError as e:
+ print(e)
diff --git a/docs/labs/lab07-aaa/add_new_user_onprem.py b/docs/labs/lab07-aaa/add_new_user_onprem.py
new file mode 100644
index 0000000..218c9fc
--- /dev/null
+++ b/docs/labs/lab07-aaa/add_new_user_onprem.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from getpass import getpass
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+username = "cvpuser2"
+password = getpass()
+role = "network-admin"
+status = "Enabled"
+first_name = "Cloud"
+last_name = "Vision"
+email = "cvp@arista.com"
+utype = "TACACS"
+
+try:
+ clnt.api.add_user(username,password,role,status,first_name,last_name,email,utype)
+except CvpApiError as e:
+ print(e)
diff --git a/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py b/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py
new file mode 100644
index 0000000..c5cdda5
--- /dev/null
+++ b/docs/labs/lab07-aaa/add_users_from_csv_cvaas.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from cvprac.cvp_client import CvpClient
+import csv
+
+# Create connection to CloudVision using Service Account token
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+
+
+with open("aaa_users.csv") as csvfile:
+ for i in csv.DictReader(csvfile):
+ data = dict(i)
+ try:
+ clnt.api.add_user(data['username'], "", data['role'], data['status'], data['first_name'], data['last_name'], data['email'], data['user_type'])
+ except CvpApiError as e:
+ print(e)
+ print ("Adding user {} to CVaaS".format(data['username']))
diff --git a/docs/labs/lab07-aaa/create_svc_account.py b/docs/labs/lab07-aaa/create_svc_account.py
new file mode 100644
index 0000000..7f0e55e
--- /dev/null
+++ b/docs/labs/lab07-aaa/create_svc_account.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using user/password (on-prem only)
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+username = "cvprac2"
+description = "test cvprac"
+roles = ["network-admin", "clouddeploy"] # both role names and role IDs are supported
+status = 1 # 1 is equivalent to "ACCOUNT_STATUS_ENABLED"
+clnt.api.svc_account_set(username, description, roles, status)
diff --git a/docs/labs/lab07-aaa/create_svc_account_token.py b/docs/labs/lab07-aaa/create_svc_account_token.py
new file mode 100644
index 0000000..4be8185
--- /dev/null
+++ b/docs/labs/lab07-aaa/create_svc_account_token.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using user/password (on-prem only)
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+username = "cvprac2"
+duration = "31536000s" # 1 year validity
+description = "test cvprac"
+svc_token = clnt.api.svc_account_token_set(username, duration, description)
+
+# Write the token to file in <username>.tok format
+with open(svc_token[0]['value']['user'] + ".tok", "w") as f:
+ f.write(svc_token[0]['value']['token'])
diff --git a/docs/labs/lab07-aaa/create_terminattr_tokens.py b/docs/labs/lab07-aaa/create_terminattr_tokens.py
new file mode 100644
index 0000000..d874913
--- /dev/null
+++ b/docs/labs/lab07-aaa/create_terminattr_tokens.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+#
+# Example script to generate the TerminAttr token via REST API from CVaaS and CV on-prem
+# and save them to a file
+
+from cvprac.cvp_client import CvpClient
+from pprint import pprint as pp
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Reading the service account token from a file
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='',password='',is_cvaas=True, api_token=token)
+
+terminattr_token = clnt.api.create_enroll_token('720h')
+with open('cv-onboarding-token', 'w') as f:
+ f.write(terminattr_token[0]['enrollmentToken']['token'])
+
+primary = CvpClient()
+primary.connect(nodes=['cvp1'], username='username',password='password')
+
+terminattr_token = primary.api.create_enroll_token('720h')
+
+with open('token', 'w') as f:
+ f.write(terminattr_token['data'])
diff --git a/docs/labs/lab07-aaa/cvaas.tok b/docs/labs/lab07-aaa/cvaas.tok
new file mode 100644
index 0000000..9d0234c
--- /dev/null
+++ b/docs/labs/lab07-aaa/cvaas.tok
@@ -0,0 +1 @@
+<copy service account token here> \ No newline at end of file
diff --git a/docs/labs/lab07-aaa/delete_all_expired_svc_account_tokens.py b/docs/labs/lab07-aaa/delete_all_expired_svc_account_tokens.py
new file mode 100644
index 0000000..68e82a9
--- /dev/null
+++ b/docs/labs/lab07-aaa/delete_all_expired_svc_account_tokens.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using user/password (on-prem only)
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+clnt.api.svc_account_delete_expired_tokens()
diff --git a/docs/labs/lab07-aaa/delete_svc_account.py b/docs/labs/lab07-aaa/delete_svc_account.py
new file mode 100644
index 0000000..a6f7854
--- /dev/null
+++ b/docs/labs/lab07-aaa/delete_svc_account.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using user/password (on-prem only)
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+username = "cvprac2"
+clnt.api.svc_account_delete(username)
diff --git a/docs/labs/lab07-aaa/delete_svc_account_created_by_user.py b/docs/labs/lab07-aaa/delete_svc_account_created_by_user.py
new file mode 100644
index 0000000..b8f4045
--- /dev/null
+++ b/docs/labs/lab07-aaa/delete_svc_account_created_by_user.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using user/password (on-prem only)
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+svc_accounts = clnt.api.svc_account_get_all()
+created_by = 'john.smith'
+
+# Delete service accounts created by user john.smith
+for account in svc_accounts:
+ if account['value']['created_by'] == created_by:
+ clnt.api.svc_account_delete(account['value']['key']['name'])
diff --git a/docs/labs/lab07-aaa/get_user_info.py b/docs/labs/lab07-aaa/get_user_info.py
new file mode 100644
index 0000000..5e5a193
--- /dev/null
+++ b/docs/labs/lab07-aaa/get_user_info.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+from cvprac.cvp_client import CvpClient
+
+with open("cvaas.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['www.arista.io'], username='', password='', is_cvaas=True, api_token=token)
+
+user_info = clnt.api.get_user('kishore')
+print (user_info)
diff --git a/docs/labs/lab07-aaa/svc_account_misc.py b/docs/labs/lab07-aaa/svc_account_misc.py
new file mode 100644
index 0000000..d3eccca
--- /dev/null
+++ b/docs/labs/lab07-aaa/svc_account_misc.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from cvprac.cvp_client_errors import CvpApiError
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Create connection to CloudVision using user/password (on-prem only)
+clnt = CvpClient()
+clnt.connect(['cvp1'],'username', 'password')
+
+# Get all service accounts states
+
+accounts = clnt.api.svc_account_get_all()
+
+# Get specific service account state
+
+account = clnt.api.svc_account_get_one("cvprac2")
+
+# Get all service account token states
+
+tokens = clnt.api.svc_account_token_get_all()
+
+# Get specific token state
+
+token = clnt.api.svc_account_token_get_one("9bfb39ff892c81d6ac9f25ff95d0389719595feb")
+
+# Delete a service account token
+
+clnt.api.svc_account_token_delete("9bfb39ff892c81d6ac9f25ff95d0389719595feb")
diff --git a/docs/labs/lab08-resource-apis/resource_cvprac.py b/docs/labs/lab08-resource-apis/resource_cvprac.py
new file mode 100644
index 0000000..e454fc9
--- /dev/null
+++ b/docs/labs/lab08-resource-apis/resource_cvprac.py
@@ -0,0 +1,187 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+from cvprac.cvp_client import CvpClient
+from pprint import pprint as pp
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+import requests.packages.urllib3
+requests.packages.urllib3.disable_warnings()
+
+# Reading the service account token from a file
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+def get_events_all(client):
+ ''' Get All events '''
+ event_url = '/api/resources/event/v1/Event/all'
+ response = client.get(event_url)
+ return response['data']
+
+def get_event(client, key, ts):
+ event_url = '/api/resources/event/v1/Event?'
+ url = event_url + 'key.key=' + key + "&key.timestamp=" + ts
+ response = client.get(url)
+ return response
+
+def get_events_t1_t2(client, t1, t2):
+ event_url = '/api/resources/event/v1/Event/all?'
+ url = event_url + 'time.start=' + t1 + "&time.end=" + t2
+ response = client.get(url)
+ return response['data']
+
+def get_events_by_severity(client, severity):
+ payload = {"partialEqFilter": [{"severity": severity }]}
+ event_url = '/api/resources/event/v1/Event/all'
+ response = client.post(event_url, data=payload)
+ if 'data' in response.keys():
+ return response['data']
+ else:
+ return response
+
+def get_events_by_type(client, etype):
+ payload = {"partialEqFilter": [{"eventType": etype }]}
+ event_url = '/api/resources/event/v1/Event/all'
+ response = client.post(event_url, data=payload)
+ if 'data' in response.keys():
+ return response['data']
+ else:
+ return response
+
+def get_active_devices(client):
+ ''' Get active devices '''
+ dev_url = '/api/resources/inventory/v1/Device/all'
+ devices_data = client.get(dev_url)
+ devices = []
+ for device in devices_data['data']:
+ try:
+ if device['result']['value']['streamingStatus'] == "STREAMING_STATUS_ACTIVE":
+ devices.append(device['result']['value']['hostname'])
+ # pass on archived datasets
+ except KeyError as e:
+ continue
+ return devices
+
+def get_all_device_tags(client):
+ tag_url = '/api/resources/tag/v1/DeviceTag/all'
+ tag_data = client.get(tag_url)
+ tags = []
+ for tag in tag_data['data']:
+ tags.append({tag['result']['value']['key']['label']:tag['result']['value']['key']['value']})
+ return tags
+
+def get_all_interface_tags(client):
+ tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig/all'
+ tags = client.get(tag_url)
+ return tags['data']
+
+def filter_interface_tag(client, dId=None, ifId=None, label=None, value=None):
+ tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig/all'
+ payload = {
+ "partialEqFilter": [
+ {"key": {"deviceId": dId, "interfaceId": ifId, "label": label, "value": value}}
+ ]
+ }
+ response = client.post(tag_url, data=payload)
+ return response
+
+def create_itag(client, label, value):
+ tag_url = '/api/resources/tag/v1/InterfaceTagConfig'
+ payload = {"key":{"label":label,"value":value}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+def assign_itag(client, dId, ifId, label, value):
+ tag_url = '/api/resources/tag/v1/InterfaceTagAssignmentConfig'
+ payload = {"key":{"label":label, "value":value, "deviceId": dId, "interfaceId": ifId}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+def create_dtag(client, label, value):
+ tag_url = '/api/resources/tag/v1/DeviceTagConfig'
+ payload = {"key":{"label":label,"value":value}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+def assign_dtag(client, dId, label, value):
+ tag_url = '/api/resources/tag/v1/DeviceTagAssignmentConfig'
+ payload = {"key":{"label":label, "value":value, "deviceId": dId}}
+ response = client.post(tag_url, data=payload)
+ return response
+
+### Uncomment the below functions/print statement to test
+
+# ### Get all active events
+# print ('=== All active events ===')
+# cvpevents = get_events_all(clnt)
+# for event in cvpevents:
+# print(event)
+
+# ### Get a specific event
+# key = "6098ae39e4c8a9d7"
+# ts ="2021-04-06T21:53:00Z"
+# get_event(clnt, key, ts)
+
+# ### Get events between two dates
+# t1 = "2021-04-06T09:00:00Z"
+# t2 = "2021-04-06T14:00:00Z"
+# events = get_events_t1_t2(clnt, t1, t2)
+# print(f"=== Events between {t1} and {t2} ===")
+# pp(events)
+
+# ### Get all INFO severity events ###
+# # EVENT_SEVERITY_UNSPECIFIED = 0
+# # EVENT_SEVERITY_INFO = 1
+# # EVENT_SEVERITY_WARNING = 2
+# # EVENT_SEVERITY_ERROR = 3
+# # EVENT_SEVERITY_CRITICAL = 4
+# ####################################
+
+# severity = 1 ## Severity INFO
+# info = get_events_by_severity(clnt, severity)
+# print('=== Get all INFO severity events ===')
+# pp(info)
+
+# ### Get specific event types
+
+# etype = "LOW_DEVICE_DISK_SPACE"
+# event = get_events_by_type(clnt, etype)
+# print('=== Get all Low Disk Space events ===')
+# pp(event)
+
+# ### Get the inventory
+# print ('=== Inventory ===')
+# print(get_active_devices(clnt))
+
+# ### Get all devie tags
+# print('=== Device Tags ===' )
+# for tag in get_all_device_tags(clnt):
+# print (tag)
+
+# ### Get all interface tag assignments
+# print(get_all_interface_tags(clnt))
+
+# ### Get all interfaces that have a tag with a specific value on a device
+# print(filter_interface_tag(clnt, dId="JPE14070534", value="speed40Gbps"))
+
+# ### Get all tags for an interface of a device
+# print(filter_interface_tag(clnt, dId="JPE14070534", ifId="Ethernet1"))
+
+# ### Get all interfaces that have a specific tag assigned
+# print(filter_interface_tag(clnt, dId="JPE14070534", label="lldp_hostname"))
+
+# ### Create an interface tag
+# create_itag(clnt, "lldp_chassis", "50:08:00:0d:00:48")
+
+# ### Assign an interface tag
+# assign_itag(clnt, "JPE14070534", "Ethernet4", "lldp_chassis", "50:08:00:0d:00:38")
+
+# ### Create a device tag
+# create_dtag(clnt, "topology_hint_pod", "ire-pod11")
+
+# ### Assign an interface tag
+# assign_dtag(clnt, "JPE14070534", "topology_hint_pod", "ire-pod11" )
diff --git a/docs/labs/lab08-resource-apis/topology_tag_assignment.py b/docs/labs/lab08-resource-apis/topology_tag_assignment.py
new file mode 100644
index 0000000..973e9f7
--- /dev/null
+++ b/docs/labs/lab08-resource-apis/topology_tag_assignment.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2021 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the COPYING file.
+
+# In this example we are going to assign topology tags using the tags.v2 workspace aware API
+# More details on tag.v2 can be found at https://aristanetworks.github.io/cloudvision-apis/models/tag.v2/
+# NOTE: Tag.v2 can be used for assigning both device and interface tags (studios, topology, etc) and it's not
+# limited to topology tags only.
+# The following are some of the built-in tags that can be used to modify the Topology rendering:
+# topology_hint_type: < core | edge | endpoint | management | leaf | spine >
+# topology_hint_rack: < rack name as string >
+# topology_hint_pod: < pod name as string >
+# topology_hint_datacenter: < datacenter name as string >
+# topology_hint_building: < building name as string >
+# topology_hint_floor: < floor name as string >
+# topology_network_type: < datacenter | campus | cloud >
+
+from cvprac.cvp_client import CvpClient
+import uuid
+from datetime import datetime
+
+# Reading the service account token from a file
+with open("token.tok") as f:
+ token = f.read().strip('\n')
+
+# Create connection to CloudVision
+clnt = CvpClient()
+clnt.connect(nodes=['cvp1'], username='',password='',api_token=token)
+
+tags_common = [{"key": "topology_hint_pod", "value": "tp-avd-pod1"},
+ {"key": "topology_hint_datacenter", "value": "tp-avd-dc1"}]
+tags_leaf1 = [{"key": "topology_hint_rack", "value": "tp-avd-leafs1"},
+ {"key": "topology_hint_type", "value": "leaf"}]
+tags_leaf2 = [{"key": "topology_hint_rack", "value": "tp-avd-leafs2"},
+ {"key": "topology_hint_type", "value": "leaf"}]
+tags_spines = [{"key": "topology_hint_rack", "value": "tp-avd-spines"},
+ {"key": "topology_hint_type", "value": "spine"}]
+
+# Create workspace
+display_name = f"Change_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+workspace_id = str(uuid.uuid4())
+clnt.api.workspace_config(workspace_id,display_name)
+
+### Create tags
+element_type = "ELEMENT_TYPE_DEVICE"
+
+for tag in tags_common+tags_leaf1+tags_leaf2+tags_spines:
+ tag_label = tag['key']
+ tag_value = tag['value']
+ clnt.api.tag_config(element_type, workspace_id, tag_label, tag_value)
+
+### Assign tags
+devices = {"leafs1":["BAD032986065E8DC14CBB6472EC314A6","0123F2E4462997EB155B7C50EC148767"],
+ "leafs2":["8520AF39790A4EC959550166DC5DEADE", "6323DA7D2B542B5D09630F87351BEA41"],
+ "spines":["CD0EADBEEA126915EA78E0FB4DC776CA", "2568DB4A33177968A78C4FD5A8232159"]}
+
+for tag in tags_common+tags_leaf1:
+ tag_label = tag['key']
+ tag_value = tag['value']
+ interface_id = ''
+ for leaf in devices['leafs1']:
+ device_id = leaf
+ clnt.api.tag_assignment_config(element_type, workspace_id, tag_label, tag_value, device_id, interface_id)
+for tag in tags_common+tags_leaf2:
+ tag_label = tag['key']
+ tag_value = tag['value']
+ interface_id = ''
+ for leaf in devices['leafs2']:
+ device_id = leaf
+ clnt.api.tag_assignment_config(element_type, workspace_id, tag_label, tag_value, device_id, interface_id)
+for tag in tags_common+tags_spines:
+ tag_label = tag['key']
+ tag_value = tag['value']
+ interface_id = ''
+ for spine in devices['spines']:
+ device_id = spine
+ clnt.api.tag_assignment_config(element_type, workspace_id, tag_label, tag_value, device_id, interface_id)
+
+### Start build
+request = 'REQUEST_START_BUILD'
+request_id = 'b1'
+description='testing cvprac build'
+clnt.api.workspace_config(workspace_id=workspace_id, display_name=display_name,
+ description=description, request=request, request_id=request_id)
+
+### Check workspace build status and proceed only after it finishes building
+b = 0
+while b == 0:
+ build_id = request_id
+ # Requesting for the build status too fast might fail if the build start didn't finish creating
+ # the build with the request_id/build_id
+ while True:
+ try:
+ request = clnt.api.workspace_build_status(workspace_id, build_id)
+ break
+ except Exception as e:
+ continue
+ if request['value']['state'] == 'BUILD_STATE_SUCCESS':
+ b = b+1
+ else:
+ continue
+
+### Submit workspace
+request = 'REQUEST_SUBMIT'
+request_id = 's1'
+clnt.api.workspace_config(workspace_id=workspace_id,display_name=display_name,description=description,request=request,request_id=request_id)
diff --git a/docs/labs/static/serviceaccount1.png b/docs/labs/static/serviceaccount1.png
new file mode 100644
index 0000000..b6de68b
--- /dev/null
+++ b/docs/labs/static/serviceaccount1.png
Binary files differ
diff --git a/docs/labs/static/serviceaccount2.png b/docs/labs/static/serviceaccount2.png
new file mode 100644
index 0000000..3b49542
--- /dev/null
+++ b/docs/labs/static/serviceaccount2.png
Binary files differ
diff --git a/docs/labs/static/serviceaccount3.png b/docs/labs/static/serviceaccount3.png
new file mode 100644
index 0000000..2eca99a
--- /dev/null
+++ b/docs/labs/static/serviceaccount3.png
Binary files differ