Привет, Хабр! На связи Андрей Золотых, старший инженер по разработке ПО для коммутаторов YADRO KORNFELD. Сегодня я расскажу о пути, который проходит новая фича перед выходом в свет. Разберем весь процесс имплементации, начиная с идеи и заканчивая продакшеном. Особое внимание уделим тестированию, которое в случае сетевого оборудования имеет ряд особенностей. А еще поговорим о специалистах, которые участвуют в процессе: от бизнес-аналитиков до сетевых инженеров.

Проработка требований
Прежде чем приступать к разработке, нам нужно понять, что именно должен включать в себя новый функционал. Поэтому точка входа для инженера — документ PRD (Product Requirements Document), который подробно описывает новую фичу. Над ним работает продуктовая команда — технические эксперты и менеджеры, которые взаимодействуют с заказчиками и составляют дорожную карту развития коммутатора. Разработчики не сразу берут PRD в работу, а согласовывают еще один документ — HLD (High Level Design). Обычно он описывает архитектуру и основные компоненты системы на высоком уровне, без углубления в технические детали реализации. В нашей команде мы называем его карточкой. Она создается на базе PRD-требований и содержит детальное описание реализации новой функции в коммутаторе.
Карточка помогает команде обсудить нюансы реализации функционала и зафиксировать изменения, чтобы любой инженер мог посмотреть подробную информацию и понять, почему было принято то или иное решение.
В стандартной карточке есть следующие основные поля:
Document status — общий статус документа: в бэклоге, новая задача, в работе, готов для QA, отклонен, на тестировании, в реализации, завершен и так далее.
PRD references — ссылка на документ с требованиями к продукту.
Feature branch — ветка фичи.
Feature Lead — тимлид, отвечающий за реализацию фичи.
Cross-review/Approvals — список представителей всех команд, которые участвуют в разработке фичи и работают с документом.
References — связанные с новой фичей задачи, документы, код и так далее.
Team's work items + tasks — тикеты, которые нужны команде для работы.
Context — краткое описание, зачем нужна и как должна работать новая фича.
Functional Requirements — функциональные требования.
Non-functional Requirements — нефункциональные требования.
REST / Open config — интерфейсы управления коммутатором, задействованные в работе фичи, которые нужно поменять.
CLI — список команд, которые относятся к фиче.
Notes — полезные заметки для всей команды, которая участвует в разработке, тестировании и реализации фичи.
В карточке мы также указываем все возможные расхождения в стандартах. К ней обращаются самые разные специалисты: от QA до сетевых инженеров и разработчиков. Поэтому чем лучше мы описали функционал, тем меньше времени инженер потратит на выяснение деталей в документации по стандартам. Из карточки уже понятно, что именно нужно разрабатывать.

Дизайн реализации новой фичи прорабатывается на этапе создания карточки, чтобы не переделывать его в процессе разработки. Мы начинаем с сетевого дизайна, то есть описания работы функционала в соответствии с требованиями RPD.
В качестве примера возьмем функцию BUM Storm Control, которая нужна для защиты сети от перегрузок из-за BUM-штормов. BUM расшифровывается так:
Broadcast — широковещательный трафик (например, ARP-запросы).
Unknown-unicast — фреймы с неизвестным MAC-адресом назначения.
Multicast — групповой трафик (например, видеостримы).
Допустим, у нас в сети возникает петля или аномальный рост broadcast-трафика: он реплицируется и расходится по всем портам. BUM Storm Control сравнивает интенсивность трафика с заданным лимитом (по пакетам в секунду или полосе пропускания). При превышении лимита избыточные пакеты отбрасываются и предотвращают перегрузку сети, иначе лавинообразная репликация такого трафика может вызвать перегрузки на линках, а также «положить» коммутатор.

Также на этапе создания карточки мы продумываем, какие команды пользователь будет вводить в командной строке или веб-интерфейсе, работая с функционалом. У разных производителей команды могут отличаться, поэтому наша задача — сделать их понятными и привычными даже для тех пользователей, которые не работали с коммутаторами KORNFELD.
Затем к работе над карточкой подключаются ручные тестировщики — это не привычные QA, а сетевые инженеры. Они проводят тестирование исключительно с точки зрения разработки — нужно выяснить, может ли коммутатор работать с условиями из карточки и нет ли конфликта с существующим функционалом коммутатора.
Присоединяйся к нашей команде, будем вместе разрабатывать и тестировать новые фичи в коммутаторе:
Так, BUM Storm Control может быть двух видов — с блокировкой порта или с подрезанием полосы.
Решение о разблокировке порта принимают сетевые инженеры: они настраивают системное значение по умолчанию, которое разблокирует порт через определенное время. Пользователь может отключить разблокировку или задать количество попыток, которые коммутатор совершает, чтобы открыть «залоченный» порт, если он не открывается через указанное время. Для этого есть команды со специальным синтаксисом, который прописан в карточке.
Пример команд в карточке:
configure terminal
interface Ethernet 1
storm-control broadcast 100000
В проработке требований участвуют представители всех отделов (менеджеры по продукту, сетевые инженеры, разработчики) — они задают вопросы о планируемом функционале со своей стороны и одобряют его либо предлагают изменения.
Эмуляция на ASIC
Вариант с BUM Storm Control с подрезанием полосы быстрее всего протестировать с помощью эмуляции на ASIC, который внутри команды мы называем просто чип. Сетевые инженеры вместе с программистами создают упрощенную сборку, где функция легко активируется. Проверить, способен ли чип «отрезать» трафик, можно по документации к нему или с помощью практических экспериментов.
Управлять чипом можно через регистры, но это долго и сложно, поэтому мы используем API от SDK и управляем чипом через предоставляемые функции. Также производитель SDK поставляет в комплекте утилиту для конфигурации SDK из консольного режима. Она не всегда работает корректно, поэтому разработчики вносят оперативные правки для проверки функциональности. В YADRO такой подход мы называем PoC (Proof of Concept).
Подготовкой PoC занимаются разработчики, которые создают специальную сборку с нужными функциями и способами их активации. Далее эта сборка передается инженерам по ручному тестированию — сетевым инженерам. Они собирают сети с использованием физического и виртуального оборудования (коммутаторы, серверы, генераторы и измерители трафика), создают на эти сети различные нагрузки и совместно с разработчиками проводят измерения и исследования.
Для выполнения этих задач от сетевого инженера требуются знания сетевых технологий и внутреннего устройства коммутатора. Последнее нужно, чтобы вызывать не только команды, доступные пользователю, но и обращаться к внутренним механизмам — например, через ручное изменение модели данных или вызовы SDK чипа.
Самый прямой способ попадания в операционную систему в режиме отладки — открыть доступ с пользовательского CLI в операционную систему. Если он открыт, то можно вызвать из нее специальную консоль к ASIC и открыть соединение к специальному сокету. Через него можно выдавать команды в чип в формате, который поддерживает SDK.

Вы можете спросить, почему функции в ASIC не включены по умолчанию? Дело в том, что чип имеет ограниченные ресурсы, которые используются для выполнения различного функционала. Если мы включаем один функционал, то можем заблокировать или ограничить другой.
Возможна ситуация, когда мы включаем функцию, но при этом игнорируется та или иная настройка, о чем сказано в документации. Для корректной реализации функционала мы обращаемся к документам и связанным CLI-командам, а разработчики решают, какие изменения нужно внести в код и сколько потребуется времени на доработки.
Работа BUM Storm Control зависит от поведения и направления трафика: если он идет на чип транзитом, то эта функция сработает. Но если трафик предназначен для самого коммутатора, то он не доходит до BUM Storm Control и уходит на Control Plane (плоскость управления).
В чипе есть сотни различных счетчиков, которые могут подсчитывать количество пакетов нужного типа или определенные события — например, количество Multicast-пакетов, потери пакетов и так далее. Некоторые счетчики требуются для работы BUM Storm Control, и нам нужно получить их значения, чтобы использовать в алгоритме реализации фичи.
Далее к работе подключаются QA-инженеры и вместе с разработчиками составляют тест-план для новой функции.
Тестирование
Процесс проходит в два этапа: внутренний с юнит-тестами, ручным и автоматическим тестированием, и внешний, на котором подключаются сетевые тестировщики.
Внутренние тесты
Мы закончили разработку — пришло время открыть pull request. На этом этапе будут прогоняться автотесты Data Plane и Control Plane и тесты, связанные с языками программирования (юнит-тесты).
Код для Control Plane коммутатора, с одной стороны, не очень алгоритмически сложный, но с другой — должен учитывать большое количество комбинаций функционала и параметров. Его задачи:
Принять функции и настройки от пользователя.
Сформировать несколько команд для чипа.
Понять, что конфигурация не противоречивая.
Записать настройки.
Убедиться, что они применились.
Поэтому мы проверяем новую фичу не только автотестами, но и вручную, с помощью юнит-тестирования и тестов на утечки памяти.
В ходе тестирования убеждаемся, что новый фрагмент кода работает именно так, как нужно, используя две группы тестов:
Юнит-тесты (GTest) для проверки на корректность набора команд, летящих на чип, — создание pull request автоматически запускает прогон основных функций в эмуляторе (не на реальном железе), который запущен в контейнере.
Тесты по настройке чипа — в эмуляторе GNS3 и на железе. Некоторые тесты подставляют контейнер вместо Data Plane, чтобы весь Control Plane «считал», что работает с реальным чипом. Для проверки Data Plane приходится использовать реальное железо, такие тесты проводим раньше и отдельно от остальных.
Мы прогоняем все тесты, поэтому сразу видим, если что-то сломалось.
Тестирование на утечки памяти кода на C++ выполняется с помощью санитайзера адресов (ASan) во время прогона тестов при открытии pull request или при активации определенной настройки в файлах сборки.
Для реализации юнит-тестов создаются заглушки с использованием gMock. Входной точкой на этом этапе служит пользовательская информация, а выходной — вызов API SDK. Специальный фрагмент кода передает информацию в эмулированную функцию, которая фиксирует факт вызова как успешное прохождение функции. Если вызов не произошел, то тест завершается с ошибкой либо выводится предупреждение, что ожидается настройка, которой сейчас нет.
Можно решить, что программист пишет два кода — исполняемый и тестируемый. Но это не так. Тестирование, как правило, это язык сценариев. У нас на GTest получаются довольно общие тесты исходя из того, в какую часть кода приносятся изменения, — они запускают определенные end-to-end и юнит-тесты именно сетевых сценариев. Также для верификации кода мы используем ревизию кода (code review) и данные статического анализа.
Внешние тесты
Итак, наша фича прошла серию внутренних тестов, которые писали программисты, и теперь к процессу подключаются сетевые тестировщики. Они вручную проверяют сценарий, пробуют его воспроизвести и пытаются найти необычные способы взаимодействия пользователя с нашей фичей, чтобы проверить все возможные варианты и обнаружить ее нестандартную работу.
Затем мы запускаем регрессионные тесты, которые представляют собой комплексные сетевые сценарии, а для их выполнения используется специальная сборка. Она автоматически попадает в очередь тестирования и развертывается на тестовых стендах, как только оборудование освобождается от других задач. В процессе тестирования весь код и сетевой трафик проходят проверку на уровнях L2 и L3, включая все соответствующие протоколы.
Продолжительность полного цикла тестирования значительна, поэтому мы оптимизируем процесс, чтобы удержать его в пределах двух часов — наш внутренний стандарт для подобных проверок.

Теперь наступает время «найтликов» (ночных сборок). На этом этапе увеличивается количество тестов, а также проводится тестирование на актуальных ревизиях оборудования со всеми внесенными изменениями. Полноценное тестирование со всеми пользовательскими сценариями обычно занимает около пяти рабочих дней. Чтобы успеть за ночь, мы применяем компромиссный подход: сначала запускаем максимально возможный набор сетевых сценариев, а при нехватке времени переходим к итеративному методу — от минимально необходимых проверок постепенно идем к расширению тестового покрытия.

На этапе разработки тестов для нового функционала QA-инженеры выявляют около 80% всех багов. Этот процесс требует глубокого анализа различных комбинаций параметров, где вариативность фичи играет незначительную роль. Основное многообразие сценариев возникает при работе с портами: каждый тип порта (обычный, LAG, SVI, breakout) — это отдельный кейс для тестирования.
Также мы уделяем внимание негативным и сложным сценариям. Например, в случае с BUM Storm Control мы проверяем поведение системы при изменении конфигурации разбивки QSFP-порта с подрезанием трафика. Порт поддерживает четыре оптические линии и может работать в двух режимах: все линии объединены в один логический порт, либо каждая линия работает как отдельный порт. Проверка происходит так:
настраиваем на портах коммутатора BUM storm-control и генерируем трафик,
дожидаемся срабатывания storm-control,
на «горячую» выполняем сборку-разборку порта и смотрим на сложные переходные процессы, которые за этим следуют.
Такие тесты позволяют вскрывать критические проблемы в работе системы. Также бывают граничные случаи, такие как блокировка порта при достижении определенного порога пропускной способности. При проведении сложных тестов мы учитываем наличие и время сходимости протоколов, например LACP. Так, в случае автоматической разблокировки порта тест в течение 30 секунд ожидает, что порт станет доступным. На это время весь процесс тестирования приостанавливается. В LAG и LACP нам нужно получить три тайм-аута о том, что у нас не пришли LACP PDU. Такие фоновые процессы требуют времени.
Для управления коммутатором есть несколько интерфейсов: CLI, SNMP, NETCONF и так далее. В нашем случае все эти интерфейсы используют единый внутренний API, что позволяет нам:
создавать комплексные автотесты с комбинаторикой параметров,
проводить кросс-интерфейсное тестирование (например, одинаковые тесты можно реализовать как через CLI, так и через RESTCONF/NETCONF).
При ручном тестировании сложных сценариев через CLI, таких как многократное удаление и повторное создание конфигураций с разными параметрами, автотесты работают гораздо быстрее ручного ввода. Наши CLI-автотесты эмулируют поведение реального пользователя: устанавливают настоящее SSH-соединение и вводят команды, как если бы их вводили вручную. Однако в основном это стресс-тест, который проводится достаточно быстро.

QA-инженеры также анализируют баги, чтобы понять, почему новая фича не прошла некоторые автотесты. Затем QA исправляют тесты вручную и заносят информацию в базу знаний.
После реализации нового функционала разработчиками мы переходим в период «шлейфа» из багов. Их заводят автотестеры, когда в автоматическом режиме создают сценарий тестирования и выполняют его по методу черного ящика, то есть без знания внутренней структуры системы. Если часть тестов не были пройдены, то автотестеры переключаются на методы серого или белого ящика c доступом к исходному коду. Они стараются максимально точно локализовать баг:
Минимизируют шаги для воспроизведения ошибки.
Работают с дебажной сборкой, заходят в операционную систему, в базы данных, изучают логи. Так можно определить, какие ресурсы доступны, а какие — отсутствуют или работают некорректно.
Такой подход экономит время разработчиков, которые получают от QA-инженеров максимально полный контекст для исправления бага и локализуют проблему: четкий сценарий воспроизведения, необходимые логи и отправные точки.
Наконец новая фича успешно прошла все тесты и теперь считается одобренной для master-ветки.
Сдача фичи
Продуктовая команда убеждается, что все тесты пройдены успешно. При этом реализация новой фичи может быть частичной, это зависит от плана разработки функционала коммутатора. Именно менеджеры по продукту определяют, какая именно функция и когда будет добавлена в релиз. Например, фича может работать на обычном или LAG-порте, но отсутствовать в виртуальном интерфейсе SVI.
Ряд ограничений связан с чипом коммутатора, и в таких случаях новый функционал может отклоняться от стандартной реализации, но при этом сохранять работоспособность и достигать нужного нам результата. Например, для нашей фичи BUM Storm Control нам нужно, чтобы в чипе были реализованы и подрезание полосы, и блокировка порта. Но чип не может блокировать порт: у него нет механизма разблокировки после заданного времени. Это аппаратное ограничение, с которым нам тоже приходится работать. Мы обходим его программным способом, как и другие особенности «железа».
Заключение
В создании новой фичи для коммутатора KORNFELD участвуют самые разные специалисты: бизнес-аналитики, сетевые инженеры, разработчики, тестировщики, QA и менеджеры по продукту. При разработке команда учитывает множество нюансов: от архитектурных особенностей системы до специфики «железа».
В недавнем релизе KORNFELD 1.6 мы добавили поддержку технологии виртуализации сетей Virtual Extensible LAN, внедрили профиль LPM Forwarding, расширили возможности командной строки, а также реализовали поддержку STP-расширений Root Guard и BPDU Filter.
Буду рад ответить на ваши вопросы в комментариях!
Статья написана по мотивам подкаста «Жизненный цикл фичи в коммутаторе».