diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-05-11 09:04:53 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-05-13 09:34:21 +0000 |
commit | 47553c43d71b7b1144f912ab9679f5b60e858fa2 (patch) | |
tree | 08378beaeeea8f9bb2686d3037c7b6f5062bb948 /docs/labs | |
parent | Initial commit. (diff) | |
download | cvprac-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')
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 Binary files differnew file mode 100644 index 0000000..b6de68b --- /dev/null +++ b/docs/labs/static/serviceaccount1.png diff --git a/docs/labs/static/serviceaccount2.png b/docs/labs/static/serviceaccount2.png Binary files differnew file mode 100644 index 0000000..3b49542 --- /dev/null +++ b/docs/labs/static/serviceaccount2.png diff --git a/docs/labs/static/serviceaccount3.png b/docs/labs/static/serviceaccount3.png Binary files differnew file mode 100644 index 0000000..2eca99a --- /dev/null +++ b/docs/labs/static/serviceaccount3.png |