• CloudVision Portal RESTful API Client

 
 
Print Friendly, PDF & Email

Arista Cloudvision® Portal (CVP) provides a central point of management for Arista network switches through shared snippets of configuration (configlets) enabling Network Engineers to provision the network more consistently and efficiently. While CVP highlights a graphical user interface for configuration and management of devices, it also includes a full-featured RESTful API that provides all of the same functionality available via the GUI which can be used to automate workflows and integrate with other tools. CVPRAC is a wrapper client for CVP’s RESTful APIs which greatly simplifies usage of the API and more elegantly handles the connections to the CVP nodes.

CVPRAC is available in multiple languages (Python, Ruby and Golang), and provides a client that handles connections to nodes in addition to many helpful API wrapper methods that make getting started with the CVP RESTful API much easier. CVPRAC enables engineers to work with CVP using the API without having to dive into the details of individual API calls or write any code to manage sessions to what could be a multi node CVP appliance. The article’s example code is in Python, and the Appendix contains more complete examples for all three supported languages.

Installation

pip install cvprac

CVPRAC Client Connection

Applications that leverage the CVP RESTful APIs can execute on CVP or on any server in your data center with connectivity to the CVP appliance. CVPRAC improves management of login sessions to CVP nodes by providing a client that automatically retries GET and POST operations that fail due to transitory errors such as CVP session expiration or a CVP node failure. The client allows the developer to focus on using the GET and POST operations to build CVP applications without having to code error and session handling features nor worry about which CVP node is processing the request.

The first step to using CVPRAC is to instantiate the client object (CvpClient) which is responsible for logging, managing the connections to the nodes, and providing low level methods such as connect, get, and post that allow the user to make the RESTful API calls to CVP. When the client class is instantiated the logging is configured. Either syslog, file logging, both, or none can be enabled. If neither syslog nor filename is specified then no logging will be performed. The below example instantiates the client with both syslog and file logging.

from cvprac.cvp_client import CvpClient
client = CvpClient(syslog=True, filename='/path_to_file/cvprac_log')

The client connection method can connect to one or more CVP nodes and allows the user to specify the username, password, connection timeout (default 10 seconds), and port number for the connections. Below we will use the client to connect to the nodes by adding to the example above.

client.connect(['192.168.1.101', '192.168.1.102', '192.168.1.103'], 'cvpuser', 'cvppass')

The connection is to a three node CVP appliance using https with the default port and connection timeout. As of version 2017 and onward the CVP API only supports https for connections. For this reason CVPRAC defaults to using https for the connection protocol. CVPRAC will also attempt to fall back to http if there is an error while connecting via https. This helps maintain supportability in the case someone is running an older version of CVP.

At this point we are ready to start using the CVPRAC API wrappers to begin interacting with our CVP appliance, but let’s set one more variable on our client. The connections to the CVP nodes are handled by the client. If a request to a node hits a timeout or logout error the request will be retried on the same node (after logging back in for the logout case) up to NUM_RETRY_REQUESTS times. NUM_RETRY_REQUESTS is a CvpClient class variable that defaults to 3. If a timeout/logout error occurs more than NUM_RETRY_REQUESTS times, or the request receives a connection/http error, the client will move to the next available node in the list and try again. If any of the errors persists across all nodes then the GET or POST request will fail and the last error that occurred will be raised. Let’s set our NUM_RETRY_REQUESTS to 4 for our client.

client.NUM_RETRY_REQUESTS = 4

Using CVPRAC API wrapper functions

The CVPRAC API routines make interfacing with CVP even easier for users who want to jump right into developing tool/integration functionality without having to dig into the details of how each RESTful API call works. It also simplifies complicated tasks that seem very simple when using the GUI, but require several API calls with parameters that are not easily identified, to complete.

Let’s start by using the previously instantiated client to get the inventory using the CVPRAC API get_inventory method.

from pprint import pprint
inventory = client.api.get_inventory()
pprint(inventory)
[{u'fqdn': u'switch1',
  u'ipAddress': u'192.168.1.10',
  u'modelName': u'vEOS',
  u'serialNumber': u'ABC12345678',
  u'systemMacAddress': u'11:11:22:22:33:33',
  u'type': u'netelement',
  u'version': u'4.17.1F',
  ** output truncated for brevity. **},
 {u'fqdn': u'switch2',
  u'ipAddress': u'192.168.1.20',
  u'modelName': u'vEOS',
  u'serialNumber': u'ABC87654321',
  u'systemMacAddress': u'44:44:55:55:66:66',
  u'type': u'netelement',
  u'version': u'4.17.1F',
  ** output truncated for brevity. **}]

This convenient API method allows a developer to use the getInventory API endpoint without having to know that it requires two parameters; startIndex and endIndex. The API method allows these parameters to be sent to the function but by default sets them both to 0 which will return the full list of devices.

The API also contains helper functions for some more complex actions. For example the steps to create a configlet and add it to a device in CVP via the GUI are pretty straightforward, but following the same steps via the API are not as intuitive. The steps in the GUI are to create the configlet, select the device, select the configlet, select apply, then save the changes which will spawn a task that needs to be executed for the new configs to be applied to the device. Using the proper API calls to replicate these steps isn’t as obvious.

Fortunately the CVPRAC API allows the user to avoid this level of detail when handling what seems like a simple task of applying a new configlet to a device. The API contains helper functions called add_configlet, apply_configlets_to_device, and execute_task that take the provided information and make the necessary API calls to complete the task. Let’s use our previously defined client from above to walk through these steps.

First let’s create the configlet then store the new configlets info in a variable. Take note of the netElementCount. It will be referenced again later.

configlet_name = 'VLANS'
configlet_content = 'vlan 100\n   name DEMO\nend'
result = client.api.add_configlet(configlet_name, configlet_content)
pprint(result)
u'new_configlet_key'
configlet_info = client.api.get_configlet_by_name(configlet_name)
pprint(configlet_info)
{u'config': u'vlan 100\n   name DEMO\nend',
 u'containerCount': 0,
 u'id': 3,
 u'key': u'new_configlet_key',
 u'name': u'VLANS',
 u'netElementCount': 0,
 ** output truncated for brevity. **}

Next let’s say we know we want to apply this configlet to a switch named switch1.

device_info = client.api.get_device_by_name('switch1')

At this point we can use the apply_configlets_to_device CVPRAC API wrapper function to handle the steps of applying our new configlet to our switch. The method takes a string for an identifier, the device info as a dictionary and a list of configlet info (only one item in the list in our case), in addition to a parameter create_task that defaults to True. The create_task parameter will save the changes made and then return the task spawned to the user after saving the changes.

response_data = client.api.apply_configlets_to_device('APP_NAME', device_info, [configlet_info], create_task=True)
pprint(response_data)
{u'data': {u'status': u'success', u'taskIds': [u'10']}}

The returned data will include a status and a list of task ids created as a result of applying the configlet to the device. In this example the create_task parameter was set to True so the response should contain a task id to be executed. The update of the configuration on the device will not run until the task is executed. The CVPRAC API also contains a method for executing a task called execute_task. All that is required is for the task id returned from applying the config to the device to be passed to execute_task and the full update is complete without ever using the GUI.

client.api.execute_task(response_data['data']['taskIds'][0])
configlet_info = client.api.get_configlet_by_name(configlet_name)
pprint(configlet_info)
{u'config': u'vlan 100\n   name DEMO\nend',
 u'containerCount': 0,
 u'id': 3,
 u'key': u'new_configlet_key',
 u'name': u'VLANS',
 u'netElementCount': 1,
 ** output truncated for brevity. **}

Note that after the task has executed the netElementCount is now 1 because the new configlet is now applied to one device.

The existing CVPRAC API wrappers provide many of the common tasks used for interacting with CVP but it does not cover everything. If a use case arises that does not fit into the already available methods in CVPRAC a user can easily add additional API calls by simply sending the proper endpoint and parameters to the client get or post functions. An example of using the client’s lower level get function is shown below to get the CVP appliance info.

client.get('/cvpInfo/getCvpInfo.do')
{u'version': u'cvp_version', u'appVersion': u'app_version'}

Summary

CVPRAC is a great place to start when developing applications or automation to interface with CVP via the API. With the CVPRAC wrapper API methods and the lower level client methods a user can build out whatever workflow is needed to more seamlessly integrate into their business environment. For more information about the CVP RESTful API in general please reference the EOS Central Article here. Some integration examples that use CVPRAC can be found at the links below.

https://eos.arista.com/export-cvp-functionality-ansible/ https://eos.arista.com/cvp-telemetry-ztp-ansible-environment/

Appendix

Full Python Example Code

from pprint import pprint
from cvprac.cvp_client import CvpClient
from cvprac.cvp_client_errors import CvpApiError

# Instantiate Client and connect to CVP nodes
client = CvpClient()
client.connect(['192.168.1.101', '192.168.1.102', '192.168.1.103'], 'cvpuser', 'cvppass')

# Define new configlet name and content
configlet_name = 'VLANS'
configlet_content = 'vlan 100\n   name DEMO\nend'

node = 'switch1'

# Check if we already have a configlet by this name
try:
    configlet = client.api.get_configlet_by_name(configlet_name)
except CvpApiError as err:
    if 'Entity does not exist' in err.msg:
        # Configlet doesn't exist let's create one
        result = client.api.add_configlet(configlet_name, configlet_content)
        pprint(result)
    else:
        raise
else:
    # Configlet does exist, let's update the content
    result = client.api.update_configlet(configlet_content, configlet['key'], configlet_name)
    pprint(result)

configlet_info = client.api.get_configlet_by_name(configlet_name)
pprint(configlet_info)

# Get our device
device_info = client.api.get_device_by_name(node)

# Check if the configlet is already applied to this device
current_configlets = client.api.get_configlets_by_device_id(device_info['systemMacAddress'])
found = false
for applied_configlet in current_configlets:
    if applied_configlet['key'] == configlet_info['key']:
        found = true

# Apply configlet to our device if it is not already applied
if not found:
    response_data = client.api.apply_configlets_to_device('APP_NAME', device_info,
                                                          [configlet_info], create_task=True)
    pprint(response_data)
    # Execute task returned
    if 'taskIds' in response_data['data'] and response_data['data']['taskIds']:
        client.api.execute_task(response_data['data']['taskIds'][0])

Full Ruby Example Code

#!/usr/bin/env ruby
require ‘cvprac’
require 'pp'

# Instantiate Client and connect to CVP nodes
cvp = CvpClient.new
cvp.connect(['192.168.1.101', '192.168.1.102, '192.168.1.103'],
            'cvpuser', 'cvppass')

# Define new configlet name and content
name = "VLAN 100"
content = <  err
  if err.message.include? 'Entity does not exist'
    # Configlet doesn't exist let's create one
    result = cvp.api.add_configlet(name, content)
  else
    raise
  end
else
  # Configlet does exist, let's update the content
  configlet = cvp.api.get_configlet_by_name(name)
  result = cvp.api.update_configlet(name, configlet['key'], content)
  pp(result)
end

# Get our device
net_elem = cvp.api.get_device_by_name(node)

# Check if the configlet is already applied to this device
current = cvp.api.get_configlets_by_device_id(net_elem['systemMacAddress'])
found = false
current.each do |cfglt|
  found = true if configlet['key'] == cfglt['key']
end

# Apply configlet to our device if it is not already applied
result = cvp.api.apply_configlets_to_device(
  File.basename($PROGRAM_NAME), net_elem, [configlet]) unless found

Full Go Example Code

package main

import (
	"log"
	"github.com/aristanetworks/go-cvprac/client"
)

func main() {
        // Instantiate Client and connect to CVP nodes
	hosts := []string{"192.168.1.101"}
	cvpClient, _ := client.NewCvpClient(
		client.Protocol("https"),
		client.Port(443),
		client.Hosts(hosts...),
		client.Debug(false))
	if err := cvpClient.Connect("cvpuser", "cvppass"); err != nil {
		log.Fatalf("ERROR: %s", err)
	}

        // Define new configlet name and content
	name := "VLAN 100"
	content := "vlan 100\n   name DEMO_traffic\nend"

	node := "switch1"

	// Check if we already have a configlet by this name
	configlet, err := cvpClient.API.GetConfigletByName(name)
	if err != nil {
		log.Fatalf("Failed to get Configlet: %s", err)
	}
	if configlet == nil {
		// Configlet doesn't exist let's create one
		configlet, err = cvpClient.API.AddConfiglet(name, content)
		if err != nil {
			log.Fatalf("Failed to Add Configlet: %s", err)
		}
	} else {
		// Configlet does exist, let's update the content
		if err = cvpClient.API.UpdateConfiglet(content, name, configlet.Key); err != nil {
			log.Fatalf("Failed to get Update Configlet: %s", err)
		}
	}

	// Get our device
	netElement, err := cvpClient.API.GetDeviceByName(node)
	if err != nil {
		log.Fatalf("Failed to get Device %s: %s", node, err)
	}
	if netElement == nil {
		log.Fatalf("No device %s found", node)
	}

        // Check if the configlet is already applied to this device
        configletList, err := cvpClient.API.GetConfigletsByDeviceID(netElement.SystemMacAddress)
	if err != nil {
		log.Fatalf("Failed to get Configlet list for device %s. %s",
			netElement.SystemMacAddress, err)
	}
        var found bool
	for _, cfglt := range configletList {
		if cfglt.Key == configlet.Key {
                     found = true
		}
	}

if !found { // Apply configlet to our device _, err = cvpClient.API.ApplyConfigletToDevice(“AppName”, netElement, configlet, true) if err != nil { log.Fatalf(“Failed to apply configlet to device %s”, node) }
} }

Follow

Get every new post on this blog delivered to your Inbox.

Join other followers: