Введение
Приложение представляет собой web-интерфейс для просмотра и анализа результатов сборок BuildBot.
Общие термины
- Тип сборки – конструктор задачи в системе BuildBot (
builders). - Сборка – отдельная задача в системе BuildBot (
builds). - Шаги – этап выполнения задачи в системе BuildBot (
steps). - Дочерняя сборка – сборка, запущенная в рамках шага другой (родительской) сборки.
- Панель – страница, отображающая несколько карточек сборок.
- Страница сборки – вариант представления для сборки, когда на странице показывается одна конкретная сборка с более детализированной информацией по ней, в том числе с результатами шагов, которые запускали дочерние сборки.
- Страница фильтрации – страница для выбора фильтров, позволяющая гибко отфильтровывать сборки на панели.
Основные технологии
Для разработки веб-приложения используются следующие ключевые технологии:
- Основной язык Kotlin.
- Обработка HTTP-запросов с помощью библиотеки http4k.
- Взаимодействие с СУБД MariaDB с помощью библиотеки jOOQ.
- Формирование HTML-страниц с помощью библиотеки Pebble.
- Динамическое обновление HTML на стороне веб-браузера Htmx.
- Стилизация HTML-элементов с помощью CSS-фреймворка Bootstrap.
- Подключение JS/CSS-компонентов с помощью проекта WebJars.
- Выполнение фоновых задач JobRunr.
Архитектура приложения
Архитектурные принципы
В проекте используются следующие принципы:
- Все основные компоненты приложения инициализируются в
main()функции в файлеBuildBotDashboards.kt. - HTTP-обработчики (Handler) не содержат бизнес-логики, отвечают только за обработку HTTP-запроса, извлечение параметров из него, передачу управления операции и формирование HTTP-ответа.
- Вся логика получения и обработки данных сосредоточена в операциях.
- Domain-модели не зависят от других слоев приложения.
- Классы ViewModel используются только для передачи данных в шаблоны Pebble.
- Шаблонизатор Pebble отвечает только за формирование HTML-кода на основе данных из ViewModel.
Структура пакетов
config
Конфигурация приложения, настройка параметров подключения к БД и настройка JobRunr (менеджера фоновых задач).
db.operations
Слой доступа к данным и бизнес-логики приложения.
OperationHolder представляет собой хранилище специализированных классов операций
и служит для централизованного доступа к ним. Каждый класс-операция инкапсулирует полный сценарий получения данных.
Операция может:
- выполнять один или несколько SQL-запросов через jOOQ;
- использовать общие подзапросы из
subqueries; - объединять результаты нескольких запросов;
- выполнять фильтрацию и преобразование данных;
- формировать доменные модели, используемые
web-слоем.
domain
Доменная модель приложения.
Содержит сущности предметной области, используемые различными слоями приложения. Данный слой не зависит от других.
jobrunr
Фоновые задачи.
Используется для регистрации и выполнения периодических или долгих задач, на данный момент используется только для получения и обновления глобального кэша приложения.
web
HTTP-интерфейс приложения.
Структура:
Router— регистрация маршрутов.handlers— обработка HTTP-запросов.configs— описание варианта отображения для типов сборок (DisplayBuildConfig).models— ViewModel для Pebble.view— рендеринг HTML.filters— http4k-фильтры.lens— извлечение данных из HTTP-запросов.extensions— расширение функциональности шаблонизатора Pebble (Extending Pebble документация) .
Сгенерированный код
Каталоги kotlin-jooq-buildbotdb и kotlin-jooq-maxscaledb содержат
автоматически сгенерированный jOOQ-код на основе схемы базы данных.
Изменения в этих каталогах вручную не вносятся.
Цикл обработки HTTP-запроса
HTTP Request
│
▼
Router
│
▼
DisplayBuildConfig
│
▼
Handler
│
▼
Operation
│
▼
Domain Model
│
▼
ViewModel
│
▼
Pebble Template
│
▼
HTTP Response
Общий паттерн обработчиков
Практически все страницы используют один и тот же паттерн.
Для каждого типа сборок существуют три обработчика:
- Страница фильтрации (
*AllFiltersHandler) - Панель (
*AllHandler) - Страница сборки (
*Handler) // если нужна детальная информация по отдельной сборке
Пример:
MaxScaleBuild
├── MaxScaleBuildHandler
├── MaxScaleBuildAllHandler
└── MaxScaleBuildAllFiltersHandler
Аналогичная структура используется для остальных типов сборок.
Структура шаблонов Pebble
Все HTML-страницы приложения реализованы с использованием шаблонизатора Pebble.
Шаблоны располагаются в каталоге src/main/resources/net/mariadb/web/models
Каждому ViewModel соответствует одноимённый Pebble-шаблон. Пути и названия файлов должны совпадать, по ним
шаблонизатор устанавливает соответствие.
Например:
kotlin/.../models/MaxScaleBuildAllViewModel
│
▼
resources/.../models/MaxScaleBuildAllViewModel.peb
Основные шаблоны
Основные шаблоны (*ViewModel.peb) отвечают за отображение отдельных страниц приложения.
Из них DashboardsViewModel.peb — главная страница приложения с карточками панелей.
Как правило, для каждой категории сборок существует три шаблона:
- страница фильтрации;
- панель;
- страница сборки (если нужна).
models/Layout
Layout.peb
Базовый шаблон приложения.
Определяет общую HTML-структуру страницы, подключение CSS, навигацию и общий каркас интерфейса.
Все страницы наследуются от него.
layouts
Крупные переиспользуемые компоненты интерфейса.
Например:
BuildCardDashboard.peb— основа страницы панели;DetailedBuildCard.peb— основа страницы сборки;FiltersForm.peb— основа страницы фильтрации.
parts
Небольшие повторно используемые части интерфейса.
Например:
BuildCard.peb— карточка сборки на панелях;StepAccordion.peb— аккордеоны шагов на странице сборки.
Используются внутри основных шаблонов и layouts.
macros
Набор Pebble-макросов для отображения повторяющихся элементов.
Например:
- свойства сборки (полученные из БД);
- вычисляемые свойства;
- ссылки на внешние сервисы;
- пагинация;
- отдельные шаги сборки в коротком формате;
- фильтры на формах.
Использование макросов позволяет избежать дублирования HTML-кода.
accessory
Шаблоны вспомогательных компонентов.
DifferenceBetweenStepConfigsViewModel.peb — особый шаблон для анализа соответствия текущей конфигурации отображения
и реального дерева шагов последних N сборок определенного типа.
Добавление новой панели
Для добавления новой панели рекомендуется выполнить следующие шаги:
- Если это новый тип сборок, необходимо добавить его в перечисление
Dashboard(вdomain). - Создать конфигурации отображения для панели и, если нужно, для страницы сборки.
- Создать
Handler(если нужна страница с детальной информацией),AllHandlerиAllFiltersHandler. - Создать соответствующие ViewModel.
- Добавить Pebble-шаблоны.
- Зарегистрировать маршруты в
Router. - Добавить новую категорию на основную страницу панелей
resources/.../models/DashboardsViewModel.peb(при необходимости).
Создание конфигурации отображения
В пакете web.configs находятся все конфигурации отображений. Для одного и того же типа сборок (builder) может быть несколько вариантов отображения.
Они делятся на два типа:
dashboard– конфигурация для сборок, отображаемых на панелях в виде карточек.detailed– конфигурация для страницы сборки.
Для добавления конфигурации необходимо:
- Создать функцию, которая возвращает объект класса
DisplayBuildConfiguration, т.е. конфигурацию. - В классе
DisplayBuildConfigHolderдобавить отдельное поле для этого объекта. - Необходимо, обернуть функцию создания конфигурации в
registerConfig, что позволит проанализировать конфигурацию на этапе компиляции приложения. - В маршрутизаторе (
Router) передать конфигурацию в нужный обработчик (Handler).
Автоматизация сборки приложения
В приложении используется система сборки Gradle. Она отвечает за компиляцию проекта, управление зависимостями, генерацию исходного кода и запуск дополнительных инструментов контроля качества.
Основные задачи
Компиляция
Компиляция Kotlin-кода и сборка проекта:
./gradlew build
Запуск приложения
Для локального запуска используется:
./gradlew run
Генерация jOOQ
Проект использует jOOQ для генерации типобезопасного доступа к базе данных.
Сгенерированный код располагается в каталогах:
src/main/kotlin-jooq-buildbotdb/src/main/kotlin-jooq-maxscaledb/
Эти каталоги содержат автоматически сгенерированные классы и не должны редактироваться вручную.
Сгенерировать классы позволяют задачи Gradle:
./gradlew generateBuildBotClassesJooq../gradlew generateMaxScaleClassesJooq.
Генерацию необходимо выполнять после изменения схемы базы данных, а так же для нее требуется доступ к базе данных.
Проверка стиля кода
Для проверки соответствия Kotlin-кода принятому стилю используется плагин ktlint.
Основные задачи:
Проверка проекта на соответствие правилам форматирования.
./gradlew ktlintCheck
Автоматическое исправление большинства нарушений форматирования.
./gradlew ktlintFormat
Управление зависимостями
Gradle также отвечает за:
- загрузку внешних библиотек;
- управление версиями зависимостей;
- настройку плагинов проекта;
- конфигурацию задач сборки.
Для добавления новой зависимости нужно:
- Найти нужную библиотеку в Maven Central Repository.
- Выбрать нужную версию библиотеки (можно ориентироваться на текст для Gradle).
- Описать зависимость в файле
buildbot-dashboards/gradle/libs.versions.toml. - Добавить зависимость в файл
buildbot-dashboards/build.gradle.kts. - (Опционально) Если это клиентская зависимость проекта WebJars, нужно добавить ее компоненты (css и js) в общий шаблон
resources/.../models/Layout.peb
Пример jobRunr:
// на сайте для Gradle
implementation("org.jobrunr:jobrunr:8.5.1")
// в libs.versions.toml
[versions]
jobRunr = "8.5.1"
[libraries]
jobRunr = { module = "org.jobrunr:jobrunr", version.ref = "jobRunr"}
// в build.gradle.kts
dependencies {
implementation(libs.jobRunr)
}
Базы данных
BuildBot Dashboards работают с двумя базами данных и только на чтение.
buildbot
Осовная база данных – buildbot. Ниже приведена схема для таблиц, с которыми взаимодействует приложение.
Неиспользуемые таблицы и поля исключены из схемы.
[Важно] Реальная схема базы данных отличается от данного представления.
maxscale_test_results
Вспомогательная база данных – maxscale_test_results. Она используется только для подсчета общего количества
проваленных тестов для сборок MaxScale (на панелях
Build & Test MaxScale и
Test MaxScale Parallel это значение
отображается как Total number of failed tests). Неиспользуемые таблицы и поля исключены из схемы.
[Важно] Реальная схема базы данных отличается от данного представления.
Слой операций
Операции отвечают за формирование SQL-запросов с помощью jOOQ, их выполнение и формирование на основе полученных данных результатов в виде классов предметной области.
Центральный компонент слоя операций – класс OperationHolder. Через него web-слою предоставляется доступ к операциям.
OperationHolder инициализируется в main() функции приложения единожды при компиляции и зависит от контекста
выполнения запросов DSLContext библиотеки jOOQ.
DSLContext создается функцией createJooqContext на основе данных для подключения к БД по протоколу JDBC.
Контекст подключения создается исключительно в main() функции и передается в OperationHolder.
Subqueries
Подзапросы представляют собой переиспользуемые части запросов. Они встраиваются в другие запросы и позволяют избежать дублирования кода операций.
ExtractBuilderIdSubquery
Подзапрос для получения идентификатора Builder, используя SHA1 хэш от имени. Поиск по хэш позволяет использовать
индекс для получения конкретного типа сборки.
ExtractFilteredBuildsSubquery
Подзапрос для получения сборок, список которых ограничен постраничным выводом и примененными фильтрами.
При фильтрации по свойствам сборки имя параметра должно совпадать польностью, а значение соответствовать введенному
пользователем регулярному выражению.
ExtractInvokedBuildsSubquery
Подзапрос для получения сборок, запущенных в рамках выполнения шага (steps).
В базе данных buildbot связь шага и запущенной им сборки хранится в формате json в поле buildbot.steps.urls_json, примеры:
Запуск одной сборки:
[
{ // запрос на создание сборки X
"name": "install_test_one_step_all #2203993",
"url": "https://mdbe-buildbot.mariadb.net/#/buildrequests/2203993"
},
{ // сборка X, добавляется только после завершения выполнения сборки
"name": "failure: install_test_one_step_all #23601",
"url": "https://mdbe-buildbot.mariadb.net/#/builders/21/builds/23601"
}
]
Запуск нескольких сборок:
[
{ // запрос на создание сборки X
"name": "run_mtr_all #2203368",
"url": "https://mdbe-buildbot.mariadb.net/#/buildrequests/2203368"
},
{
"name": "run_mtr_all #2203369",
"url": "https://mdbe-buildbot.mariadb.net/#/buildrequests/2203369"
},
{ // сборка X, добавляется только после завершения выполнения сборки
"name": "success: Running all MTR on 'aarch64_rhel_9_gcp' #290",
"url": "https://mdbe-buildbot.mariadb.net/#/builders/766/builds/290"
},
{
"name": "success: Running all MTR on 'rocky_9_gcp' #436",
"url": "https://mdbe-buildbot.mariadb.net/#/builders/608/builds/436"
}
]
Данный подзапрос позволяет каждому шагу сопоставить дочерние сборки, которые он запустил. Выбираются только url,
содержащие подстроку buildrequests и берется buildrequestid (число после последнего /), далее по нему находится запущенная сборка.
url -> id запроса на запуск сборки -> сборка
https://mdbe-buildbot.mariadb.net/#/buildrequests/2203368 -> 2203368 -> BuildsRecord(id=2198479, ...)
Так же дополнительно определено выражение invocationType, которое позволяет классифицировать шаги по типу запуска дочених задач:
- MULTIPLE – запуск нескольких задач;
- SINGLE – запуск одной задачи;
- NONE – без запуска дочерних задач.
Операции
Все операции делятся на два типа:
- публичные, которые вызываются из обработчиков;
- приватные, которые используются только в рамках других операций.
ExtractBuildOperation
Операция для получения детальной информации о сборке, которая после будет отображена на странице сборки (странице с детальной информацией).
Входные данные:
- номер сборки (
builds.number); - конфигурация (
BuildConfiguration)- тип сборки (
dashboard), - теги интересующих шагов (например,
#16,#21), - необходимые свойства сборки (например,
target,branch,Image).
- тип сборки (
Выходные данные – объект ProcessedBuildInfo, если сборка с указанным номером существует, и NoBuildError, если нет.
ExtractBuildPackagesAllOperation
Операция для получения краткой информации по нескольким сборкам одного типа (builder). Используется для страниц панелей.
[Примечание] BuildPackagesAll в названии класса неверно, эта операция позволяет получать данные для любого типа сборки.
Входные данные:
buildConfiguration– конфигурация (BuildConfiguration),paginationStart– порядковый номер первого элемента для постраничного отображения,buildsLimit– количество выбираемых сборок,filters– набор применяемых фильтров.
Выходные данные: список объектов BuildInfo.
Логика работы:
- Выбираем N сборок с учетом фильтров и постраничного вывода.
- Только для выбранных сборок получаем:
- родительские сборки (если сборка была запущена другой сборкой),
- значения интересующих свойств,
- интересующие шаги.
- Формируем список объектов
BuildInfo, связывая отдельные группы данных по id сборки.
ExtractBuildPropertiesOperation
Приватная операция, которая позволяет выбирать значения свойств для нескольких сборок.
ExtractBuildStepsOperation
Приватная операция, которая позволяет выбирать интересующие шаги нескольких сборок. Используется для панелей.
В результате выполнения операции для каждой сборки формируется ассоциативный массив, где ключ – тег шага (#16, #21),
а значение – StepAndBuild. Пара шаг и сборка, которую он запустил.
[Важно] Принято соглашение. Если шаг запускал несколько сборок, то результат выполнения шага берется, из результата самого шага, а не результата запущенной им сборки. В тоже время данные запущенной шагом сборки заменяются данными основной сборки. На панелях это отражается следующим образом: ссылки рядом с шагами, которые запускают несколько дочерних сборок, ведут на саму основную сборку, а не на какую-либо из запущенных шагом.
ExtractParentBuildOperation
Приватная операция, которая позволяет находить для сборок их родительские сборки. Родитеская сборка – это сборка, которая в рамках одного из своих шагов запустила данную дочернюю сборку.
ExtractTotalNumberFailedTests
Операция для подсчета общего количества упавших тестов для MaxScale сборок. Данные запрошиваются из бд maxscale_test_results.
На панелях Build & Test MaxScale и Test MaxScale Parallel это значение отображается как Total number of failed tests
ExtractStepConfigDifferenceOperation
Операция для аналитики. Позволяет получить сведения о дереве шагов, которые запускались у N последних сборок определенного типа и сопоставить их с отображаемыми шагами. Позволяет понять, актуальна ли конфигурация отображения или необходимо в нее внести изменения.
ExtractFiltersCacheOperation
Данная операция не определена в OperationHolder, она используется в фоновой задаче jobrunr.backgroundTasks.RefreshFiltersCacheTask
и позволяет получать все возможные значения определенных параметров для конкретных типов сборок.
Полученные данные используются в элементах на страницах фильтрации, в качестве возможных вариантов выбора.