Skip to content

Commit

Permalink
Merge pull request #158 from aristanetworks/release-1.0.7
Browse files Browse the repository at this point in the history
Release 1.0.7
  • Loading branch information
mharista authored Jul 1, 2021
2 parents e8900dd + d1a8e89 commit 7023169
Show file tree
Hide file tree
Showing 46 changed files with 1,393 additions and 32 deletions.
7 changes: 7 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ include LICENSE
include VERSION
include .pylintrc
recursive-include docs *.rst
recursive-include docs *.cfg
recursive-include docs *.csv
recursive-include docs *.md
recursive-include docs *.png
recursive-include docs *.py
recursive-include docs *.tok
recursive-include docs *.txt
recursive-include test *.py
recursive-include test *.yaml
recursive-include test *.swix
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.6
1.0.7
2 changes: 1 addition & 1 deletion cvprac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
''' RESTful API Client class for Cloudvision(R) Portal
'''

__version__ = '1.0.6'
__version__ = '1.0.7'
__author__ = 'Arista Networks, Inc.'
121 changes: 96 additions & 25 deletions cvprac/cvp_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,11 +810,15 @@ def get_devices_in_container(self, name):
devices.append(device)
return devices

def get_device_by_name(self, fqdn):
def get_device_by_name(self, fqdn, search_by_hostname=False):
''' Returns the net element device dict for the devices fqdn name.
Args:
fqdn (str): Fully qualified domain name of the device.
fqdn (str): Fully qualified domain name or hostname of the
device.
search_by_hostname (boolean): if set True will attempt to split
the fqdn string to match on the hostname portion
specifically which should be the first component
Returns:
device (dict): The net element device dict for the device if
Expand All @@ -826,9 +830,14 @@ def get_device_by_name(self, fqdn):
device = {}
if 'netElementList' in data:
for netelem in data['netElementList']:
if netelem['fqdn'] == fqdn:
device = netelem
break
if not search_by_hostname:
if netelem['fqdn'] == fqdn:
device = netelem
break
else:
if netelem['fqdn'].split('.')[0] == fqdn:
device = netelem
break
return device

def get_device_by_mac(self, device_mac):
Expand All @@ -852,6 +861,27 @@ def get_device_by_mac(self, device_mac):
break
return device

def get_device_by_serial(self, device_serial):
''' Returns the net element device dict for the devices serial number.
Args:
device_serial (str): Serial number of the device.
Returns:
device (dict): The net element device dict for the device if
otherwise returns an empty hash.
'''
self.log.debug('get_device_by_serial: Serial Number: %s'
% device_serial)
data = self.search_topology(device_serial)
device = {}
if 'netElementList' in data:
for netelem in data['netElementList']:
if netelem['serialNumber'] == device_serial:
device = netelem
break
return device

def get_device_configuration(self, device_mac):
''' Returns the running configuration for the device provided.
Expand Down Expand Up @@ -1009,21 +1039,24 @@ def add_configlet_builder(self, name, config, draft=False, form=None):
draft (bool): If builder is a draft
form (list): Array/list of form data
Parameters:
fieldId (str):
fieldLabel (str):
value (str):
type (str): {
'Text box',
fieldId (str): "",
fieldLabel (str): "",
value (str): "",
type (str): "", (Options below)
('Text box',
'Text area',
'Drop down',
'Check box',
'Radio button',
'IP address',
'Password'
}
validation:
mandatory (boolean):
helpText (str)
'Password')
validation: {
mandatory (boolean): true,
},
helpText (str): "",
depends (str): "",
dataValidationErrorExist (boolean): true,
dataValidation (string): ""
Returns:
key (str): The key for the configlet
Expand Down Expand Up @@ -1102,27 +1135,51 @@ def update_configlet(self, config, key, name, wait_task_ids=False):
timeout=self.request_timeout)

def update_configlet_builder(self, name, key, config, draft=False,
wait_for_task=False):
wait_for_task=False, form=None):
''' Update an existing configlet builder.
Args:
config (str): Contents of the configlet builder configuration
key: (str): key/id of the configlet builder to be updated
name: (str): name of the configlet builder
draft (boolean): is update a draft
wait_for_task (boolean): wait for task IDs to be generated
form (list): Array/list of form data
Parameters:
fieldId (str): "",
fieldLabel (str): "",
value (str): "",
type (str): "", (Options below)
('Text box',
'Text area',
'Drop down',
'Check box',
'Radio button',
'IP address',
'Password')
validation: {
mandatory (boolean): true,
},
helpText (str): "",
depends (str): "",
dataValidationErrorExist (boolean): true,
dataValidation (string): ""
'''
if not form:
form = []

data = {
"name": name,
"waitForTaskIds": wait_for_task,
"data": {
"formList": form,
"main_script": {
"data": config
}
}
}
debug_str = 'update_configlet_builder:' \
' config: {} key: {} name: {} '
self.log.debug(debug_str.format(config, key, name))
' config: {} key: {} name: {} form: {}'
self.log.debug(debug_str.format(config, key, name, form))
# Update the configlet builder
url_string = '/configlet/updateConfigletBuilder.do?' \
'isDraft={}&id={}&action=save'
Expand Down Expand Up @@ -1262,7 +1319,7 @@ def _save_topology_v2(self, data):
return self.clnt.post(url, data=data, timeout=self.request_timeout)

def apply_configlets_to_device(self, app_name, dev, new_configlets,
create_task=True):
create_task=True, reorder_configlets=False):
''' Apply the configlets to the device.
Args:
Expand All @@ -1271,6 +1328,18 @@ def apply_configlets_to_device(self, app_name, dev, new_configlets,
new_configlets (list): List of configlet name and key pairs
create_task (bool): Determines whether or not to execute a save
and create the tasks (if any)
reorder_configlets (bool): Defaults to False. To use this
parameter you must first get the full list of configlets
applied to the device (for example via the
get_configlets_by_device_id function) and provide the
full list of configlets (in addition to any new configlets
being applied) in the desired order as the new_configlets
parameter. It is also important to keep in mind configlets
that are applied to parent containers because they will
be applied before configlets applied to the device
directly. Set this parameter to True only with the full
list of configlets being applied to the device provided
via the new_configlets parameter.
Returns:
response (dict): A dict that contains a status and a list of
Expand All @@ -1280,15 +1349,17 @@ def apply_configlets_to_device(self, app_name, dev, new_configlets,
'''
self.log.debug('apply_configlets_to_device: dev: %s names: %s' %
(dev, new_configlets))
# Get all the configlets assigned to the device.
configlets = self.get_configlets_by_device_id(dev['systemMacAddress'])

# Get a list of the names and keys of the configlets
cnames = []
ckeys = []
for configlet in configlets:
cnames.append(configlet['name'])
ckeys.append(configlet['key'])

if not reorder_configlets:
# Get all the configlets assigned to the device.
configlets = self.get_configlets_by_device_id(
dev['systemMacAddress'])
for configlet in configlets:
cnames.append(configlet['name'])
ckeys.append(configlet['key'])

# Add the new configlets to the end of the arrays
for entry in new_configlets:
Expand Down
53 changes: 53 additions & 0 deletions docs/labs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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.

## 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)

### User/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)
```
17 changes: 17 additions & 0 deletions docs/labs/lab01-cvp-info/get_cvp_info.py
Original file line number Diff line number Diff line change
@@ -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)
57 changes: 57 additions & 0 deletions docs/labs/lab02-inventory-operations/compliance_check.py
Original file line number Diff line number Diff line change
@@ -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])
21 changes: 21 additions & 0 deletions docs/labs/lab02-inventory-operations/remove_all_devices.py
Original file line number Diff line number Diff line change
@@ -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(nodes=['cvp1'], username="username",password="password")

inventory = clnt.api.get_inventory()

devices = []
for netelement in inventory:
devices.append(netelement['systemMacAddress'])

clnt.api.delete_devices(devices)
Loading

0 comments on commit 7023169

Please sign in to comment.