From 347677824316a115d1d4e604447f7aba73239a84 Mon Sep 17 00:00:00 2001 From: Fedor Korotkiy Date: Sun, 5 Apr 2020 15:47:58 +0300 Subject: [PATCH] Update distbuild/README.md --- distbuild/README.md | 220 +++++------------------ distbuild/disttest/README.md | 19 ++ distbuild/disttest/fixture.go | 4 + distbuild/disttest/three_workers_test.go | 2 +- 4 files changed, 72 insertions(+), 173 deletions(-) create mode 100644 distbuild/disttest/README.md diff --git a/distbuild/README.md b/distbuild/README.md index 8d0272d..a655db0 100644 --- a/distbuild/README.md +++ b/distbuild/README.md @@ -18,10 +18,11 @@ - Файлы, которые породили другие джобы. Команды внутри джоба могут писать результаты своей работы в файлы на диске. Выходные файлы -обязаны находиться внутри `OUTPUT_DIR` директории. +обязаны находиться внутри его выходной директории. Директория с результатом работы джоба называется +артефактом. ```go -package graph +package build import "crypto/sha1" @@ -32,65 +33,33 @@ type ID [sha1.Size]byte // Job описывает одну вершину графа сборки. type Job struct { - // ID задаёт уникальный идентификатор джоба. - // - // ID вычисляется как хеш от всех входных файлов, команд запуска и хешей зависимых джобов. - // - // Выход джоба целиком опеределяется его ID. Это важное свойство позволяет кешировать - // результаты сборки. - ID ID + // ID задаёт уникальный идентификатор джоба. + // + // ID вычисляется как хеш от всех входных файлов, команд запуска и хешей зависимых джобов. + // + // Выход джоба целиком опеределяется его ID. Это важное свойство позволяет кешировать + // результаты сборки. + ID ID - // Name задаёт человекочитаемое имя джоба. - // - // Например: - // build gitlab.com/slon/disbuild/pkg/b - // vet gitlab.com/slon/disbuild/pkg/a - // test gitlab.com/slon/disbuild/pkg/test - Name string + // Name задаёт человекочитаемое имя джоба. + // + // Например: + // build gitlab.com/slon/disbuild/pkg/b + // vet gitlab.com/slon/disbuild/pkg/a + // test gitlab.com/slon/disbuild/pkg/test + Name string - // Inputs задаёт список файлов из директории с исходным кодом, - // которые нужны для работы этого джоба. - // - // В типичном случае, тут будут перечислены все .go файлы одного пакета. - Inputs []string + // Inputs задаёт список файлов из директории с исходным кодом, + // которые нужны для работы этого джоба. + // + // В типичном случае, тут будут перечислены все .go файлы одного пакета. + Inputs []string - // Deps задаёт список джобов, выходы которых нужны для работы этого джоба. - Deps []ID + // Deps задаёт список джобов, выходы которых нужны для работы этого джоба. + Deps []ID - // Cmds описывает список команд, которые нужно выполнить в рамках этого джоба. - Cmds []Cmd -} - -// Cmd описывает одну команду сборки. -// -// Есть несколько видов команд. Все виды команд описываются одной структурой. -// Реальный тип определяется тем, какие поля структуры заполнены. -// -// exec - выполняет произвольную команду -// cat - записывает строку в файл -// -// Все строки в описании команды могут содержать в себе на переменные. Перед выполнением -// реальной команды, переменные заменяются на их реальные значения. -// -// {{OUTPUT_DIR}} - абсолютный путь до выходной директории джоба. -// {{SOURCE_DIR}} - абсолютный путь до директории с исходными файлами. -// {{DEP:f374b81d81f641c8c3d5d5468081ef83b2c7dae9}} - абсолютный путь до директории, -// содержащей выход джоба с id f374b81d81f641c8c3d5d5468081ef83b2c7dae9. -type Cmd struct { - // Exec описывает команду, которую нужно выполнить. - Exec []string - - // Environ описывает переменные окружения, которые необходимы для работы команды из Exec. - Environ []string - - // WorkingDirectory задаёт рабочую директорию для команды из Exec. - WorkingDirectory string - - // CatTemplate задаёт шаблон строки, которую нужно записать в файл. - CatTemplate string - - // CatOutput задаёт выходной файл для команды типа cat. - CatOutput string + // Cmds описывает список команд, которые нужно выполнить в рамках этого джоба. + Cmds []Cmd } ``` @@ -108,125 +77,32 @@ type Cmd struct { 3. Воркеры начинают выполнять вершины графа, пересылая друг другу выходные директории джобов. 4. Результаты работы джобов скачиваются на клиента. -## Протоколы +# Как решать эту задачу -Общение между компонентами будет происходить поверх HTTP и json. В реальной системе мы бы -взяли более продвинутый протокол и более эффективный формат сериализации, но в этой учебной -задаче нам важнее уменьшить сложность системы. +Задача разбита на шаги. В начале, вам нужно будет реализовать небольшой набор независимых пакетов, +которые реализует нужные примитивы. Код в этих пакетах покрыт юниттестами. В каждом пакете находится +файл README.md, объясняющий подзадачу. -### Протокол: Клиент <-> Координатор +Рекомендуемый порядок выполнения: -При общении клиента и кординатора, клиент всегда выступает инициатором запроса. +- [`distbuild/pkg/build`](./distbuild/pkg/build) - определение графа сборки. В этом пакете ничего писать не нужно, + нужно ознакомиться с существующим кодом. +- [`distbuild/pkg/tarstream`](./distbuild/pkg/tarstream) - передача директории через сокет. +- [`distbuild/pkg/api`](./distbuild/pkg/api) - протокол общения между компонентами. +- [`distbuild/pkg/artifact`](./distbuild/pkg/artifact) - кеш артефактов и протокол передачи артефактов между воркерами. +- [`distbuild/pkg/filecache`](./distbuild/pkg/filecache) - кеш файлов и протокол передачи файлов между компонентами. +- [`distbuild/pkg/scheduler`](./distbuild/pkg/filecache) - планировщик с экристикой локальности. -* `POST /build` - стартует новый билд. Клиент посылает в Body запроса json c описанием сборки. Сервер -стримит в body ответа json сообщения описывающие прогресс сборки (тут правильнее было бы использовать -websocket, но нас устраивает более простое решение). +После того, как все кубики будут готовы, нужно будет соединить их вместе, реализовав `distbuild/pkg/worker`, +`distbuild/pkg/client` и `distbuild/pkg/coordinator`. Код в этих пакетах нужно отлаживать на +интеграционных тестах в [`distbuild/disttest`](./distbuild/disttest). -* `POST /source/{sha1}` - загружает файл с исходным кодом на координатор. Клиент посылает -содержимое файла в body запроса. `{sha1}` - равен `{sha1}` хешу от содержимого файла. +Код тестов в этом задании менять нельзя. Это значит, что вы не можете менять интерфейсы в тех местах, где +код покрыт тестами. -### Протокол: Координатор <-> Воркер - -При общении воркера и координатора, воркер всегда выступает инициатором запроса. - -* `GET /source/{sha1}` - скачивает файл с исходным кодом с координатора. - -* `POST /heartbeat` - синхронизирует состояние воркера и координатора. Воркер посылает -в теле запроса json описывающий изменение в своём состоянии. Коордитора отвечает json-ом со -списком задач, которые должен выполнить воркер. - -```go -package proto - -// CompleteJob описывает результат работы джоба. -type CompletedJob struct { - ID graph.ID - - Stdout, Stderr []byte - - // Error описывает сообщение об ошибке, из-за которого джоб не удалось выполнить. - // - // Если Error == nil, значит джоб завершился успешно. - Error *string -} - -type HeartbeatRequest struct { - // WorkerID задаёт персистентный идентификатор данного воркера. - // - // WorkerID так же выступает в качестве endpoint-а, к которому можно подключиться по HTTP. - // - // В наших тестов, идентификатор будет иметь вид "localhost:%d". - WorkerID string - - // ProcessID задаёт эфемерный идентификатор текущего процесса воркера. - // - // Координатор запоминает ProcessID для каждого воркера. - // - // Измение ProcessID значит, что воркер перезапустился. - ProcessID string - - // RunningJobs перечисляет список джобов, которые выполняются на этом воркере - // в данный момент. - RunningJobs []graph.ID - - DownloadingSources []graph.ID - - DownloadingArtifacts []graph.ID - - // FreeSlots сообщаяет, сколько еще процессов можно запустить на этом воркере. - FreeSlots int - - // CompletedJobs сообщает координатору, какие джобы завершили исполнение на этом воркере - // на этой итерации цикла. - CompletedJobs []CompletedJob - - // AddedArtifacts говорит, какие артефакты появились в кеше на этой итерации цикла. - AddedArtifacts []graph.ID - - // AddedSourceFiles говорит, какие файлы появились в кеше на этой итерации цикла. - AddedSourceFiles []graph.ID -} - -// JobSpec описывает джоб, который нужно запустить. -type JobSpec struct { - -} - -// ArtifactSpec описывает артефакт, который нужно скачать с другого воркера. -type ArtifactSpec struct { - -} - -// SourceFileSpec описывает файл с исходным кодом, который нужно скачать с координатора. -type SourceFileSpec struct { - -} - -type HeartbeatResponse struct { - JobsToRun map[graph.ID]JobSpec - - ArtifactsToDownload map[graph.ID]ArtifactSpec - - ArtifactsToRemove []graph.ID - - SourceFilesToDownload map[graph.ID]SourceFileSpec - - SourceFilesToRemove []graph.ID -} -``` - -### Протокол: Воркер <-> Воркер - -Общение между воркерам происходит тогда, когда системе нужно передать артефакты сборки с -одного воркера на другой. - -* `GET /artifact/{sha1}` - возвращает директорию с выходными данными джоба в формате `tar`. - -## Кеширование - -### Кеш исходного кода - -### Кеш артефактов - -## Тестирование +# Критерии оценки +Решение должно проходить все тесты, так же как в обычной задаче. После успешной попытки, в таблице gdoc +будет стоять 0. После этого, проверяющие должны будут просмотреть решение и заменить оценку в таблице на 1. +Это будет значить, что задача засчитана. Code Review не будет, проверка нужна только чтобы удостовериться что +посылка честно проходит все тесты. diff --git a/distbuild/disttest/README.md b/distbuild/disttest/README.md new file mode 100644 index 0000000..bae2874 --- /dev/null +++ b/distbuild/disttest/README.md @@ -0,0 +1,19 @@ +# disttest + +Пакет `disttest` содержит интеграционные тесты. + +Тесты запускают все компоненты внутри одного процесса. Это сделано для удобства отладки. В случае +паники в любом месте, весь тест упадёт целиком. Все логи пишутся в один файл, так что всегда сразу понятен +порядок событий. А к зависшему тесту можно подключиться в отладчике прямо из goland. + +- `fixture.go` содержит код инициализации и остановки. Вам не нужно его менять. В `testdata/{{ .TestName }}` + хранится директория с исходным кодом, которую использует клиент в соответствующем тесте. `workdir/{{ .TestName }}` + сохраняет файлы после работы теста. +- `single_worker_test.go` содержит тесты с одним воркером. Каждый тест проверяет отдельную функциональность. + Отлаживайте тесты по одному, в порядке усложнения. +- `three_workers_test.go` содержит тесты с тремя воркерами. Приступайте к их отладке, после того как тесты с одним + воркером полностью пройдут. + +Все тесты останавливают окружение отменяя корневой контекст. Если ваш код где-то неправильно обрабатывает +отмену контекста, то тест может зависать на остановке. Вы можете отладить такое зависание, подключившись +к зависшему тесту в дебагере, или послав SIGQUIT зависшему процессу. \ No newline at end of file diff --git a/distbuild/disttest/fixture.go b/distbuild/disttest/fixture.go index 7c4ae8f..4fcf0fd 100644 --- a/distbuild/disttest/fixture.go +++ b/distbuild/disttest/fixture.go @@ -11,6 +11,8 @@ import ( "testing" "github.com/stretchr/testify/require" + "go.uber.org/goleak" + "gitlab.com/slon/shad-go/distbuild/pkg/api" "gitlab.com/slon/shad-go/distbuild/pkg/artifact" "gitlab.com/slon/shad-go/distbuild/pkg/client" @@ -152,5 +154,7 @@ func newEnv(t *testing.T, config *Config) (e *env, cancel func()) { cancelRootContext() _ = env.HTTP.Shutdown(context.Background()) _ = env.Logger.Sync() + + goleak.VerifyNone(t) } } diff --git a/distbuild/disttest/three_workers_test.go b/distbuild/disttest/three_workers_test.go index 42dddf0..82c4694 100644 --- a/distbuild/disttest/three_workers_test.go +++ b/distbuild/disttest/three_workers_test.go @@ -60,5 +60,5 @@ func TestArtifactTransferBetweenWorkers(t *testing.T) { wg.Wait() testDuration := time.Since(startTime) - assert.True(t, testDuration < time.Second*2, "test duration should be less than 2 seconds") + assert.True(t, testDuration < time.Second*5/2, "test duration should be less than 2.5 seconds") }