- Offline CLI `upload-migrate OLD_CONFIG NEW_CONFIG --component HOST
[--execute]`: dry run по умолчанию, local ↔ S3 и S3 ↔ S3 без удаления source. - Последовательное копирование с проверкой размера и полным SHA-256 readback destination; конфликтующий объект не перезаписывается. Временный диск — максимум один переносимый файл дополнительно к постоянному destination. - Durable migration journal позволяет продолжить прерванную операцию с теми же конфигурациями. Незавершённый или повреждённый journal блокирует startup; backend identity меняется только после проверки всех файлов. - Native `uploads`/`upload_stats`, slots и JWT сохраняются; поддерживаются native локальные файлы без Rust completion sidecar. Миграция требует остановки RustXMPP и Prosody, использующих общий `data_path`. - Multipart intent сохраняется до создания удалённого upload. Recovery находит потерянный UploadId через paginated listing и очищает только точный key принадлежащего slot. При сохранных metadata cleanup не зависит от lifecycle; lifecycle остаётся дополнительной защитой, а не гарантией. - Перед completion сохраняется durable marker. Неопределённый результат completion при отсутствии подтверждённого объекта помещает только этот slot в quarantine: PUT 409, retention не удаляет его, maintenance повторяет сверку. Startup продолжается; сохраняется single-use семантика. - Добавлены network regressions миграции и расширены проверки multipart recovery, потерянных ответов, отмены запросов и конфликтов destination. Настройка, команды миграции, recovery и ограничения отката: [docs/HTTP-UPLOAD.md](docs/HTTP-UPLOAD.md). Команды, результаты и границы подтверждения, включая qualification реального object store и 10 GiB: [docs/VALIDATION-0.7.9.md](docs/VALIDATION-0.7.9.md). Итог: 265 тестов, нагрузка 500 TLS клиентов и native reference — успешно. Реальные 10 GiB через MinIO (описанный test adapter), 1280parts и полный SHA-256 пройдены; подробности — [VALIDATION-0.7.9](docs/VALIDATION-0.7.9.md).
This commit is contained in:
1 parent
c260d7bab8
commit
b30fc524d8
41 files changed
+5936
-152
No files matched your search
@@ -0,0 +1,251 @@
|
||||
# HTTP File Upload (0.7.9)
|
||||
|
||||
XEP-0363 component `http_file_share` выдаёт slots через XMPP и обслуживает
|
||||
HTTP PUT/GET/HEAD. Доступны локальные файлы и S3-compatible object storage.
|
||||
В S3-режиме RustXMPP работает как gateway: клиент получает URL component,
|
||||
а сервер подписывает запросы к bucket по AWS Signature Version 4. Это не
|
||||
выдача клиенту S3 credentials или presigned bucket URLs.
|
||||
|
||||
## Несколько components
|
||||
|
||||
Каждый component сохраняет собственные XMPP JID, discovery, размер файла,
|
||||
квоты, slots, JWT secret, backend и HTTP mount. HTTP-маршрут выбирается по
|
||||
`Host` и `http_paths.file_share`. Можно использовать разные HTTP hosts с
|
||||
одинаковым mount либо один host с непересекающимися mounts:
|
||||
|
||||
```lua
|
||||
-- Добавьте к существующей конфигурации; сохраните VirtualHost, TLS и модули.
|
||||
Component "upload.example.org" "http_file_share"
|
||||
http_host = "files.example.org";
|
||||
http_paths = { file_share = "/personal"; }
|
||||
http_file_share_backend = "local";
|
||||
http_file_share_size_limit = 10737418240; -- 10 GiB
|
||||
http_file_share_daily_quota = 21474836480;
|
||||
http_file_share_global_quota = 107374182400;
|
||||
http_file_share_expires_after = "7d";
|
||||
|
||||
Component "media.example.org" "http_file_share"
|
||||
http_host = "files.example.org";
|
||||
http_paths = { file_share = "/media"; }
|
||||
http_file_share_backend = "local";
|
||||
http_file_share_size_limit = 104857600;
|
||||
```
|
||||
|
||||
Для одного effective HTTP host одинаковые или вложенные mounts отвергаются
|
||||
при startup с сообщением `ambiguous HTTP upload components`, содержащим
|
||||
имена обоих components и пути. Например, `/files` конфликтует с `/files`
|
||||
и `/files/private`; `/files` и `/files-other` не конфликтуют. Mount `/`
|
||||
пересекается со всеми mounts этого host. Hosts сравниваются без учёта
|
||||
регистра; порт в HTTP Host не выбирает отдельный component.
|
||||
|
||||
WebSocket, BOSH и `http_altconnect` используют общие HTTP listeners вместе
|
||||
с uploads. Их маршруты обрабатываются соответствующим middleware. Выделяйте
|
||||
uploads собственные mounts, чтобы действующие transport/discovery endpoints
|
||||
не перекрывали публичные адреса загрузки.
|
||||
|
||||
`http_external_url` задаёт публичную базу, сохраняя внутренний mount.
|
||||
`http_file_share_base_url` явно переопределяет полный публичный upload prefix.
|
||||
При reverse proxy настройте переписывание пути и передавайте правильный Host:
|
||||
одного изменения рекламируемого URL недостаточно для настройки proxy.
|
||||
Пример конфигурации proxy — [HTTP-PROXY.md](HTTP-PROXY.md).
|
||||
|
||||
## S3-compatible backend
|
||||
|
||||
```lua
|
||||
Component "upload-s3.example.org" "http_file_share"
|
||||
http_host = "files.example.org";
|
||||
http_paths = { file_share = "/objects"; }
|
||||
http_external_url = "https://files.example.org";
|
||||
http_file_share_backend = "s3";
|
||||
http_file_share_size_limit = 10737418240;
|
||||
http_file_share_daily_quota = 21474836480;
|
||||
http_file_share_global_quota = 107374182400;
|
||||
http_file_share_s3 = {
|
||||
endpoint = "https://objects.example.org";
|
||||
bucket = "xmpp-uploads";
|
||||
region = "us-east-1";
|
||||
access_key_env = "RUSTXMPP_S3_ACCESS_KEY";
|
||||
secret_key_env = "RUSTXMPP_S3_SECRET_KEY";
|
||||
-- Только для временных credentials:
|
||||
-- session_token_env = "RUSTXMPP_S3_SESSION_TOKEN";
|
||||
key_prefix = "upload-s3";
|
||||
}
|
||||
```
|
||||
|
||||
Переменные должны присутствовать в окружении процесса сервера при startup,
|
||||
например через защищённый EnvironmentFile службы. Их отсутствие вызывает
|
||||
ошибку startup. Доступны и строковые поля `access_key`, `secret_key`,
|
||||
`session_token`; соответствующее поле `*_env` имеет приоритет. Credentials
|
||||
не включаются в сохранённую backend identity.
|
||||
|
||||
Endpoint использует path-style bucket addressing. HTTPS требуется для
|
||||
нелокальных S3 endpoints. HTTP допускается только для `localhost` или
|
||||
loopback IP, например `http://127.0.0.1:9000` в изолированном тесте.
|
||||
Production endpoint должен иметь сертификат, доверенный системным root CAs.
|
||||
Не путайте HTTPS object-store endpoint с публичным HTTPS upload URL:
|
||||
оба соединения настраиваются независимо. Для публичных клиентов используйте
|
||||
HTTPS listener либо существующий TLS reverse proxy с HTTPS advertised URL.
|
||||
|
||||
Bucket должен разрешать создание multipart upload, загрузку частей, завершение,
|
||||
abort, `ListMultipartUploads` (`s3:ListBucketMultipartUploads`), HEAD/GET
|
||||
и удаление объектов для retention.
|
||||
Перед CreateMultipartUpload сервер сохраняет intent для принадлежащего ему
|
||||
slot. Если ответ с UploadId потерян, recovery перечисляет multipart uploads
|
||||
с pagination и прерывает только uploads с полным совпадением object key;
|
||||
соседние keys с тем же prefix не затрагиваются. Сохранных native metadata,
|
||||
подтверждающих владение slot, достаточно и для legacy records без intent или
|
||||
UploadId. Cleanup такого slot не зависит от bucket lifecycle.
|
||||
|
||||
Lifecycle `AbortIncompleteMultipartUpload` рекомендуется как дополнительная
|
||||
защита, например при утрате metadata или внешних orphan uploads. Он не даёт
|
||||
абсолютной гарантии очистки; scope, сроки и выполнение правил зависят от
|
||||
object store. Не запускайте внешних writers с теми же slot keys: cleanup
|
||||
предполагает исключительное владение ими.
|
||||
|
||||
Gateway передаёт файл частями по 8 MiB и ограничивает число одновременно
|
||||
активных S3 uploads восемью на component. Файл целиком не буферизуется в памяти и не
|
||||
сохраняется во временный локальный файл. Служебная память, TLS/HTTP buffers
|
||||
и буфер текущего входящего frame добавляются к памяти частей; ограничение
|
||||
не является гарантией точного общего RSS. Ограничение размера применяется
|
||||
и к chunked PUT, а квоты учитывают зарезервированные slots component.
|
||||
|
||||
## Metadata, recovery и смена credentials
|
||||
|
||||
XMPP upload metadata остаются в native хранилище `uploads` и `upload_stats`
|
||||
в `data_path`. S3 заменяет хранение содержимого файлов, а не XMPP metadata.
|
||||
Сохраняйте `data_path` вместе с bucket: один bucket не восстанавливает
|
||||
владельцев, quotas и slots. В `rust_uploads.json` сохраняются служебное
|
||||
состояние slots, completion, multipart intent и незавершённые UploadId;
|
||||
файл не заменяет native архив. `rust_upload_backend.json` фиксирует backend identity.
|
||||
|
||||
Перед открытием listeners сервер восстанавливает незавершённые S3 records:
|
||||
прерывает принадлежащие slot multipart uploads, затем проверяет удалённый
|
||||
объект и размер и учитывает уже завершённый commit. Ошибка обязательного
|
||||
recovery, кроме quarantine completion, останавливает startup. Перед
|
||||
CompleteMultipartUpload сохраняется `completion_attempted`. Если результат
|
||||
completion неизвестен и после cleanup
|
||||
HEAD ещё возвращает 404, только этот slot остаётся в quarantine: PUT возвращает
|
||||
409, retention его не удаляет, остальные slots и startup продолжают работу.
|
||||
Maintenance каждые 60 секунд повторяет сверку; подтверждённый объект
|
||||
ожидаемого размера снимает quarantine и завершает recovery. Неопределённый
|
||||
результат требует сверки оператором с object store, если объект так и не
|
||||
появился. Корректный явный S3 completion error допускает повтор после
|
||||
reconciliation; network/timeout или некорректный ответ не разрешают
|
||||
перезапись возможного позднего commit. Ошибки обязательных listing/HEAD
|
||||
по-прежнему останавливают startup.
|
||||
PUT остаётся single-use; повторная запись завершённого slot возвращает conflict.
|
||||
|
||||
При наличии slots нельзя незаметно переключить local/S3 backend, endpoint,
|
||||
bucket или prefix: startup отвергает изменение backend identity. Добавление
|
||||
S3 к существующим local slots также требует явной миграции через offline CLI,
|
||||
описанный ниже; не удаляйте identity-файл для обхода проверки.
|
||||
|
||||
Для rotation S3 credentials обновите переменные окружения/секреты службы
|
||||
и перезапустите сервер, сохранив endpoint, bucket и prefix. Новая identity
|
||||
credentials не содержит, поэтому их rotation не считается миграцией.
|
||||
Сначала обеспечьте новым credentials доступ к существующим объектам и
|
||||
multipart cleanup. Rotation `http_file_share_secret` — отдельная операция:
|
||||
смена JWT signing secret делает ранее выданные PUT tokens недействительными.
|
||||
|
||||
## Offline миграция local ↔ S3 и S3 ↔ S3
|
||||
|
||||
Остановите RustXMPP и Prosody, использующие этот `data_path`, и сохраните
|
||||
резервную копию metadata и содержимого backend. Подготовьте две полные
|
||||
конфигурации: `current.cfg.lua` описывает действующий source, `new.cfg.lua` —
|
||||
destination. Обе должны содержать тот же VirtualHost, upload component,
|
||||
`data_path`, HTTP/HTTPS ports, публичные маршруты и `http_file_share_secret`;
|
||||
меняйте только параметры backend. Credentials обоих S3 хранилищ должны быть
|
||||
доступны CLI; используйте отдельные переменные окружения для source и
|
||||
destination. Для каждого component выполняется отдельная команда:
|
||||
|
||||
```sh
|
||||
# План: без копирования объектов, изменения journal или backend identity.
|
||||
rust-xmpp upload-migrate current.cfg.lua new.cfg.lua --component upload.example.org
|
||||
|
||||
# Копирование и проверка; при остановке повторите эту же команду.
|
||||
rust-xmpp upload-migrate current.cfg.lua new.cfg.lua --component upload.example.org --execute
|
||||
|
||||
# После сообщения Migration committed запускайте сервер с новой конфигурацией.
|
||||
rust-xmpp serve new.cfg.lua --experimental
|
||||
```
|
||||
|
||||
Команда захватывает exclusive lock `data_path`, исключающий параллельный
|
||||
RustXMPP или другую миграцию; Prosody этот lock не использует. Source и
|
||||
destination должны иметь разные backend identities. Поддерживаются local →
|
||||
S3, S3 → local и S3 → другой endpoint, bucket или prefix. Перед миграцией
|
||||
восстановите незавершённые uploads исходным сервером. Dry run проверяет
|
||||
конфигурации и metadata и определяет объекты для копирования; полный SHA-256
|
||||
рассчитывается при `--execute`.
|
||||
|
||||
Копируются завершённые файлы, включая native файлы без Rust completion
|
||||
sidecar и файлы, опубликованные перед сбоем записи completion cache.
|
||||
Отсутствующие незавершённые reservations остаются в metadata. Native
|
||||
`uploads`, `upload_stats`, владельцы, slots и JWT не переписываются.
|
||||
Source objects никогда не удаляются. Для каждого файла CLI проверяет размер,
|
||||
считает SHA-256 source и повторно читает destination для сверки; ETag не
|
||||
используется как checksum. Существующий destination с другим содержимым
|
||||
вызывает отказ без перезаписи.
|
||||
|
||||
Миграция обрабатывает файлы последовательно через приватный временный файл:
|
||||
нужно свободное место размером максимум в один переносимый файл дополнительно
|
||||
к постоянному local destination. Память ограничена buffers частей, файл
|
||||
целиком в RAM не загружается. После сбоя временный spool удаляется и текущий
|
||||
файл читается заново; это возобновление по объектам, не по byte offset.
|
||||
|
||||
`rust_upload_migration.json` хранит identities, fingerprint metadata,
|
||||
checksums и прогресс. Повторный запуск с теми же конфигурациями сверяет source
|
||||
и уже скопированные файлы, восстанавливает multipart cleanup и продолжает
|
||||
работу. Backend identity переключается только после проверки всех объектов.
|
||||
Незавершённый или повреждённый journal блокирует startup до offline recovery,
|
||||
даже если component убран из новой конфигурации. Не удаляйте journal для
|
||||
обхода барьера и не редактируйте metadata во время миграции.
|
||||
|
||||
Сохранённый source позволяет сверить данные и подготовить откат, но простая
|
||||
подмена конфигурации после commit отвергается identity guard. Пока migration
|
||||
не завершена, продолжите её с теми же параметрами. После commit выполняйте
|
||||
обратную offline миграцию с текущей конфигурацией как source и прежней как
|
||||
destination. Новые uploads могли появиться только в destination; откат к
|
||||
старой резервной копии без их переноса потеряет эти изменения. Конфликтующие
|
||||
объекты обратная миграция также не перезаписывает. Удаление прежнего source
|
||||
после проверки результата — отдельное решение оператора.
|
||||
|
||||
## Проверки и границы подтверждения
|
||||
|
||||
`tests/upload_multi_network.py` проверяет настоящий binary через TLS XMPP
|
||||
и HTTP: отдельные/shared hosts, discovery и IQ slots, квоты, изоляцию JWT,
|
||||
PUT/GET и restart, а также композицию с WS/BOSH/host-meta и отказ при
|
||||
неоднозначных mounts.
|
||||
|
||||
`tests/upload_large_network.py` выполняет opt-in передачу полного файла по
|
||||
проверяемому HTTPS с local backend и записывает измерения в JSON. Для default
|
||||
10 GiB необходимо более 10 GiB свободного места. Это отдельная проверка от S3
|
||||
fixture, и её результат не подтверждает передачу 10 GiB в object store.
|
||||
|
||||
`tests/upload_s3_network.py` использует независимый локальный HTTP fixture,
|
||||
который повторно вычисляет SigV4 и реализует S3 protocol для проверки gateway,
|
||||
multipart, чтения и обработки ошибок. Это не qualification на AWS S3,
|
||||
MinIO или другом production object store. Их совместимость требует отдельного
|
||||
запуска в целевой среде.
|
||||
|
||||
`tests/upload_migration_network.py` проверяет offline миграцию с независимым
|
||||
SigV4 fixture: local ↔ S3, S3 ↔ S3, dry run, прерывание/возобновление,
|
||||
checksums, сохранность source и native metadata, блокировку startup.
|
||||
|
||||
`tests/upload_minio_network.py` — opt-in запуск с настоящим MinIO executable.
|
||||
По умолчанию он передаёт 10 GiB через HTTPS gateway в MinIO, проверяет полный
|
||||
GET и SHA-256, multipart, Range, restart и crash cleanup. Малый `--size`
|
||||
проверяет smoke path, но не подтверждает передачу большого объекта.
|
||||
|
||||
Итоговые команды, результаты и измерения release validation публикуются в
|
||||
[release validation report](VALIDATION-0.7.9.md). Предыдущий результат 10 GiB
|
||||
с local backend, SHA-256, Range за 4 GiB и выборочным RSS опубликован в
|
||||
[отчёте 0.7.8](VALIDATION-0.7.8.md); сам по себе он не подтверждает S3 backend.
|
||||
|
||||
|
||||
В 0.7.9 фактические 10 GiB HTTPS PUT/GET через MinIO успешно пройдены: 1280
|
||||
multipart parts, SHA-256 совпал, Range/restart/crash cleanup проверены.
|
||||
MinIO собран из upstream release с test-only fallback перечисления интерфейсов
|
||||
на loopback при EPERM; S3/auth/storage code не менялся. Точный diff и hashes
|
||||
в VALIDATION-0.7.9.md. Немодифицированный MinIO/AWS/distributed deployment этим
|
||||
не сертифицируются. MINIO_BINARY перед verify-local.sh включает opt-in 10 GiB
|
||||
прогон; для обычной среды используйте собственный MinIO executable.
|
||||
Reference in new issue
Block a user