Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions content/docs/api-service/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ attempts, and only when the error text contains `connection refused` or
## Executing the plugin

- The executable is `command[0]` from the plugin's registered config, with
`command[1:]` as arguments. It must resolve (after symlinks) to a path inside
`registry.plugins_dir`.
`command[1:]` as arguments. When the registration left `command` out, the
service recorded `{plugins_dir}/{group}/{name}/{version}/plugin`. It must
resolve (after symlinks) to a path inside `registry.plugins_dir`.
- The environment is empty except for the `env` map in the plugin's config. The
service's own credentials are not inherited.
- The request is written to stdin. Stdout is read up to
Expand All @@ -117,31 +118,38 @@ what it does not cover.
## Plugin storage

**Local mode** (no `registry.s3.bucket`): plugin binaries must already be under
`plugins_dir` — built there, mounted, or baked into an image. The registry row
only points at them.
`plugins_dir` — unpacked there by `easyp-svc plugins publish --plugins-dir`,
mounted, or baked into an image. The registry row only points at them.

**Object storage mode**: `plugins_dir` is a cache.

1. The operator packs each version directory into
`{group}/{name}/{version}/plugin.tgz` and uploads it (`easyp-svc plugins push`).
2. Registration (`CreatePlugin`) reads the archive from storage and records its
sha256 in the plugin config. If the archive is missing, registration fails
with `FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. The client cannot supply
the hash.
1. `easyp-svc plugins publish` packs each version directory into
`{group}/{name}/{version}/plugin.tgz` and uploads it with its sha256 in the
object metadata, then registers it — one version at a time, so nothing is
registered before its archive is in place.
2. Registration (`CreatePlugin`, or `UpdatePlugin` on a republish) checks the
archive is in storage, takes its sha256 from the object metadata (or reads
the object when there is none), and records it in the plugin config along
with the derived command. If the archive is missing, registration fails with
`FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. A `sha256` in the request is a
claim: if the stored archive has a different one, registration fails with
`FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`.
3. On the first request for that version, the archive is downloaded to
`plugins_dir/.tmp`, its sha256 compared with the recorded one, and unpacked.
Concurrent misses for the same key share one download.
`plugins_dir/.tmp`, its sha256 compared with the recorded one, and unpacked;
a `.sha256` marker beside the unpacked directory records which archive it
came from. A download that does not match fails with `FAILED_PRECONDITION` /
`CHECKSUM_MISMATCH` (before v1.2.0, `INTERNAL`). Concurrent misses for the
same key share one download.
4. The unpacked directories are evicted least-recently-used once they exceed
`registry.cache_max_bytes` (20 GiB). The archive stays in storage.

The order is therefore **build → push → register**. Re-pushing an archive with
`--force` after registration changes the object under a recorded checksum. A
host that already has the version unpacked keeps running the old binary — the
checksum is checked only on download — while a cold cache fails with
`plugin archive checksum mismatch`. Registering again does not help: the
version already exists and `plugins register` skips it. Publish a new version
instead, or `DeletePlugin` the version (which removes the row, the archive and
the cached copy), push, and register.
Republishing a version (`plugins publish --force`) uploads the new archive and
replaces the recorded checksum through `UpdatePlugin`. On next use every replica
sees that its `.sha256` marker no longer matches the registry and refetches; a
directory without a marker (unpacked before v1.2.0) counts as current. An
archive overwritten in storage any other way no longer has the recorded
checksum, and its next download fails with `CHECKSUM_MISMATCH` — republish it
with `publish --force`, or publish a new version.

A storage outage, or an archive that has disappeared from the bucket, fails
generation with `UNAVAILABLE` / `STORAGE_UNAVAILABLE`.
Expand Down
48 changes: 29 additions & 19 deletions content/docs/api-service/architecture.ru.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ gRPC-статусы, и конвертация снаружи переписал
## Исполнение плагина

- Исполняемый файл — `command[0]` из зарегистрированного конфига плагина,
аргументы — `command[1:]`. После разрешения симлинков он обязан лежать внутри
`registry.plugins_dir`.
аргументы — `command[1:]`. Если при регистрации `command` не задан, сервис
записал `{plugins_dir}/{group}/{name}/{version}/plugin`. После разрешения
симлинков он обязан лежать внутри `registry.plugins_dir`.
- Окружение пустое, кроме карты `env` из конфига плагина. Учётные данные самого
сервиса не наследуются.
- Запрос пишется в stdin. Stdout читается до `registry.max_output_size`
Expand All @@ -118,30 +119,39 @@ gRPC-статусы, и конвертация снаружи переписал
## Хранение плагинов

**Локальный режим** (нет `registry.s3.bucket`): бинарники плагинов уже должны
лежать в `plugins_dir` — собраны там, смонтированы или вшиты в образ. Запись в
реестре только указывает на них.
лежать в `plugins_dir` — распакованы туда `easyp-svc plugins publish
--plugins-dir`, смонтированы или вшиты в образ. Запись в реестре только
указывает на них.

**Режим объектного хранилища**: `plugins_dir` — это кеш.

1. Оператор упаковывает каталог каждой версии в
`{group}/{name}/{version}/plugin.tgz` и загружает его (`easyp-svc plugins push`).
2. Регистрация (`CreatePlugin`) читает архив из хранилища и записывает его
sha256 в конфиг плагина. Если архива нет, регистрация падает с
`FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. Клиент не может подставить хеш.
1. `easyp-svc plugins publish` упаковывает каталог каждой версии в
`{group}/{name}/{version}/plugin.tgz` и загружает его с sha256 в метаданных
объекта, а затем регистрирует — по одной версии за раз, так что ничего не
регистрируется раньше, чем архив окажется на месте.
2. Регистрация (`CreatePlugin` или, при повторной публикации, `UpdatePlugin`)
проверяет, что архив лежит в хранилище, берёт его sha256 из метаданных
объекта (или читает сам объект, если их нет) и записывает её в конфиг плагина
вместе с выведенной командой. Если архива нет, регистрация падает с
`FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. `sha256` в запросе — это
утверждение: если у архива в хранилище другая сумма, регистрация падает с
`FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`.
3. При первом запросе этой версии архив скачивается в `plugins_dir/.tmp`, его
sha256 сверяется с записанным, и он распаковывается. Одновременные промахи
по одному ключу делят одно скачивание.
sha256 сверяется с записанным, и он распаковывается; маркер `.sha256` рядом с
распакованным каталогом запоминает, из какого архива он получен. Несовпавшее
скачивание падает с `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH` (до v1.2.0 —
`INTERNAL`). Одновременные промахи по одному ключу делят одно скачивание.
4. Распакованные каталоги вытесняются по LRU, когда их объём превышает
`registry.cache_max_bytes` (20 ГиБ). Архив в хранилище остаётся.

Поэтому порядок — **build → push → register**. Повторная загрузка архива с
`--force` после регистрации меняет объект под записанной контрольной суммой.
Хост, у которого эта версия уже распакована, продолжает запускать старый
бинарник — сумма проверяется только при скачивании, — а при холодном кеше
скачивание падает с `plugin archive checksum mismatch`. Повторная регистрация не
помогает: версия уже существует, и `plugins register` её пропускает. Выпустите
новую версию либо удалите эту через `DeletePlugin` (удаляется строка, архив и
закешированная копия), затем push и register.
Повторная публикация версии (`plugins publish --force`) загружает новый архив и
заменяет записанную контрольную сумму через `UpdatePlugin`. При следующем
обращении каждая реплика видит, что её маркер `.sha256` больше не совпадает с
реестром, и скачивает архив заново; каталог без маркера (распакованный до
v1.2.0) считается актуальным. Архив, перезаписанный в хранилище любым другим
способом, уже не имеет записанной контрольной суммы, и его следующее скачивание
падает с `CHECKSUM_MISMATCH` — перепубликуйте его через `publish --force` или
выпустите новую версию.

Недоступность хранилища или исчезнувший из бакета архив дают при генерации
`UNAVAILABLE` / `STORAGE_UNAVAILABLE`.
Expand Down
17 changes: 10 additions & 7 deletions content/docs/api-service/backup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ matters: a `plugins` row without its archive fails generation with
the missing key. An archive with no row is merely orphaned. **So prefer a bucket snapshot slightly newer than the database
one** — never older.

"Newer" is safe only while archives are never overwritten. If an archive was
re-pushed with `--force` between the two snapshots, the newer bucket holds an
object whose sha256 is not the one the restored row recorded, and that plugin
fails with `plugin archive checksum mismatch` on its first download. Two ways to
rule it out: never overwrite a registered version (publish a new one instead),
or enable object versioning on the bucket and restore the object version that
existed at the database snapshot's time.
"Newer" is safe only while archives are never overwritten. If a version was
republished with `plugins publish --force` between the two snapshots, the newer
bucket holds an object whose sha256 is not the one the restored row recorded,
and that plugin fails with `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH` on its
first download. Republishing it once more after the restore —
`plugins publish --force --filter '<group>/<name>:<version>'` — records the
checksum of what the bucket holds. To rule it out instead: never overwrite a
registered version (publish a new one), or enable object versioning on the
bucket and restore the object version that existed at the database snapshot's
time.

## How much history to keep

Expand Down
15 changes: 9 additions & 6 deletions content/docs/api-service/backup.ru.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ description: "Что обязано попасть в бэкап API Service, с
брать чуть свежее снимка базы** — но никогда не старее.

«Свежее» безопасно, только пока архивы не перезаписываются. Если между двумя
снимками архив перезалили с `--force`, в более свежем бакете лежит объект, чей
sha256 не совпадает с записанным в восстановленной строке, и этот плагин падает
с `plugin archive checksum mismatch` при первом скачивании. Исключить это можно
двумя способами: никогда не перезаписывать зарегистрированную версию (выпускать
новую) или включить версионирование объектов в бакете и восстанавливать ту
версию объекта, что существовала на момент снимка базы.
снимками версию перепубликовали через `plugins publish --force`, в более свежем
бакете лежит объект, чей sha256 не совпадает с записанным в восстановленной
строке, и этот плагин падает с `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH` при
первом скачивании. Ещё одна перепубликация после восстановления —
`plugins publish --force --filter '<group>/<name>:<version>'` — запишет
контрольную сумму того, что лежит в бакете. Исключить это заранее можно двумя
способами: никогда не перезаписывать зарегистрированную версию (выпускать новую)
или включить версионирование объектов в бакете и восстанавливать ту версию
объекта, что существовала на момент снимка базы.

## Сколько хранить историю

Expand Down
15 changes: 13 additions & 2 deletions content/docs/api-service/client-usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func main() {
| `WithToken(token)` | — | `authorization: Bearer` on every call. Needed for writes, and for reads when the service requires authentication. |
| `WithGenerateCodeTimeout(d)` | 30 s | Per-call deadline when the context has none. Raise it towards the server's `generation_timeout`. |
| `WithListPluginsTimeout(d)` | 10 s | Spans the whole paginated walk. |
| `WithCreatePluginTimeout(d)` | 120 s | Registration reads the archive from storage. |
| `WithCreatePluginTimeout(d)` | 120 s | Registration checks the archive in storage, and reads it when the object carries no checksum. |
| `WithMaxRetries(n)`, `WithRetryBaseDelay(d)`, `WithRetryMaxDelay(d)` | 3, 100 ms, 5 s | Exponential backoff. |
| `WithMaxRecvMsgSize(n)` | 64 MiB | Matches the server's output limit. |
| `WithHealthCheck(interval)` | 30 s | Background connection-state check. |
Expand All @@ -131,6 +131,16 @@ re-runs a generation that already used its full timeout.
Methods: `GenerateCode`, `ListPlugins` (walks every page), `CreatePlugin`,
`UpdatePlugin`, `DeletePlugin`, `Close`.

`CreatePlugin` and `UpdatePlugin` take a `config` struct. On a v1.2.0 or newer
service `command` may be omitted: the service derives
`{plugins_dir}/{group}/{name}/{version}/plugin`, so a client need not know the
server's `plugins_dir` (older services require it). A `sha256` key is a claim
about the archive in storage — registration fails with `FAILED_PRECONDITION` /
`CHECKSUM_MISMATCH` unless the stored archive has exactly that checksum; without
it the service records the stored archive's checksum itself. A config holding
only `sha256` is what `easyp-svc plugins publish` sends. All keys:
[Plugins](/docs/api-service/plugins#registered-configuration).

## gRPC directly

The contract is `easyp.generator.v1.GeneratorAPI` in
Expand Down Expand Up @@ -173,7 +183,8 @@ stable `reason`. Branch on the reason, not the message.
| `SERVER_OVERLOADED` | `RESOURCE_EXHAUSTED` | Worker or generation queue full. |
| `MAX_PLUGINS_EXCEEDED` | `RESOURCE_EXHAUSTED` | Community ceiling of 10 versions. |
| `ALREADY_EXISTS` | `ALREADY_EXISTS` | Version already registered. |
| `BINARY_NOT_UPLOADED` | `FAILED_PRECONDITION` | Registering before `plugins push`. |
| `BINARY_NOT_UPLOADED` | `FAILED_PRECONDITION` | Registering a version whose archive is not in storage. |
| `CHECKSUM_MISMATCH` | `FAILED_PRECONDITION` | The config's `sha256` is not the stored archive's, or a downloaded archive does not match the registered checksum (before v1.2.0 the latter was `INTERNAL`). Republish the version. |
| `STORAGE_UNAVAILABLE` | `UNAVAILABLE` | Object storage down, or the archive is gone. |
| `SHUTTING_DOWN` | `UNAVAILABLE` | The service is draining. |
| `FEATURE_DENIED` | `PERMISSION_DENIED` | Licence does not include the feature. |
Expand Down
Loading
Loading