Update distbuild/README.md
This commit is contained in:
parent
a28ac30f2e
commit
3476778243
4 changed files with 72 additions and 173 deletions
|
@ -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 не будет, проверка нужна только чтобы удостовериться что
|
||||
посылка честно проходит все тесты.
|
||||
|
|
19
distbuild/disttest/README.md
Normal file
19
distbuild/disttest/README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# disttest
|
||||
|
||||
Пакет `disttest` содержит интеграционные тесты.
|
||||
|
||||
Тесты запускают все компоненты внутри одного процесса. Это сделано для удобства отладки. В случае
|
||||
паники в любом месте, весь тест упадёт целиком. Все логи пишутся в один файл, так что всегда сразу понятен
|
||||
порядок событий. А к зависшему тесту можно подключиться в отладчике прямо из goland.
|
||||
|
||||
- `fixture.go` содержит код инициализации и остановки. Вам не нужно его менять. В `testdata/{{ .TestName }}`
|
||||
хранится директория с исходным кодом, которую использует клиент в соответствующем тесте. `workdir/{{ .TestName }}`
|
||||
сохраняет файлы после работы теста.
|
||||
- `single_worker_test.go` содержит тесты с одним воркером. Каждый тест проверяет отдельную функциональность.
|
||||
Отлаживайте тесты по одному, в порядке усложнения.
|
||||
- `three_workers_test.go` содержит тесты с тремя воркерами. Приступайте к их отладке, после того как тесты с одним
|
||||
воркером полностью пройдут.
|
||||
|
||||
Все тесты останавливают окружение отменяя корневой контекст. Если ваш код где-то неправильно обрабатывает
|
||||
отмену контекста, то тест может зависать на остановке. Вы можете отладить такое зависание, подключившись
|
||||
к зависшему тесту в дебагере, или послав SIGQUIT зависшему процессу.
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue