Задача Егора 2ч.
This commit is contained in:
@@ -81,7 +81,7 @@ sequenceDiagram
|
||||
| `TenantRoutingDataSource` | Маршрутизирует запросы и атомарно публикует неизменяемый снимок `TenantConfig + DataSource` |
|
||||
| `TenantDataSourceConfig` | Загружает конфигурацию тенантов из JSON-файла, создаёт HikariCP пулы |
|
||||
| `TenantWebMvcConfig` | Регистрирует `TenantInterceptor` и `AuthorizationInterceptor` в MVC-слое |
|
||||
| `TenantConfigWatcher` | Периодически (каждые 30 сек) перечитывает `tenants.json`, синхронизирует тенантов |
|
||||
| `TenantConfigWatcher` | Периодически перечитывает `tenants.json`, сравнивает полный нормализованный снимок и повторяет неудачную синхронизацию с ограниченным backoff |
|
||||
| `TenantConfigStore` | Задаёт явные атомарные операции upsert/remove и условную компенсацию persisted-конфигурации |
|
||||
| `KubernetesTenantSecretUpdater` | Реализует `TenantConfigStore` через GET и условный PUT Kubernetes Secret по `resourceVersion`, проверяя TLS по service-account CA |
|
||||
| `TenantLifecycleService` | Сериализует prepare/validate/migrate/persist/swap/remove и выполняет компенсацию при отказе |
|
||||
@@ -128,10 +128,11 @@ sequenceDiagram
|
||||
|
||||
### Жизненный цикл тенанта
|
||||
|
||||
1. **Добавление или обновление через API:** `TenantLifecycleService` нормализует конфигурацию,
|
||||
создаёт непубликуемый HikariCP candidate, проверяет реальное соединение и выполняет
|
||||
Flyway. Затем `TenantConfigStore` читает актуальный Secret и применяет к нему только
|
||||
upsert запрошенного домена.
|
||||
1. **Добавление или обновление через API:** перед мутацией `TenantConfigWatcher` разбирает,
|
||||
нормализует и при необходимости полностью применяет текущую mounted-проекцию как baseline.
|
||||
Ошибка подготовки baseline прерывает API-операцию. Затем `TenantLifecycleService` создаёт
|
||||
непубликуемый HikariCP candidate, проверяет реальное соединение, выполняет Flyway, а
|
||||
`TenantConfigStore` читает актуальный Secret и применяет к нему только upsert домена.
|
||||
2. **Межподовая запись:** `KubernetesTenantSecretUpdater` выполняет `GET` текущего
|
||||
`tenants-secret`, нормализует и сортирует список, после чего отправляет полный объект
|
||||
условным `PUT` с прочитанным `metadata.resourceVersion`. При `409 Conflict` актуальное
|
||||
@@ -140,6 +141,9 @@ sequenceDiagram
|
||||
не отправляет лишний `PUT`.
|
||||
3. **Атомарная локальная публикация:** только после успешной или подтверждённой повторным
|
||||
чтением персистенции одной публикацией заменяется связка `TenantConfig + DataSource`.
|
||||
Успешный lifecycle возвращает внутренний `TenantLifecycleMutationResult` с конфигурацией
|
||||
и `TenantSecretUpdateReceipt`, содержащей признаки `persisted`/`changed`, а также
|
||||
семантические снимки `previousTenants` и `committedTenants`.
|
||||
4. **Безопасный отказ и компенсация:** ошибка credentials, соединения, Flyway или Secret
|
||||
закрывает candidate, не меняя действующий route. Компенсация прежним снимком разрешена
|
||||
только для подтверждённой записи и только пока текущий Secret сохраняет выданный ей
|
||||
@@ -151,7 +155,10 @@ sequenceDiagram
|
||||
а прежний Hikari pool закрывается после завершения активных подключений либо по истечении
|
||||
настраиваемого grace timeout.
|
||||
6. **Синхронизация подов:** `TenantConfigWatcher` каждые 30 секунд проверяет смонтированный
|
||||
`tenants.json` и применяет добавление/удаление под тем же локальным lifecycle-monitor.
|
||||
`tenants.json` и сравнивает все поля нормализованного `TenantConfig`: `name`, `domain`,
|
||||
`url`, `username` и `password`. Для нового или изменённого тенанта lifecycle без повторной
|
||||
записи Secret выполняет prepare → проверку соединения → Flyway → атомарный swap; отсутствующие
|
||||
домены удаляются под тем же локальным monitor.
|
||||
7. **Удаление:** `DELETE /api/database/tenants/{domain}` применяет к актуальному Secret только
|
||||
удаление указанного домена через тот же условный `PUT`, затем атомарно исключает tenant
|
||||
из маршрутизации и передаёт pool на drain. Для отказа локального удаления действуют те же
|
||||
@@ -171,13 +178,25 @@ monitor по-прежнему не считается межподовой бл
|
||||
цепочки и обязательную проверку hostname. Trust-all fallback отсутствует: ошибка CA или TLS
|
||||
завершает персистенцию безопасным отказом.
|
||||
|
||||
`TenantConfigWatcher` сравнивает содержимое смонтированного из Secret `tenants.json` по
|
||||
SHA-256, а не по `String.hashCode()`, чтобы изменение файла не пропускалось из-за
|
||||
32-битной коллизии. На текущем этапе watcher активирует новые домены и удаляет отсутствующие,
|
||||
но ещё не заменяет подключение существующего домена при изменении его URL или credentials.
|
||||
Полная синхронизация такого изменения относится к проблеме №13; до её выполнения новое
|
||||
значение сбрасывает readiness этого tenant в состояние ожидания, а pod не должен считаться
|
||||
готовым по старому подключению.
|
||||
`TenantConfigWatcher` использует SHA-256 содержимого `tenants.json` как идентификатор файловой
|
||||
ревизии, но решения о запаздывающих проекциях принимает по нормализованным семантическим
|
||||
снимкам `name`/`domain`/`url`/`username`/`password`. Хеш становится последним применённым
|
||||
только после успешного разбора и полного lifecycle sync. Ошибка сохраняет прежний хеш,
|
||||
снимает readiness и запускает экспоненциальные повторы через 30–300 секунд; другая неудачная
|
||||
файловая ревизия получает собственную попытку без ожидания backoff предыдущей.
|
||||
|
||||
После API-операции snapshot fence устанавливается только если `TenantSecretUpdateReceipt`
|
||||
подтверждает реальное общее изменение: `persisted=true` и `changed=true`. Снимок до записи
|
||||
и ранее ожидавшиеся committed-снимки временно считаются deferred, а новый committed-снимок —
|
||||
ожидаемым. Поэтому при быстрых мутациях H0 → H1 → H2 запаздывающие H0 и H1 не откатывают
|
||||
runtime, а H2 полностью синхронизируется и снимает fence. Неизвестный объединённый снимок,
|
||||
которого нет среди deferred-состояний, также немедленно проходит полный sync и не теряет
|
||||
изменение другого pod. Локальная персистенция (`persisted=false`) и подтверждённый no-op
|
||||
(`changed=false`) fence не создают.
|
||||
|
||||
Если ошибка readiness произошла уже после успешного swap, следующий watcher retry видит
|
||||
совпадающую активную конфигурацию, проверяет действующее соединение и восстанавливает
|
||||
readiness без повторной миграции и второго swap.
|
||||
|
||||
### Liveness и readiness
|
||||
|
||||
|
||||
Reference in New Issue
Block a user