scrapli#

Примечание

Основы scrapli с использованием синхронных вариантов транспорта рассматриваются в книге Python для сетевых инженеров.

scrapli поддерживает разные варианты подключения: system, paramiko, ssh2, telnet, asyncssh, asynctelnet. Тут рассматривается только asyncssh и asynctelnet.

Доступные варианты асинхронного транспорта:

  • asyncssh

  • asynctelnet (реализация telnet на основе asyncio)

Примечание

Рассматривается scrapli версии 2021.1.30.

Для асинхронного транспорта, как и для синхронного, в scrapli есть два варианта подключения: используя общий класс AsyncScrapli, который выбирает нужный driver по параметру platform или конкретный driver, например, AsyncIOSXEDriver. При этом параметры передаются те же самые и конкретному драйверу и Scrapli.

В целом методы и параметры методов такие же как и в синхронном варианте scrapli, отличается транспорт, драйверы и то, что методы являются сопрограммами.

Параметры подключения#

Основные параметры подключения:

  • host - IP-адрес или имя хоста

  • auth_username - имя пользователя

  • auth_password - пароль

  • auth_secondary - пароль на enable

  • auth_strict_key - контролирует проверку SSH ключей сервера, а именно разрешать ли подключаться к серверам ключ которых не сохранен в ssh/known_hosts. False - разрешить подключение (по умолчанию значение True)

  • platform - нужно указывать при использовании AsyncScrapli

  • transport - для async варианта transport указывать обязательно: asyncssh или asynctelnet

  • transport_options - опции для конкретного транспорта

Процесс подключения немного отличается в зависимости от того используется асинхронный менеджер контекста или нет. При подключении без менеджера контекста, сначала надо передать параметры драйверу или AsyncScrapli, а затем вызвать метод open:

In [1]: from scrapli import AsyncScrapli

In [2]: r1 = {
   ...:    "host": "192.168.100.1",
   ...:    "auth_username": "cisco",
   ...:    "auth_password": "cisco",
   ...:    "auth_secondary": "cisco",
   ...:    "auth_strict_key": False,
   ...:    "platform": "cisco_iosxe",
   ...:    "transport": "asyncssh",
   ...: }

In [3]: ssh = AsyncScrapli(**r1)

In [5]: await ssh.open()

После этого можно отправлять команды:

In [6]: await ssh.get_prompt()
Out[6]: 'R1#'

In [7]: await ssh.close()

При использовании асинхронного менеджера контекста, open вызывать не надо:

In [8]: async with AsyncScrapli(**r1) as ssh:
   ...:     print(await ssh.get_prompt())
R1#

Примечание

Отличия в подключении с менеджером контекста и без это особенность классов для работы с асинхронным подключением.

Использование драйвера#

Доступные async драйверы:

Оборудование

Драйвер

Параметр platform

Cisco IOS-XE

AsyncIOSXEDriver

cisco_iosxe

Cisco NX-OS

AsyncNXOSDriver

cisco_nxos

Cisco IOS-XR

AsyncIOSXRDriver

cisco_iosxr

Arista EOS

AsyncEOSDriver

arista_eos

Juniper JunOS

AsyncJunosDriver

juniper_junos

Пример подключения с использованием драйвера IOSXEDriver (технически подключение выполняется к Cisco IOS):

In [10]: from scrapli.driver.core import AsyncIOSXEDriver

In [11]: r1_driver = {
    ...:    "host": "192.168.100.1",
    ...:    "auth_username": "cisco",
    ...:    "auth_password": "cisco",
    ...:    "auth_secondary": "cisco",
    ...:    "auth_strict_key": False,
    ...:    "transport": "asyncssh",
    ...: }

In [12]: async with AsyncIOSXEDriver(**r1_driver) as ssh:
    ...:     print(await ssh.get_prompt())
R1#

Пример базового использования scrapli#

В остальном, принципы работы те же, что и с синхронным вариантом.

Пример подключения к одному устройству с помощью asyncssh и AsyncScrapli:

import asyncio
from scrapli import AsyncScrapli
from scrapli.exceptions import ScrapliException

r1 = {
    "host": "192.168.100.1",
    "auth_username": "cisco",
    "auth_password": "cisco",
    "auth_secondary": "cisco",
    "auth_strict_key": False,
    "timeout_socket": 5,  # timeout for establishing socket/initial connection in seconds
    "timeout_transport": 10,  # timeout for ssh|telnet transport in seconds
    "platform": "cisco_iosxe",
    "transport": "asyncssh",
}


async def send_show(device, command):
    try:
        async with AsyncScrapli(**device) as conn:
            result = await conn.send_command(command)
            return result.result
    except ScrapliException as error:
        print(error, device["host"])


if __name__ == "__main__":
    output = asyncio.run(send_show(r1, "show ip int br"))
    print(output)

Получение структурированного вывода с помощью TextFSM#

Пример получения структурированного вывода с помощью TextFSM. Функция возвращает структурированный вывод, если удалось его получить (есть шаблон и вернулось не пустое значение) и обычный вывод команды, если нет:

from pprint import pprint
import asyncio
from scrapli import AsyncScrapli
from scrapli.exceptions import ScrapliException

r1 = {
    "host": "192.168.100.1",
    "auth_username": "cisco",
    "auth_password": "cisco",
    "auth_secondary": "cisco",
    "auth_strict_key": False,
    "timeout_socket": 5,  # timeout for establishing socket/initial connection in seconds
    "timeout_transport": 10,  # timeout for ssh|telnet transport in seconds
    "platform": "cisco_iosxe",
    "transport": "asyncssh",
}

async def send_show(device, show_commands):
    cmd_dict = {}
    if type(show_commands) == str:
        show_commands = [show_commands]
    try:
        async with AsyncScrapli(**device) as ssh:
            for cmd in show_commands:
                reply = await ssh.send_command(cmd)
                parsed_data = reply.textfsm_parse_output()
                if parsed_data:
                    cmd_dict[cmd] = parsed_data
                else:
                    cmd_dict[cmd] = reply.result
        return cmd_dict
    except ScrapliException as error:
        print(error, device["host"])


if __name__ == "__main__":
    output = asyncio.run(send_show(r1, ["sh run | i ^interface", "show ip int br"]))
    pprint(output)

Подключение к нескольким устройствам#

Пример подключения к нескольким устройствам:

from pprint import pprint
import asyncio

import yaml
from scrapli import AsyncScrapli
from scrapli.exceptions import ScrapliException


async def send_show(device, show_commands):
    cmd_dict = {}
    if type(show_commands) == str:
        show_commands = [show_commands]
    try:
        async with AsyncScrapli(**device) as ssh:
            for cmd in show_commands:
                reply = await ssh.send_command(cmd)
                cmd_dict[cmd] = reply.result
        return cmd_dict
    except ScrapliException as error:
        print(error, device["host"])


async def send_command_to_devices(devices, commands):
    coroutines = [send_show(device, commands) for device in devices]
    result = await asyncio.gather(*coroutines)
    return result


if __name__ == "__main__":
    with open("devices_async.yaml") as f:
        devices = yaml.safe_load(f)
    result = asyncio.run(send_command_to_devices(devices, "sh ip int br"))
    pprint(result, width=120)

Файл devices_async.yaml:

- host: 192.168.100.1
  auth_username: cisco
  auth_password: cisco
  auth_secondary: cisco
  auth_strict_key: false
  timeout_socket: 5
  timeout_transport: 10
  platform: cisco_iosxe
  transport: asyncssh
- host: 192.168.100.2
  auth_username: cisco
  auth_password: cisco
  auth_secondary: cisco
  auth_strict_key: false
  timeout_socket: 5
  timeout_transport: 10
  platform: cisco_iosxe
  transport: asyncssh
- host: 192.168.100.3
  auth_username: cisco
  auth_password: cisco
  auth_secondary: cisco
  auth_strict_key: false
  timeout_socket: 5
  timeout_transport: 10
  platform: cisco_iosxe
  transport: asyncssh

Подключение с транспортом asynctelnet#

При подключении asynctelnet надо указать транспорт asynctelnet и порт 23. Кроме того, надо данный момент (scrapli 2021.1.30) при подключении asynctelnet к недоступному адресу таймаут будет через 2 минуты, чтобы уменьшить его, можно использовать async_timeout:

import asyncio
from scrapli.driver.core import AsyncIOSXEDriver
from scrapli.exceptions import ScrapliException
from async_timeout import timeout

r1 = {
    "host": "192.168.100.11",
    "auth_username": "cisco",
    "auth_password": "cisco",
    "auth_secondary": "cisco",
    "auth_strict_key": False,
    "transport": "asynctelnet",
    "port": 23,
}


async def send_show(device, command):
    # На данный момент (scrapli 2021.1.30) таймаут при подключении к недоступному
    # хосту будет 2 минуты, поэтому пока что лучше добавлять wait_for или
    # async_timeout вокруг подключения
    try:
        async with timeout(10):
            async with AsyncIOSXEDriver(**device) as ssh:
                result = await ssh.send_command(command)
                return result.result
    except ScrapliException as error:
        print(error, device["host"])
    except asyncio.exceptions.TimeoutError:
        print("asyncio.exceptions.TimeoutError", device["host"])


if __name__ == "__main__":
    output = asyncio.run(send_show(r1, "show ip int br"))
    print(output)