livrare lot 2
This commit is contained in:
commit
8ecc78e729
763 changed files with 164593 additions and 0 deletions
188
backend/services/orchestration-layer/agent-v3/MIGRATION_MINIO.md
Normal file
188
backend/services/orchestration-layer/agent-v3/MIGRATION_MINIO.md
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# Migrare MinIO: local multi-bucket → cluster extern single-bucket
|
||||
|
||||
**Autor**: refactor 2026-04-25
|
||||
**Branch**: `feat/minio-cluster-single-bucket`
|
||||
**Scop**: mutarea fișierelor user-uploaded de pe `staging-dataLayer-minio` (bucket-per-user) pe cluster extern managed (`<minio-host>`, single bucket `didi-prod` cu prefix-uri).
|
||||
|
||||
---
|
||||
|
||||
## De ce single-bucket
|
||||
|
||||
Cluster extern (4 noduri MinIO `minio1..4.<minio-domain>`, EC:2, 466 GiB utilizabili) ne dă credentiale `didi-prod` cu **`s3:*` doar pe bucket-ul `didi-prod`**. Nu putem crea bucket-uri (testat: 3/3 încercări `Access Denied`).
|
||||
|
||||
Soluția: păstrăm conceptul "namespace per user" dar ca **prefix** în bucket-ul fix:
|
||||
|
||||
```
|
||||
ÎNAINTE (local, multi-bucket): DUPĂ (cluster, single-bucket):
|
||||
user-3/ didi-prod/
|
||||
images/ users/3/
|
||||
videos/ images/
|
||||
audio-files/ videos/
|
||||
user-19/ audio-files/
|
||||
images/ users/19/
|
||||
uploads/ images/
|
||||
image-files/ didi-prod/uploads/ ← legacy fallback
|
||||
didi-prod/image-files/ ← legacy fallback
|
||||
```
|
||||
|
||||
Numele de prefix-uri sistem (`uploads`, `image-files`, `audio-files`, `video-files`, `text-files`, `document-files`, `pipeline-artifacts`) sunt **identice cu numele bucket-urilor vechi** — astfel URL-urile vechi rămân interpretabile de proxy fără DB rewrite.
|
||||
|
||||
---
|
||||
|
||||
## Compatibilitate URL — backward compat
|
||||
|
||||
URL-urile vechi din `bos_analysis.analysis_session.input_media_url` și PG history pointează la formate vechi:
|
||||
|
||||
```
|
||||
https://didi365.eu/storage/user-3/images/abc.jpg ← format vechi user
|
||||
https://didi365.eu/api/v3/media/file/user-3/images/abc.jpg ← proxy vechi
|
||||
http://staging-dataLayer-minio:9000/image-files/foo.jpg ← bucket sistem
|
||||
```
|
||||
|
||||
Codul rezolvă toate aceste forme la canonic `didi-prod/<full-path>`:
|
||||
|
||||
| URL primit | Resolved bucket | Resolved key |
|
||||
|---|---|---|
|
||||
| `user-3/images/abc.jpg` | `didi-prod` | `users/3/images/abc.jpg` |
|
||||
| `image-files/foo.jpg` | `didi-prod` | `image-files/foo.jpg` |
|
||||
| `didi-prod/users/3/images/abc.jpg` | `didi-prod` | `users/3/images/abc.jpg` (passthrough) |
|
||||
|
||||
Implementat în:
|
||||
- `didiFramework/src/config/minio.ts` → `resolveBucketRequest(bucket, key)`
|
||||
- `agent-v3/src/shared/media/media-service.ts` → inline în `proxyFile()`
|
||||
|
||||
---
|
||||
|
||||
## Modificări în cod (branch `feat/minio-cluster-single-bucket`)
|
||||
|
||||
### didiFramework
|
||||
|
||||
| Fișier | Schimbare |
|
||||
|---|---|
|
||||
| `src/config/minio.ts` | Refactor complet: `BUCKET` fix, `resolveBucketRequest()`, `userObjectKey()`, no-op `ensureBucket()` în cluster mode, `createUserBucket()` lazy (doar logging) |
|
||||
| `src/routes/auth.ts` (endpoint `/internal/get-bucket-info`) | Returnează `bucketName=didi-prod` + `folder=users/{id}/{mimeFolder}` în loc de `bucketName=user-{id}` + `folder={mimeFolder}` |
|
||||
| `src/routes/uploads.ts` | **Nemodificat** — funcționează prin backward compat (caller pasează `bucket=image-files`, layer-ul îl rezolvă) |
|
||||
| `sql/migrations/010_add_user_storage_quota.sql` | NEW — adaugă `storage_used_bytes` + `storage_limit_bytes` la `internet_user` (înlocuiește bucket tags) |
|
||||
| `docker-compose.yml` | Env vars cu fallback `${MINIO_*:-default}` |
|
||||
|
||||
### agent-v3
|
||||
|
||||
| Fișier | Schimbare |
|
||||
|---|---|
|
||||
| `src/shared/media/media-service.ts` | `ensureBucket()` no-op în single-bucket mode. `proxyFile()` rezolvă legacy bucket → cluster prefix. Ownership check acceptă atât `bucket=user-{N}` cât și prefix `users/{N}/`. `uploadFile()` folosește `MINIO_BUCKET` env ca fallback |
|
||||
| `docker-compose.yml` | `MINIO_BUCKET`, `MINIO_USE_SSL`, `MINIO_PUBLIC_ENDPOINT` adăugate la `x-worker-env` cu fallback empty (= legacy multi-bucket) |
|
||||
|
||||
### Scripts
|
||||
|
||||
| Fișier | Schimbare |
|
||||
|---|---|
|
||||
| `scripts/minio-switch.sh` | NEW — swap între local/cluster. Sourceaza credentiale din `.cluster-credentials.env` |
|
||||
| `scripts/.cluster-credentials.env` | + `DIDI_MINIO_ACCESS_KEY` / `DIDI_MINIO_SECRET_KEY` / `DIDI_MINIO_DEV_*` |
|
||||
|
||||
---
|
||||
|
||||
## Cluster extern — date de conectare
|
||||
|
||||
```
|
||||
S3 API: http://<minio-host>:9000 (VIP 10.11.10.128)
|
||||
Console: http://<minio-console-host>:9001 (VIP 10.11.10.129)
|
||||
Region: us-east-1
|
||||
Path style: REQUIRED (forcePathStyle în SDK)
|
||||
TLS: false acum (HTTPS în curând prin HAProxy)
|
||||
|
||||
User PROD: didi-prod / 627074a6ceb8d8531feffaf4ee9cc45007350c58
|
||||
User DEV: didi-dev / 6f51811a4a157cb8f8d0d4376877552b780a06b8
|
||||
Bucket PROD: didi-prod (full s3:* doar pe bucket-ul propriu)
|
||||
Bucket DEV: didi-dev (idem)
|
||||
```
|
||||
|
||||
Infrastructură: 4 noduri Proxmox extern (`minio1..4.<minio-domain>`, IPs `10.11.10.124-127`), erasure coding EC:2, HAProxy + keepalived VRRP, 466 GiB utilizabili.
|
||||
|
||||
---
|
||||
|
||||
## Cutover plan — NEEXECUTAT încă
|
||||
|
||||
Codul e gata pe branch, **runtime-ul production rămâne pe local MinIO** până când executăm cutover-ul deliberat.
|
||||
|
||||
### Pre-cutover checklist
|
||||
|
||||
1. ✓ Backup complet creat: `/home/admin365/backups/pre-minio-refactor-20260425-0935/` (545 MB cu cod + git bundle + 476 MB MinIO data)
|
||||
2. ✓ Branch `feat/minio-cluster-single-bucket` cu refactor + tests
|
||||
3. ✓ Cluster `didi-dev` validat (write/list/delete cu prefix-uri noi)
|
||||
4. ⏳ Apply migration 010 pe PG cluster
|
||||
5. ⏳ Smoke test cu un user fictiv pe `didi-dev` (upload + analiză + URL public)
|
||||
|
||||
### Cutover steps
|
||||
|
||||
```bash
|
||||
# 1. Apply DB migration
|
||||
docker exec didi-framework node -e "
|
||||
const fs = require('fs');
|
||||
const { Pool } = require('pg');
|
||||
const pool = new Pool({ host: '10.11.50.167', port: 5000, user: 'bos_interface', password: 'interface', database: 'DIDI' });
|
||||
const sql = fs.readFileSync('/app/sql/migrations/010_add_user_storage_quota.sql', 'utf8');
|
||||
pool.query(sql).then(() => console.log('migration 010 applied')).catch(console.error).finally(() => pool.end());
|
||||
"
|
||||
|
||||
# 2. Mirror data local → cluster (~2 min pe LAN)
|
||||
mc alias set local http://staging-dataLayer-minio:9000 minioadmin minio123
|
||||
mc alias set prod http://<minio-host>:9000 didi-prod '<secret>'
|
||||
|
||||
# Per-user buckets → users/{id}/ prefix
|
||||
for u in $(mc ls local/ | grep -oE 'user-[0-9]+/' | tr -d '/'); do
|
||||
id=${u#user-}
|
||||
echo "mirroring $u → users/$id/"
|
||||
mc mirror local/$u prod/didi-prod/users/$id/
|
||||
done
|
||||
|
||||
# Sistem buckets → identical names ca prefix top-level
|
||||
for b in uploads image-files audio-files video-files text-files document-files pipeline-artifacts backups; do
|
||||
mc mirror local/$b prod/didi-prod/$b/
|
||||
done
|
||||
|
||||
# 3. Switch env la cluster
|
||||
./backend/services/orchestration-layer/scripts/minio-switch.sh cluster
|
||||
# (rescrie .env, restart didi-framework + agent-v3)
|
||||
|
||||
# 4. Smoke test
|
||||
curl -X POST https://didi365.eu/api/v3/pipeline/analyze ...
|
||||
|
||||
# 5. Monitor 24-48h. Local stays running ca fallback.
|
||||
```
|
||||
|
||||
### Rollback (30s)
|
||||
|
||||
```bash
|
||||
./backend/services/orchestration-layer/scripts/minio-switch.sh local
|
||||
```
|
||||
|
||||
Local container rămâne pornit și păstrează datele. Singura pierdere: upload-uri făcute între cutover și rollback nu sunt pe local. Acceptabil pentru fereastra de monitoring.
|
||||
|
||||
---
|
||||
|
||||
## Riscuri cunoscute
|
||||
|
||||
1. **Quota** — la cutover, `storage_used_bytes` în PG e populat din migration 010 doar dacă userul are deja subscription cu `storage_limit_gb`. Pentru calcul real folosit, după cutover rulez:
|
||||
```sql
|
||||
-- Reconciliere usage cu MinIO real (job periodic recomandat)
|
||||
SELECT internet_user_id FROM bos_sysadmin.internet_user WHERE storage_used_bytes = 0;
|
||||
-- Pentru fiecare → mc du didi-prod/users/{id}/ → UPDATE
|
||||
```
|
||||
|
||||
2. **TLS în curând** — endpoint va deveni `https://<minio-host>` (Let's Encrypt prin HAProxy). La acel moment: `MINIO_USE_SSL=true` + endpoint nou.
|
||||
|
||||
3. **Cleanup local cluster** — local `staging-dataLayer-minio` are 7 alte proiecte (lege365-corpus, ml-models, voice-recordings, publisher-assets, rafai-prod). NU le ștergem la cleanup.
|
||||
|
||||
4. **HAProxy failover** — VIP `10.11.10.128` rulează pe `cai1`. La failover poate fi o pauză de 2-5s. SDK-ul minio-js are retry implicit, dar la upload-uri mari (>100MB) merită setări explicite.
|
||||
|
||||
---
|
||||
|
||||
## Status build
|
||||
|
||||
```
|
||||
✓ tsc didiFramework — exit 0
|
||||
✓ tsc agent-v3 — zero erori noi (doar pre-existente pe `pg` module)
|
||||
✓ docker compose build — toate imagini OK
|
||||
✓ minio-switch.sh status — funcțional
|
||||
✓ Test write/list/delete pe didi-dev cu prefix-uri noi — validat
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue