Ещё одна тестирующая система (Виктор Яковлев, OSEDUCONF-2022)
- Докладчик
- Виктор Яковлев
Тестирующая система ejudge, разработанная около 20 лет назад — стандарт для автоматизации проверки задач по программированию как при проведении олимпиад, так и в учебном процессе. За годы эксплуатации системы ejudge в учебном процессе на курсах, связанных с операционных системами, был накоплен опыт использования, который привёл к созданию новой тестирующей системы.
Новая система решает такие проблемы ejudge, как сложность администрирования и поддержки системы; отсутствие интерфейса преподавателя для мобильный устройств. Кроме того, улучшена изоляция тестирования с использованием Linux namespaces и cgroup, что позволяет выполнять тестирование задач на реализацию сетевых сервисов и межпроцессного взаимодействия. В настоящее время система успешно проходит апробацию на курсе по операционным системам в магистратуре МФТИ.
Содержание
Видео
Презентация
Thesis
Предпосылки разработки
Для организации приёма заданий на курсе «Архитектура компьютеров и операционные системы» (АКОС) ФПМИ МФТИ четвёртый год подряд используется тестирующая система Ejudge.
Несмотря на то что эта система изначально была создана для проведения олимпиадных мероприятий по программированию, она является достаточно гибкой в плане конфигурирования, поэтому возможно её использование и в учебном процессе.
Студентам каждую неделю открываются от 2 до 4 задач по самым разным темам начиная с основ низкоуровневого программирования, в том числе с использованием ассемблеров AArch64 и x86-64, до сетевого взаимодействия и использования сторонних библиотек. Для каждой учебной группы создаётся отдельный контест, в который регистрируются студенты, семинарист и учебный ассистент группы. Каждый контест имеет свои настройки дедлайнов, которые зависят от дня недели, в который проводятся занятия определённой группы. Настройки задач и тесты — общие, это организовано через механизм символических ссылок.
Проверка большинства задач может быть организована штатными средствами ejudge, но есть и исключения, которые требуют нестандартной реализации, например: использование кросс-компиляции и запуска qemu, тестирование низкоуровневых сетевых задач, требующих канальный уровень сети, либо нестандартное взаимодействие с тестируемыми программами.
За годы использования ejudge приходится постоянно следить за работоспособностью тестирующей системы, поскольку нестандартные сценарии менее надёжны, чем хотелось бы. Кроме того, периодически приходится выполнять конфигурирование новых контестов, например, в начале каждого нового модуля, либо при добавлении новых задач. Учитывая тенденцию многих вузов по ежегодному увеличению количества студентов на IT-специальностях, и, соответственно, количества учебных групп, поддержка системы становится всё более трудоёмкой.
Для постепенной замены ejudge разрабатывается новая тестирующая система yajudge (Yet Another Judge), которая предназначена для использования в первую очередь именно в учебном процессе, и должна иметь следующие преимущества по сравнению с ejudge:
- Упрощённое конфигурирование задач. Сейчас в ejudge используются конфигурации из файлов INI-формата, тексты заданий в формате XML, а если необходимо нестандартное тестирование или взаимодействие, то нужно писать вспомогательные программы на языке Си. При этом ошибки, например, в XML-структуре файлов условий, приводят к их неработоспособности с отсутствием подробной диагностики ошибки, а отлаживать нестандартное тестирование или взаимодействие достаточно трудоёмко.
- Упрощённое конфигурирование курсов. В ejudge для создания нового контеста приходится избыточно дублировать одни и те же настройки, например, добавление нового преподавателя подразумевает не только добавление пользователя в таблице БД, но и правку конфигурационного XML-файла. Добавление же новой задачи в контест, может приводить к страшным последствиям, если задача добавляется не самой последней, поскольку ejudge автоматически сопоставляет ID задач с тем, в каком порядке они встретились в конфигурационном файле.
- Возможность аналитики отправленных решений, наиболее важная практическая задача в этом — анализ решений на плагиат. В системе ejudge отправленные решения хранятся в виде файлов, пути к которым выясняются из БД, что усложняет реализацию антиплагиата.
- Большая степень изоляции, по сравнению с ejudge, при тестировании решений задач. Ввиду специфики изучаемого в курсе предмета, студенты могут использовать механизмы межпроцессного взаимодействия и взаимодействовать с сетевыми интерфейсами. Изоляция только на уровне отдельного пользователя и установка ресурсных лимитов не достаточны для безопасного выполнения программ, поскольку это не спасает, например, от посылки сигнала процессу с PID=-1, или открытию большого количества сетевых портов, которые затем не закрываются.
- Возможность тестирования задач на разных системах, поскольку в курсе, помимо архитектуры x86, рассматривается архитектура ARM (AArch64 в последней итерации). Хотя тестирование с использованием эмулятора qemu и кросс-компилятора позволяет решать эту задачу, этот подход нельзя назвать идеальным.
- Более современный пользовательский интерфейс. В частности, некоторые виды взаимодействий с системой (просмотр посылок, ревью кода) не требуют использования компьютера и могут выполнятся с мобильных устройств (планшеты и смартфоны), но интерфейс ejudge для этого не приспособлен.
Система yajudge
Система yajudge состоит из центрального мастер-сервера, который должен быть установлен на машине с выделенным IP-адресом. Этот сервер предназначен для взаимодействия с клиентами посредством веб-интерфейса (также возможно применение «толстого» клиента), хранит сведения о пользователях, курсах и посылках в базе данных PostgreSQL, и раздаёт задания грейдерам, — отдельным сервисам, которые выполняют тестирование посылок задач.
Грейдер может быть запущен как в единственном экземпляре на том же физическом сервере, что и мастер-сервер, так и на отдельных выделенных серверах, в том числе с динамическими IP-адресами, различными операционными системами (хотя только в Linux реализована изоляция запуска), и разной архитектурой процессоров. При подключении к мастер-серверу грейдер сообщает ему свою ОС и архитектуру процессора, поэтому мастер-сервер может раздавать грейдерам разные задания, в зависимости от требований отдельных задач.
Учебные курсы и задачи
Содержание заданий и учебных курсов хранятся в виде обычных файлов, которые находятся на машине с установленным мастер-сервером. Файлы, относящиеся к задачам, собраны в единый пул задач, у каждой задачи есть свой текстовый идентификатор, название которого совпадает с именем подкаталога задачи.
Содержание курсов хранится также в виде файлов, но отдельно от пула задач. Это сделано по двум причинам. Во-первых, контент курсов может иметь отличную от контента задач доступность, например, курс — общедоступный, а задачи — закрытые. Во-вторых, это позволяет достаточно гибко составлять разные курсы исходя из их продолжительности и целевой аудитории, используя при этом подмножества задач из единого пула. Курсы структурированы: разделы и отдельные уроки курса, которым соответствуют отдельные подкаталоги. Помимо ссылок на задачи из общего пула, курсы могут иметь материалы для изучения.
Для описания структуры курсов и параметров задач, как и для конфигурации системы в целом используется формат YAML, который является более компактным чем XML или JSON. Формат отдельных тестов для задач полностью позаимствован у системы ejudge, а для не стандартных тестирующих программы и генераторов тестов используется язык Python. Более подробное описание интерфейса приведено в демо-курсе.
Всё, что связано с динамически изменяемыми данными: пользователи, запуски курсов и посылки, включая решения заданий, хранятся уже в базе данных PostgreSQL.
Тестирование решений
Тестирование решений реализуется отдельным сервисом, который выполняет стадии проверки стиля кода, компиляции, запуска решений и сравнения с эталоном либо с использованием нестандартного проверяющего скрипта. Поддерживается тестирование программ на языках программирования Си/С++, языке ассемблера и скриптовых языков, например, Bash или Python.
Перед началом тестирования создаётся изолированная файловая система с использованием OverlayFS. Нижние слои этой файловой системы состоят из:
- Корневой файловой системы Linux-дистрибутива, который заранее подготовлен администратором. Это может быть, в простейшем случае, либо Alpine Linux, либо система, установленная в отдельный каталог с помощью debootstrap или других инструментов.
- Каталога задачи, содержащего тесты и другие дополнительные файлы. Если используется генерация тестов, то она проводится на предварительном этапе, до начала тестирования. Генерация тестов полезна, например, для генерации случайных данных, либо для создания больших файлов.
Верхний слой файловой системы предназначен для создания исполняемого файла из исходного решения и сохранения результатов работы программы.
Полученная файловая система становится корневой для выполнения компиляции и прогона тестов. Таким образом, гарантируется, что отправленное студентом решение не будет иметь доступ к тем файлам, которые находятся вне задачи и специально выделенной файловой системы дистрибутива Linux.
Помимо изменения корня файловой системы, с помощью механизма Linux Namespaces производится изоляция процесса.
Наиболее значимые подсистемы, которые изолируются:
- Точки монтирования файловых систем. Это, во-первых, необходимо для изменения корневой файловой системы без прав администратора, а во-вторых, делает безопасным тестирование решения задач на реализацию файловых систем с помощью FUSE.
- Сетевые интерфейсы. Необходимо для задач на сетевое взаимодействие, которое становится возможным только с той вспомогательной программой, которая входит в поставку задачи.
- Пространство процессов (PID). Это исключает возможность взаимодействия с процессами, не относящимися к задаче, используя механизм сигналов.
В качестве дополнительной меры безопасности можно запретить использование определённых функций стандартной библиотеки языка Си. Это реализуется на стадии компиляции решения использованием механизма подмены функций (опция wrap линковщика). Такое решение не является идеальным, и, возможно, будет заменено на использование механизма eBPF.
Для контроля лимитов запускаемого процесса используются два механизма, дополняющих возможности друг друга: ulimit, и механизм Cgroup v2, который реализован в современных ядрах Linux.
Механизм Cgroup v2 позволяет более точно контролировать некоторые ограничения выполнения, чем классический механизм лимитов в UNIX. В частности, он даёт гарантии на количество одновременно запущенных процессов в рамках одного контейнера, что является важным для курса по операционным системам.
Пользовательский интерфейс системы
Пользовательский интерфейс системы реализован с помощью кросс-платформенного фреймворка Flutter, который предназначен для мобильной разработки, но также позволяет собирать программы под основные десктопные операционные системы (Linux, Windows, macOS), и в одностраничные Web-приложения.
Взаимодействие пользовательского интерфейса с мастер-сервером осуществляется, как и все межсервисные взаимодействия в yajudge, через протокол gRPC, который является более производительным, чем взаимодействие через REST+JSON. Для Web-версии клиентского приложения используется протокол gRPC-Web, — специальный вариант протокола gRPC для работы в условиях ограничений браузеров, и на стороне сервера необходим специальный прокси-сервер: либо Envoy, либо grpcwebproxy, входящий в поставку yajudge.
Итоги
Основной функционал системы реализован, и в настоящее время она проходит успешную апробацию на семестровом курсе по Операционным системам в магистратуре ФПМИ МФТИ. Это первая итерация практического использования системы, во время которой были устранены обнаруженные дефекты.
Пока ещё не реализована функциональность для проведения Code Review, которая необходима для использования системы в бакалавриате. Предполагается реализовать её к началу осеннего семестра 2022 года, после чего провести очередную итерацию практического тестирования.