Skip to content

4. Sastre SDK

Marcelo Reis edited this page Jun 3, 2024 · 1 revision

The Cisco SD-WAN Python SDK provides different layers of abstraction on top of vManage REST API:

  • Rest object: Provides vManage session login, logout and executor (get, post, put, delete) functions with enhanced error handling and multi-tenant support.
  • vManage models: Higher level models (python classes) that abstract Rest details such as API endpoint path, input parameters, etc.
  • Sastre tasks: Programmatically executing any of Sastre tasks, with strong parameter validation via pydantic.

The following sections explore those options in more detail with commented code examples.

Low level API and the Rest object

The lower level vManage API abstraction is provided by the Rest class in the cisco_sdwan.base.rest_api module.

from cisco_sdwan.base.rest_api import Rest

with Rest(base_url='https://198.18.1.10', username='admin', password='C1sco12345') as api:
    print(api.server_version)

Code highlights:

  • As execution enters the with block, vManage login is performed. Similarly, vManage logout is done on exit (of the with block).
  • If vManage is configured to listen on a non-default port, that can be provided in the base_url parameter. For instance, if vManage is on port 8443 one would use base_url='https://198.18.1.10:8443'.
  • Rest also accepts setting of a timeout value (default is 20s). In multi-tenant environments, tenant_name can be provided to select which tenant to login as, in case a tenant admin account is provided.
  • Inside the with block, the api (Rest) object is used to execute the basic REST methods: get, post, put, delete.
  • The following object properties are also available: server_version, is_multi_tenant and is_provider.

The following exceptions can be raised by Rest() and its methods: RestAPIException(), LoginFailedException() or BadTenantException().

Consider a use-case to retrieve information about a device's control connections. This information is available via vManage realtime REST API; more specifically, a GET request to the '/device/control/connections' endpoint, with a deviceId parameter set to the system IP from the device of interest.

Using the SDK low level APIs, this would translate to the following code:

import json
from cisco_sdwan.base.rest_api import Rest

def print_json(j_obj):
    print(json.dumps(j_obj, indent=2))

with Rest(base_url='https://198.18.1.10', username='admin', password='C1sco12345') as api:
    reply = api.get('device/control/connections', deviceId='10.3.0.1')
    print_json(reply)

Code highlights:

  • As before, inside the with block we have an authenticated session to vManage, available via the api object.
  • The api get method is used to perform a REST GET call to vManage. All positional parameters are joined to define the URL. Keyword arguments are used to define URL parameters.
  • Considering the above, api.get('device/control/connections', deviceId='10.3.0.1') translates into the following REST call: GET 'https://198.18.1.10/dataservice/device/control/connections?deviceId=10.3.0.1'
  • The same result would be obtained with api.get('device', 'control', 'connections', deviceId='10.3.0.1'). Also, leading and trailing '/'s are stripped and not significant.
  • The get method returns the JSON object that is returned by vManage, which contains the information about 10.3.0.1's control connections.
  • Function print_json is included just to make it easier to display the JSON object.

vManage Models in the SDK

The next layer of abstraction is provided by vManage models from the cisco_sdwan.base.models_vmanage module.

The following code achieves a similar result as the previous example but leveraging the DeviceControlConnections class:

from cisco_sdwan.base.rest_api import Rest
from cisco_sdwan.base.models_vmanage import DeviceControlConnections

with Rest(base_url='https://198.18.1.10', username='admin', password='C1sco12345') as api:
    control_connections = DeviceControlConnections.get(api, deviceId='10.3.0.1')
    print(control_connections)

Code highlights:

  • DeviceControlConnections.get returns an instance of DeviceControlConnections, populated with the values retrieved from vManage.
  • Printing DeviceControlConnections will display the JSON data payload. This is equivalent to print_json(reply['data']) in the previous example.

So far you are probably not seeing much benefit when compared to using the Rest object. However, DeviceControlConnections is an OperationalItem, which can be iterated over as a table-like structure by using the following properties and methods:

  • Method field_info: Returns metadata about one or more columns of the table. By default, that is the column title. Optionally, the keyword parameter info can be used to select a different type of metadata, such as info='dataType', which would return the column data types.
  • Method field_value_iter: Iterate over the different rows of the table.
  • Property field_names: A tuple containing all available columns, or field names. These can be used to discover which columns to select and use as parameters for field_info and field_value_iter.
from cisco_sdwan.base.rest_api import Rest
from cisco_sdwan.base.models_vmanage import DeviceControlConnections

with Rest(base_url='https://198.18.1.10', username='admin', password='C1sco12345') as api:
    control_connections = DeviceControlConnections.get(api, deviceId='10.3.0.1')
    
    header = control_connections.field_info('system_ip', 'local_color', 'remote_color', 'state', 'uptime_date')
    print(f"{header[0]:14} {header[1]:12} {header[2]:12} {header[3]:5} {header[4]}")
    for row in control_connections.field_value_iter('system_ip', 'local_color', 'remote_color', 'state', 'uptime_date'):
        print(f"{row.system_ip:14} {row.local_color:12} {row.remote_color:12} {row.state:5} {row.uptime_date}")

Code highlights:

  • Method field_info accepts field names as positional arguments. The list of available field names can be obtained from control_connections.field_names. Field_info returns a tuple where each entry corresponds to the title for the corresponding field name (or column name).
  • Similarly to field_info, field_value_iter accepts field names as positional arguments. It returns an iterator of rows. Each row is a named tuple.

The following shows an example of the output from code snippet above:

Peer System IP Local Color  Remote Color State Up Since
12.12.12.12    mpls         default      up    1646081880000
12.12.12.12    biz-internet default      up    1646081880000
22.22.22.22    mpls         default      up    1646081880000
22.22.22.22    biz-internet default      up    1646081880000
10.10.10.10    mpls         default      up    1646318580000

Finally, method field_value_iter also accepts conversion functions as keyword arguments. For instance, uptime_date can be converted from timestamp to a more user-friendly format.

This is illustrated in the updated version of our code below, which includes datetime_format as a function to convert uptime_date to the "%Y-%m-%d %H:%M:%S %Z" format.

from datetime import datetime, timezone
from cisco_sdwan.base.rest_api import Rest
from cisco_sdwan.base.models_vmanage import DeviceControlConnections

def datetime_format(timestamp):
    if timestamp is None:
        return ""
    
    dt = datetime.fromtimestamp(float(timestamp) / 1000, tz=timezone.utc)
    return f"{dt:%Y-%m-%d %H:%M:%S %Z}"

with Rest(base_url='https://198.18.1.10', username='admin', password='C1sco12345') as api:
    control_connections = DeviceControlConnections.get(api, deviceId='10.3.0.1')
    
    header = control_connections.field_info('system_ip', 'local_color', 'remote_color', 'state', 'uptime_date')
    print(f"{header[0]:14} {header[1]:12} {header[2]:12} {header[3]:5} {header[4]}")
    for row in control_connections.field_value_iter('system_ip', 'local_color', 'remote_color', 'state', 'uptime_date', uptime_date=datetime_format):
        print(f"{row.system_ip:14} {row.local_color:12} {row.remote_color:12} {row.state:5} {row.uptime_date}")

This change would provide the following output:

Peer System IP Local Color  Remote Color State Up Since
12.12.12.12    mpls         default      up    2022-03-03 16:22:00 UTC
12.12.12.12    biz-internet default      up    2022-03-03 16:22:00 UTC
22.22.22.22    mpls         default      up    2022-03-03 16:24:00 UTC
22.22.22.22    biz-internet default      up    2022-03-03 16:24:00 UTC
10.10.10.10    mpls         default      up    2022-03-03 14:43:00 UTC

Sastre Tasks

Moving further up the stack of vManage REST API abstraction layers, we can programmatically call full Sastre tasks. Tasks encapsulate workflows which typically include interactions with multiple vManage models.

The following example shows how to programmatically call a backup task. This task retrieves all known vManage configuration models (or a selected subset), saving as JSON files in the local filesystem.

import logging
from cisco_sdwan.base.rest_api import Rest
from cisco_sdwan.tasks.implementation import TaskBackup, BackupArgs

# Setup logging to visualize progress
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")

# Equivalent to 'sdwan backup all --no-rollover --save-running'
task_args = BackupArgs(
    save_running = True,
    no_rollover = True,
    workdir = 'backup_test',
    tags = ['all']
)

with Rest(base_url='https://198.18.1.10', username='admin', password='C1sco12345') as api:
    task = TaskBackup()
    task_output = task.runner(task_args, api)
    
    if task_output:
        print('\n\n'.join(str(entry) for entry in task_output))
    
    task.log_info(f'Task completed {task.outcome("successfully", "with caveats: {tally}")}')

Code highlights:

  • Tasks have loggers, which can be integrated into your application logging configuration.
  • Each task has a corresponding pydantic model that is used to validate its input parameters. BackupArgs capture arguments for the backup task. ValidationError exception is raised on validation failures.
  • Task execution happens on task.runner
  • Some tasks may have an output (intended to be presented to the user). That is not the case for the backup task, just showcasing the standard procedure to deal with any output that may be returned by the task.

Since logging is configured to output informational-level logging to stdout, running the example code generates the following output:

[INFO] Backup task: vManage URL: "https://198.18.1.10" -> Local workdir: "backup_test"
[INFO] Saved vManage server information
[INFO] Done CFS device configuration vManage
[INFO] Done RFS device configuration vManage
[INFO] Done CFS device configuration vSmart-1
<snip>
[INFO] Saved prefix list index
[INFO] Done prefix list DefaultRoute
[INFO] Done prefix list InfrastructureRoutes
[INFO] Saved local-domain list index
[INFO] Done local-domain list DCLOUD
[INFO] Task completed successfully
Clone this wiki locally