Привет, Habr! Меня зовут Дмитрий, и я разработчик DCImanager — панели для управления оборудованием от ISPsystem. Довольно продолжительное время в команде я провёл, разрабатывая софт для управления коммутаторами. Вместе мы пережили взлеты и падения: от написания сервисов для управления железом до падения офисной сети и часовых свиданий в серверной в надежде не потерять своих любимых.  И вот настало время тестирования. Часть обработчиков мы смогли покрыть готовыми решениями для тестирования. Но с Juniper так не получилось. Ресерч и реализация послужили идеей для написания этой статьи. Если интересно, добро пожаловать под кат! DCImanager работает с разными видами оборудования: коммутаторы, распределители питания, серверы. Сейчас DCImanager поддерживает четыре обработчика коммутаторов. Два по протоколу SNMP (Cisco Catalyst и общий snmp common) и еще два по протоколу NETCONF (Juniper с поддержкой ELS и без). Всю работу с оборудованием мы обильно покрываем тестами. Использовать для автоматического тестирования реальное оборудование не получается: тесты запускаются на каждый пуш и проходят параллельно. Поэтому мы стараемся использовать эмуляторы. Обработчики с поддержкой протокола SNMP мы смогли покрыть тестами, используя библиотеку SNMP Agent Simulator. А вот с Juniper’ом возникли проблемы. Поискав готовые решения, выбрали пару библиотек, но одна из них не завелась, а другая делала не то, что нужно — я потратил больше времени на попытки оживить это чудо. Встал вопрос, а как же эмулировать работу коммутаторов Juniper? Juniper работает по протоколу NETCONF, который, в свою очередь, работает поверх SSH. В голове промелькнула мысль написать небольшой сервис, который будет работать поверх SSH и эмулировать работу коммутатора. Соответственно, нам нужен сам сервис, а также «снимок» Juniper для эмуляции данных. Сейчас мы активно пополняем «зоопарк» обработчиков для работы с коммутаторами. Скоро у нас появятся новые обработчики, и не все из них мы сможем покрыть готовыми решениями для тестирования. Однако можно попробовать написать общую архитектуру сервиса, который будет имитировать работу различных устройств по разным протоколам.  В самом простом варианте — фабрика, которая в зависимости от протокола и обработчика (некоторые коммутаторы могут работать по нескольким протоколам), будет возвращать объект коммутатора, в котором уже будет реализована вся логика его поведения. В случае с Juniper, это небольшой синтаксический анализатор запроса. В зависимости от входного rpc-запроса с параметрами, он будет выполнять необходимые действия.  Важное ограничение: мы не сможем полностью имитировать работу коммутатора. На описание всей логики уйдёт много времени, а добавив новую функциональность в реальный обработчик, нам придется править и mock коммутатора.  Взгляд пал на библиотеку paramiko, которая предоставляет удобный интерфейс работы по протоколу SSH. Для начала хотелось не разносить архитектуру, а проверить базовые вещи, например, коннект и какой-нибудь простой запрос. Мы же всё-таки ресерчем занимаемся. Поэтому над авторизацией не заморачиваемся: простой ServerInterface и socket-сервер в связке дают нам что-то похожее на работающий вариант: Примерная реализация того, что хотелось бы видеть, но выглядит страшно При подключении клиента к серверу, второй должен ответить списком своих capabilities (возможностей). Например таким: Да, это XML ]]>]]> Если что, код работает нестабильно. В данной реализации есть проблема с закрытием сокета. Нашел пару зарегистрированных issues в paramiko с этой проблемой. Отложил ненадолго, решив проверить оставшийся вариант. Козырь в рукаве — Twisted. Это фреймворк разработки сетевых приложений с поддержкой большого количества протоколов. У него есть обширная документация и замечательный модуль Cred, который нам и поможет. Cred — это механизм аутентификации, позволяющий различным сетевым протоколам подключаться к системе в зависимости от ваших требований.  Для организации всей логики используется Realm — часть приложения, отвечающая за бизнес-логику и доступ к ее объектам. Но обо всем по порядку. Ядром входа в систему является Portal. Если мы хотим написать надстройку над сетевым протоколом, определяем стандартный Portal. В нём уже есть методы:  Для привязки бизнес-логики к системе аутентификации используется Realm-объект. Так как клиент уже авторизован, здесь начинается логика нашей надстройки над SSH. Данный интерфейс имеет всего один метод requestAvatar, который вызывается при успешной авторизации в Portal и возвращает основной объект — SwitchProtocolAvatar: Самая простая реализация Realm-объекта, возвращающая необходимый Avatar За управление бизнес-логикой отвечают специальные объекты — Avatar`ы. В нашем случае здесь начинается надстройка над протоколом SSH. Когда отправляется запрос, данные попадают в SwitchProtocolAvatar, который проверяет подсистему запроса и обновляет конфигурацию: Проверяем подсистему и обновляем конфигурацию при условии, что данный обработчик коммутатора работает по NETCONF К слову о протоколах. Не забываем, что мы работаем с NETCONF, и приступаем к выполнению. Для написания надстроек над уже существующими протоколами и реализации своей логики используется Protocol. Интерфейс данного класса простой: Минимальная реализация обертки над протоколом. Убрал лишнюю логику для наглядности Начинаем сворачивать нашу матрешку. Так как мы используем надстройку над SSH, то нам необходимо реализовать логику SSH-сервера. В нём мы определим ключи для сервера и обработчики для служб SSH. Реализация данного класса не сильно нас интересует, так как авторизация будет по паролю: Реализация SSH-сервера Для работы SSH-сервера необходимо определить логику сессий, которая работает вне зависимости от того, по какому протоколу к нам пришли, и какой интерфейс запрашивается: Логика сессий для всех описываемых интерфейсов Чуть не забыл о самом обработчике. После всех проверок и авторизаций логика переходит к объекту, эмулирующему работу коммутатора. Тут можно определить логику обработки запросов: получение или редактирование интерфейсов, конфигурация устройства и т. д.  Основная логика обработчика. Вырезал всю функциональность и обработку запросов, оставив только получение capabilities Ну и наконец-то сращиваем всё это вместе. Регистрируем адаптер сессии (описывает поведение при подключении), определяем метод подключения по имени пользователя и паролю, настраиваем Portal и запускаем наш сервис: Настройка и запуск сервера Запускаем mock-сервер. Для проверки работоспособности можно подключиться с помощью библиотеки ncclient. Обычной проверки соединения и просмотра capabilities сервера хватит: Подключаемся к mock-серверу по протоколу NETCONF и выводим список capabilities сервера Сам результат запроса представлен ниже. Мы успешно установили соединение, а сервер отдал нам список своих capabilities: Capabilities сервера У данного решения достаточно плюсов и минусов. С одной стороны, мы тратим много времени на реализацию и описание всей логики обработки запросов. С другой — получаем возможность гибкой настройки и эмуляции поведения. Но главное — это масштабируемость. Фреймворк Twisted обладает богатой функциональностью и поддерживает большое число протоколов, поэтому можно без проблем описывать новые интерфейсы обработчиков. А если всё хорошо продумать, данную архитектуру можно использовать не только для работы с коммутаторами, но и для другого оборудования. Хотелось бы узнать мнение читателей. Делали ли вы подобное и если да, то какие технологии использовали и как выстраивали процесс тестирования? Backend разработчик