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
|
```go
|
||||||
package graph
|
package build
|
||||||
|
|
||||||
import "crypto/sha1"
|
import "crypto/sha1"
|
||||||
|
|
||||||
|
@ -32,65 +33,33 @@ type ID [sha1.Size]byte
|
||||||
|
|
||||||
// Job описывает одну вершину графа сборки.
|
// Job описывает одну вершину графа сборки.
|
||||||
type Job struct {
|
type Job struct {
|
||||||
// ID задаёт уникальный идентификатор джоба.
|
// ID задаёт уникальный идентификатор джоба.
|
||||||
//
|
//
|
||||||
// ID вычисляется как хеш от всех входных файлов, команд запуска и хешей зависимых джобов.
|
// ID вычисляется как хеш от всех входных файлов, команд запуска и хешей зависимых джобов.
|
||||||
//
|
//
|
||||||
// Выход джоба целиком опеределяется его ID. Это важное свойство позволяет кешировать
|
// Выход джоба целиком опеределяется его ID. Это важное свойство позволяет кешировать
|
||||||
// результаты сборки.
|
// результаты сборки.
|
||||||
ID ID
|
ID ID
|
||||||
|
|
||||||
// Name задаёт человекочитаемое имя джоба.
|
// Name задаёт человекочитаемое имя джоба.
|
||||||
//
|
//
|
||||||
// Например:
|
// Например:
|
||||||
// build gitlab.com/slon/disbuild/pkg/b
|
// build gitlab.com/slon/disbuild/pkg/b
|
||||||
// vet gitlab.com/slon/disbuild/pkg/a
|
// vet gitlab.com/slon/disbuild/pkg/a
|
||||||
// test gitlab.com/slon/disbuild/pkg/test
|
// test gitlab.com/slon/disbuild/pkg/test
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Inputs задаёт список файлов из директории с исходным кодом,
|
// Inputs задаёт список файлов из директории с исходным кодом,
|
||||||
// которые нужны для работы этого джоба.
|
// которые нужны для работы этого джоба.
|
||||||
//
|
//
|
||||||
// В типичном случае, тут будут перечислены все .go файлы одного пакета.
|
// В типичном случае, тут будут перечислены все .go файлы одного пакета.
|
||||||
Inputs []string
|
Inputs []string
|
||||||
|
|
||||||
// Deps задаёт список джобов, выходы которых нужны для работы этого джоба.
|
// Deps задаёт список джобов, выходы которых нужны для работы этого джоба.
|
||||||
Deps []ID
|
Deps []ID
|
||||||
|
|
||||||
// Cmds описывает список команд, которые нужно выполнить в рамках этого джоба.
|
// Cmds описывает список команд, которые нужно выполнить в рамках этого джоба.
|
||||||
Cmds []Cmd
|
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
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -108,125 +77,32 @@ type Cmd struct {
|
||||||
3. Воркеры начинают выполнять вершины графа, пересылая друг другу выходные директории джобов.
|
3. Воркеры начинают выполнять вершины графа, пересылая друг другу выходные директории джобов.
|
||||||
4. Результаты работы джобов скачиваются на клиента.
|
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 описанием сборки. Сервер
|
После того, как все кубики будут готовы, нужно будет соединить их вместе, реализовав `distbuild/pkg/worker`,
|
||||||
стримит в body ответа json сообщения описывающие прогресс сборки (тут правильнее было бы использовать
|
`distbuild/pkg/client` и `distbuild/pkg/coordinator`. Код в этих пакетах нужно отлаживать на
|
||||||
websocket, но нас устраивает более простое решение).
|
интеграционных тестах в [`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"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/goleak"
|
||||||
|
|
||||||
"gitlab.com/slon/shad-go/distbuild/pkg/api"
|
"gitlab.com/slon/shad-go/distbuild/pkg/api"
|
||||||
"gitlab.com/slon/shad-go/distbuild/pkg/artifact"
|
"gitlab.com/slon/shad-go/distbuild/pkg/artifact"
|
||||||
"gitlab.com/slon/shad-go/distbuild/pkg/client"
|
"gitlab.com/slon/shad-go/distbuild/pkg/client"
|
||||||
|
@ -152,5 +154,7 @@ func newEnv(t *testing.T, config *Config) (e *env, cancel func()) {
|
||||||
cancelRootContext()
|
cancelRootContext()
|
||||||
_ = env.HTTP.Shutdown(context.Background())
|
_ = env.HTTP.Shutdown(context.Background())
|
||||||
_ = env.Logger.Sync()
|
_ = env.Logger.Sync()
|
||||||
|
|
||||||
|
goleak.VerifyNone(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,5 +60,5 @@ func TestArtifactTransferBetweenWorkers(t *testing.T) {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
testDuration := time.Since(startTime)
|
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